summaryrefslogtreecommitdiff
path: root/src/VideoMonitor.vala
blob: 7f2fd3b5504c1402091ccc90b7bf7b69260bf7e7 (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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/* Copyright 2010-2015 Yorba Foundation
 *
 * This software is licensed under the GNU Lesser General Public License
 * (version 2.1 or later).  See the COPYING file in this distribution.
 */

private class VideoUpdates : MonitorableUpdates {
    public Video video;
    
    private bool check_interpretable = false;
    
    public VideoUpdates(Video video) {
        base (video);
        
        this.video = video;
    }
    
    public virtual void set_check_interpretable(bool check) {
        check_interpretable = check;
    }
    
    public override void mark_online() {
        base.mark_online();
        
        set_check_interpretable(true);
    }
    
    public bool is_check_interpretable() {
        return check_interpretable;
    }
    
    public override bool is_all_updated() {
        return (check_interpretable == false) && base.is_all_updated();
    }
}

private class VideoMonitor : MediaMonitor {
    private const int MAX_INTERPRETABLE_CHECKS_PER_CYCLE = 5;
    
    // Performs interpretable check on video. In a background job because
    // this will create a new thumbnail for the video.
    private class VideoInterpretableCheckJob : BackgroundJob {
        // IN
        public Video video;
        
        // OUT
        public Video.InterpretableResults? results = null;
        
        public VideoInterpretableCheckJob(Video video, CompletionCallback? callback = null) {
            base (video, callback);
            this.video = video;
        }
        
        public override void execute() {
            results = video.check_is_interpretable();
        }
    }
    
    // Work queue for video thumbnailing.
    // Note: only using 1 thread. If we want to change this to use multiple
    // threads, we need to put a lock around background_jobs wherever it's modified.
    private Workers workers = new Workers(1, false);
    private uint64 background_jobs = 0;
    
    public VideoMonitor(Cancellable cancellable) {
        base (Video.global, cancellable);
                
        foreach (DataObject obj in Video.global.get_all()) {
            Video video = obj as Video;
            assert (video != null);
            if (!video.get_is_interpretable())
                set_check_interpretable(video, true);
        }
    }
    
    protected override MonitorableUpdates create_updates(Monitorable monitorable) {
        assert(monitorable is Video);
        
        return new VideoUpdates((Video) monitorable);
    }
    
    public override MediaSourceCollection get_media_source_collection() {
        return Video.global;
    }
    
    public override bool is_file_represented(File file) {
        VideoSourceCollection.State state;
        return get_state(file, out state) != null;
    }
    
    public override MediaMonitor.DiscoveredFile notify_file_discovered(File file, FileInfo info,
        out Monitorable monitorable) {
        VideoSourceCollection.State state;
        Video? video = get_state(file, out state);
        if (video == null) {
            monitorable = null;
            
            return MediaMonitor.DiscoveredFile.UNKNOWN;
        }
        
        switch (state) {
            case VideoSourceCollection.State.ONLINE:
            case VideoSourceCollection.State.OFFLINE:
                monitorable = video;
                
                return MediaMonitor.DiscoveredFile.REPRESENTED;
            
            case VideoSourceCollection.State.TRASH:
            default:
                // ignored ... trash always stays in trash
                monitorable = null;
                
                return MediaMonitor.DiscoveredFile.IGNORE;
        }
    }
    
    public override Gee.Collection<Monitorable>? candidates_for_unknown_file(File file, FileInfo info,
        out MediaMonitor.DiscoveredFile result) {
        Gee.Collection<Video> matched = new Gee.ArrayList<Video>();
        Video.global.fetch_by_matching_backing(info, matched);
        
        result = MediaMonitor.DiscoveredFile.UNKNOWN;
        
        return matched;
    }
    
    public override bool notify_file_created(File file, FileInfo info) {
        VideoSourceCollection.State state;
        Video? video = get_state(file, out state);
        if (video == null)
            return false;
        
        update_online(video);
        
        return true;
    }
    
    public override bool notify_file_moved(File old_file, File new_file, FileInfo new_file_info) {
        VideoSourceCollection.State old_state;
        Video? old_video = get_state(old_file, out old_state);
        
        VideoSourceCollection.State new_state;
        Video? new_video = get_state(new_file, out new_state);
        
        // Four possibilities:
        //
        // 1. Moving an existing photo file to a location where no photo is represented
        //    Operation: have the Photo object move with the file.
        // 2. Moving a file with no representative photo to a location where a photo is represented
        //    (i.e. is offline).  Operation: Update the photo (backing has changed).
        // 3. Moving a file with no representative photo to a location with no representative
        //    photo.  Operation: Enqueue for import (if appropriate).
        // 4. Move a file with a representative photo to a location where a photo is represented
        //    Operation: Mark the old photo as offline (or drop editable) and update new photo
        //    (the backing has changed).
        
        if (old_video != null && new_video == null) {
            // 1.
            update_master_file(old_video, new_file);
        } else if (old_video == null && new_video != null) {
            // 2.
            set_check_interpretable(new_video, true);
        } else if (old_video == null && new_video == null) {
            // 3.
            return false;
        } else {
            assert(old_video != null && new_video != null);
            
            // 4.
            update_offline(old_video);
            set_check_interpretable(new_video, true);
        }
        
        return true;
    }
    
    public override bool notify_file_altered(File file) {
        VideoSourceCollection.State state;
        return get_state(file, out state) != null;
    }
    
    public override bool notify_file_attributes_altered(File file) {
        VideoSourceCollection.State state;
        Video? video = get_state(file, out state);
        if (video == null)
            return false;
        
        update_master_file_info_altered(video);
        update_master_file_in_alteration(video, true);
        
        return true;
    }
    
    public override bool notify_file_alteration_completed(File file, FileInfo info) {
        VideoSourceCollection.State state;
        Video? video = get_state(file, out state);
        if (video == null)
            return false;
        
        update_master_file_alterations_completed(video, info);
        
        return true;
    }
    
    public override bool notify_file_deleted(File file) {
        VideoSourceCollection.State state;
        Video? video = get_state(file, out state);
        if (video == null)
            return false;
        
        update_master_file_in_alteration(video, false);
        update_offline(video);
        
        return true;
    }
    
    private Video? get_state(File file, out VideoSourceCollection.State state) {
        File? real_file = null;
        foreach (Monitorable monitorable in get_monitorables()) {
            Video video = (Video) monitorable;
            
            VideoUpdates? updates = get_existing_video_updates(video);
            if (updates == null)
                continue;
            
            if (updates.get_master_file() != null && updates.get_master_file().equal(file)) {
                real_file = video.get_master_file();
                
                break;
            }
        }
        
        return Video.global.get_state_by_file(real_file ?? file, out state);
    }
    
    public VideoUpdates fetch_video_updates(Video video) {
        VideoUpdates? updates = fetch_updates(video) as VideoUpdates;
        assert(updates != null);
        
        return updates;
    }
    
    public VideoUpdates? get_existing_video_updates(Video video) {
        return get_existing_updates(video) as VideoUpdates;
    }
    
    public void set_check_interpretable(Video video, bool check) {
        fetch_video_updates(video).set_check_interpretable(check);
    }
    
    protected override void process_updates(Gee.Collection<MonitorableUpdates> all_updates,
        TransactionController controller, ref int op_count) throws Error {
        base.process_updates(all_updates, controller, ref op_count);
        
        Gee.ArrayList<Video>? check = null;
        
        foreach (MonitorableUpdates monitorable_updates in all_updates) {
            if (op_count >= MAX_OPERATIONS_PER_CYCLE)
                break;
            
            // use a separate limit on interpretable checks because they're more expensive than
            // simple database commands
            if (check != null && check.size >= MAX_INTERPRETABLE_CHECKS_PER_CYCLE)
                break;
            
            VideoUpdates? updates = monitorable_updates as VideoUpdates;
            if (updates == null)
                continue;
            
            if (updates.is_check_interpretable()) {
                if (check == null)
                    check = new Gee.ArrayList<Video>();
                
                check.add(updates.video);
                updates.set_check_interpretable(false);
                op_count++;
            }
        }
        
        if (check != null) {
            mdbg("Checking interpretable for %d videos".printf(check.size));
            
            Video.notify_offline_thumbs_regenerated();
            
            background_jobs += check.size;
            foreach (Video video in check)
                workers.enqueue(new VideoInterpretableCheckJob(video, on_interpretable_check_complete));
        }
    }
    
    void on_interpretable_check_complete(BackgroundJob j) {
        VideoInterpretableCheckJob job = (VideoInterpretableCheckJob) j;
        
        job.results.foreground_finish();
        
        --background_jobs;
        if (background_jobs <= 0)
            Video.notify_normal_thumbs_regenerated();
    }
}