summaryrefslogtreecommitdiff
path: root/src/VideoMonitor.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/VideoMonitor.vala')
-rw-r--r--src/VideoMonitor.vala301
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();
+ }
+}
+