summaryrefslogtreecommitdiff
path: root/src/MediaInterfaces.vala
blob: 37209cfde75a0c5a202260d500ecec1ba6e22bae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

//
// Going forward, Shotwell will use MediaInterfaces, which allow for various operations and features
// to be added only to the MediaSources that support them (or make sense for).  For example, adding
// a library-mode photo or video to an Event makes perfect sense, but does not make sense for a
// direct-mode photo.  All three are MediaSources, and to make DirectPhoto descend from another
// base class is only inviting chaos and a tremendous amount of replicated code.
//
// A key point to make of all MediaInterfaces is that they require MediaSource as a base class.
// Thus, any code dealing with one of these interfaces knows they are also dealing with a
// MediaSource.
//
// TODO: Make Eventable and Taggable interfaces, which are the only types Event and Tag will deal
// with (rather than MediaSources).
//
// TODO: Make Trashable interface, which are much like Flaggable.
//
// TODO: ContainerSources may also have specific needs in the future; an interface-based system
// may make sense as well when that need arises.
//

//
// TransactionController
//
// Because many operations in Shotwell need to be performed on collections of objects all at once,
// and that most of these objects are backed by a database, the TransactionController object gives 
// a way to generically group a series of operations on one or more similar objects into a single
// transaction. This class is listed here because it's used by the various media interfaces to offer
// multiple operations.
//
// begin() and commit() may be called multiple times in layering fashion.  The implementation
// accounts for this.  If either throws an exception it should be assumed that the object is in
// a "clean" state; that is, if begin() throws an exception, there is no need to call commit(),
// and if commit() throws an exception, it does not need to be called again to revert the object
// state.
//
// This means that any user who calls begin() *must* match it with a corresponding commit(), even
// if there is an error during the transaction.  It is up to the user to back out any undesired
// changes.
//
// Because of the nature of this object, it's assumed that every object type will share one
// between all callers.
//
// The object is thread-safe.  There is no guarantee that the underlying persistent store is,
// however.
public abstract class TransactionController {
    private int count = 0;
    
    public TransactionController() {
    }
    
    ~TransactionController() {
        lock (count) {
            assert(count == 0);
        }
    }
    
    public void begin() {
        lock (count) {
            if (count++ != 0)
                return;
            
            try {
                begin_impl();
            } catch (Error err) {
                // unwind
                count--;
                
                if (err is DatabaseError)
                    AppWindow.database_error((DatabaseError) err);
                else
                    AppWindow.panic("%s".printf(err.message));
            }
        }
    }
    
    // For thread safety, this method will only be called under the protection of a mutex.
    public abstract void begin_impl() throws Error;
    
    public void commit() {
        lock (count) {
            assert(count > 0);
            if (--count != 0)
                return;
            
            // no need to unwind the count here; it's already unwound.
            try {
                commit_impl();
            } catch (Error err) {
                if (err is DatabaseError)
                    AppWindow.database_error((DatabaseError) err);
                else
                    AppWindow.panic("%s".printf(err.message));
            }
        }
    }
    
    // For thread safety, this method will only be called under the protection of a mutex.
    public abstract void commit_impl() throws Error;
}

//
// Flaggable
//
// Flaggable media can be marked for later use in batch operations.
//
// The mark_flagged() and mark_unflagged() methods should fire "metadata:flags" and "metadata:flagged"
// alterations if the flag has changed.
public interface Flaggable : MediaSource {
    public abstract bool is_flagged();
    
    public abstract void mark_flagged();
    
    public abstract void mark_unflagged();
    
    public static void mark_many_flagged_unflagged(Gee.Collection<Flaggable>? flag,
        Gee.Collection<Flaggable>? unflag, TransactionController controller) throws Error {
        controller.begin();
        
        if (flag != null) {
            foreach (Flaggable flaggable in flag)
                flaggable.mark_flagged();
        }
        
        if (unflag != null) {
            foreach (Flaggable flaggable in unflag)
                flaggable.mark_unflagged();
        }
        
        controller.commit();
    }
}

//
// Monitorable
//
// Monitorable media can be updated at startup or run-time about changes to their backing file(s).
//
// The mark_online() and mark_offline() methods should fire "metadata:flags" and "metadata:online-state"
// alterations if the flag has changed.
//
// The set_master_file() method should fire "backing:master" alteration and "metadata:name" if
// the name of the file is determined by the filename (which is default behavior).  It should also
// call notify_master_file_replaced().
//
// The set_master_timestamp() method should fire "metadata:master-timestamp" alteration.
public interface Monitorable : MediaSource {
    public abstract bool is_offline();
    
    public abstract void mark_online();
    
    public abstract void mark_offline();
    
    public static void mark_many_online_offline(Gee.Collection<Monitorable>? online,
        Gee.Collection<Monitorable>? offline, TransactionController controller) throws Error {
        controller.begin();
        
        if (online != null) {
            foreach (Monitorable monitorable in online)
                monitorable.mark_online();
        }
        
        if (offline != null) {
            foreach (Monitorable monitorable in offline)
                monitorable.mark_offline();
        }
        
        controller.commit();
    }
    
    public abstract void set_master_file(File file);
    
    public static void set_many_master_file(Gee.Map<Monitorable, File> map,
        TransactionController controller) throws Error {
        controller.begin();
        
        Gee.MapIterator<Monitorable, File> map_iter = map.map_iterator();
        while (map_iter.next())
            map_iter.get_key().set_master_file(map_iter.get_value());
        
        controller.commit();
    }
    
    public abstract void set_master_timestamp(FileInfo info);
    
    public static void set_many_master_timestamp(Gee.Map<Monitorable, FileInfo> map,
        TransactionController controller) throws Error {
        controller.begin();
        
        Gee.MapIterator<Monitorable, FileInfo> map_iter = map.map_iterator();
        while (map_iter.next())
            map_iter.get_key().set_master_timestamp(map_iter.get_value());
        
        controller.commit();
    }
}

//
// Dateable
//
// Dateable media may have their exposure date and time set arbitrarily. 
//
// The set_exposure_time() method refactors the existing set_exposure_time()
// from Photo to here in order to add this capability to videos. It should 
// fire a "metadata:exposure-time" alteration when called.
public interface Dateable : MediaSource {
    public abstract void set_exposure_time(time_t target_time);    
    
    public abstract time_t get_exposure_time();
}