diff options
Diffstat (limited to 'src/VideoMonitor.vala')
-rw-r--r-- | src/VideoMonitor.vala | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/src/VideoMonitor.vala b/src/VideoMonitor.vala new file mode 100644 index 0000000..f062999 --- /dev/null +++ b/src/VideoMonitor.vala @@ -0,0 +1,301 @@ +/* Copyright 2010-2014 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(); + } +} + |