diff options
Diffstat (limited to 'src/PhotoMonitor.vala')
-rw-r--r-- | src/PhotoMonitor.vala | 1156 |
1 files changed, 1156 insertions, 0 deletions
diff --git a/src/PhotoMonitor.vala b/src/PhotoMonitor.vala new file mode 100644 index 0000000..d7f2929 --- /dev/null +++ b/src/PhotoMonitor.vala @@ -0,0 +1,1156 @@ +/* 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 PhotoUpdates : MonitorableUpdates { + public LibraryPhoto photo; + + public bool reimport_master = false; + public bool reimport_editable = false; + public bool reimport_raw_developments = false; + public File? editable_file = null; + public bool editable_file_info_altered = false; + public bool raw_developer_file_info_altered = false; + public FileInfo? editable_file_info = null; + public bool editable_in_alteration = false; + public bool raw_development_in_alteration = false; + public bool revert_to_master = false; + public Gee.Collection<File> developer_files = new Gee.ArrayList<File>(); + + public PhotoUpdates(LibraryPhoto photo) { + base (photo); + + this.photo = photo; + } + + public override void mark_offline() { + base.mark_offline(); + + reimport_master = false; + reimport_editable = false; + reimport_raw_developments = false; + } + + public bool is_reimport_master() { + return reimport_master; + } + + public bool is_reimport_editable() { + return reimport_editable; + } + + public File? get_editable_file() { + return editable_file; + } + + public FileInfo? get_editable_file_info() { + return editable_file_info; + } + + public Gee.Collection<File> get_raw_developer_files() { + return developer_files; + } + + public override bool is_in_alteration() { + return base.is_in_alteration() || editable_in_alteration; + } + + public bool is_revert_to_master() { + return revert_to_master; + } + + public virtual void set_editable_file(File? file) { + // if reverting, don't bother + if (file != null && revert_to_master) + return; + + editable_file = file; + } + + public virtual void set_editable_file_info(FileInfo? info) { + // if reverting, don't bother + if (info != null && revert_to_master) + return; + + editable_file_info = info; + if (info == null) + editable_file_info_altered = false; + } + + public virtual void set_editable_file_info_altered(bool altered) { + // if reverting, don't bother + if (altered && revert_to_master) + return; + + editable_file_info_altered = altered; + } + + public virtual void set_editable_in_alteration(bool in_alteration) { + editable_in_alteration = in_alteration; + } + + public virtual void set_raw_development_in_alteration(bool in_alteration) { + raw_development_in_alteration = in_alteration; + } + + public virtual void set_raw_developer_file_info_altered(bool altered) { + raw_developer_file_info_altered = altered; + } + + public virtual void set_revert_to_master(bool revert) { + if (revert) { + // this means nothing any longer + reimport_editable = false; + editable_file = null; + editable_file_info = null; + } + + revert_to_master = revert; + } + + public virtual void add_raw_developer_file(File file) { + developer_files.add(file); + } + + public virtual void clear_raw_developer_files() { + developer_files.clear(); + } + + public virtual void set_reimport_master(bool reimport) { + reimport_master = reimport; + + if (reimport) + mark_online(); + } + + public virtual void set_reimport_editable(bool reimport) { + // if reverting or going offline, don't bother + if (reimport && (revert_to_master || is_set_offline())) + return; + + reimport_editable = reimport; + } + + public virtual void set_reimport_raw_developments(bool reimport) { + reimport_raw_developments = reimport; + + if (reimport) + mark_online(); + } + + public override bool is_all_updated() { + return base.is_all_updated() + && reimport_master == false + && reimport_editable == false + && editable_file == null + && editable_file_info_altered == false + && editable_file_info == null + && editable_in_alteration == false + && developer_files.size == 0 + && raw_developer_file_info_altered == false + && revert_to_master == false; + } +} + +private class PhotoMonitor : MediaMonitor { + private const int MAX_REIMPORT_JOBS_PER_CYCLE = 20; + private const int MAX_REVERTS_PER_CYCLE = 5; + + private class ReimportMasterJob : BackgroundJob { + public LibraryPhoto photo; + public Photo.ReimportMasterState reimport_state = null; + public bool mark_online = false; + public Error err = null; + + public ReimportMasterJob(PhotoMonitor owner, LibraryPhoto photo) { + base (owner, owner.on_master_reimported, new Cancellable(), + owner.on_master_reimport_cancelled); + + this.photo = photo; + } + + public override void execute() { + try { + mark_online = photo.prepare_for_reimport_master(out reimport_state); + } catch (Error err) { + this.err = err; + } + } + } + + private class ReimportEditableJob : BackgroundJob { + public LibraryPhoto photo; + public Photo.ReimportEditableState state = null; + public bool success = false; + public Error err = null; + + public ReimportEditableJob(PhotoMonitor owner, LibraryPhoto photo) { + base (owner, owner.on_editable_reimported, new Cancellable(), + owner.on_editable_reimport_cancelled); + + this.photo = photo; + } + + public override void execute() { + try { + success = photo.prepare_for_reimport_editable(out state); + } catch (Error err) { + this.err = err; + } + } + } + + private class ReimportRawDevelopmentJob : BackgroundJob { + public LibraryPhoto photo; + public Photo.ReimportRawDevelopmentState state = null; + public bool success = false; + public Error err = null; + + public ReimportRawDevelopmentJob(PhotoMonitor owner, LibraryPhoto photo) { + base (owner, owner.on_raw_development_reimported, new Cancellable(), + owner.on_raw_development_reimport_cancelled); + + this.photo = photo; + } + + public override void execute() { + try { + success = photo.prepare_for_reimport_raw_development(out state); + } catch (Error err) { + this.err = err; + } + } + } + + private Workers workers; + private Gee.ArrayList<LibraryPhoto> matched_editables = new Gee.ArrayList<LibraryPhoto>(); + private Gee.ArrayList<LibraryPhoto> matched_developments = new Gee.ArrayList<LibraryPhoto>(); + private Gee.HashMap<LibraryPhoto, ReimportMasterJob> master_reimport_pending = new Gee.HashMap< + LibraryPhoto, ReimportMasterJob>(); + private Gee.HashMap<LibraryPhoto, ReimportEditableJob> editable_reimport_pending = + new Gee.HashMap<LibraryPhoto, ReimportEditableJob>(); + private Gee.HashMap<LibraryPhoto, ReimportRawDevelopmentJob> raw_developments_reimport_pending = + new Gee.HashMap<LibraryPhoto, ReimportRawDevelopmentJob>(); + + public PhotoMonitor(Workers workers, Cancellable cancellable) { + base (LibraryPhoto.global, cancellable); + + this.workers = workers; + } + + protected override MonitorableUpdates create_updates(Monitorable monitorable) { + assert(monitorable is LibraryPhoto); + + return new PhotoUpdates((LibraryPhoto) monitorable); + } + + public override MediaSourceCollection get_media_source_collection() { + return LibraryPhoto.global; + } + + public override bool is_file_represented(File file) { + LibraryPhotoSourceCollection.State state; + return get_photo_state_by_file(file, out state) != null; + } + + public override void close() { + foreach (ReimportMasterJob job in master_reimport_pending.values) + job.cancel(); + + foreach (ReimportEditableJob job in editable_reimport_pending.values) + job.cancel(); + + foreach (ReimportRawDevelopmentJob job in raw_developments_reimport_pending.values) + job.cancel(); + + base.close(); + } + + private void cancel_reimports(LibraryPhoto photo) { + ReimportMasterJob? master_job = master_reimport_pending.get(photo); + if (master_job != null) + master_job.cancel(); + + ReimportEditableJob? editable_job = editable_reimport_pending.get(photo); + if (editable_job != null) + editable_job.cancel(); + } + + public override MediaMonitor.DiscoveredFile notify_file_discovered(File file, FileInfo info, + out Monitorable monitorable) { + LibraryPhotoSourceCollection.State state; + LibraryPhoto? photo = get_photo_state_by_file(file, out state); + if (photo == null) { + monitorable = null; + + return MediaMonitor.DiscoveredFile.UNKNOWN; + } + + switch (state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.OFFLINE: + monitorable = photo; + + return MediaMonitor.DiscoveredFile.REPRESENTED; + + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.EDITABLE: + case LibraryPhotoSourceCollection.State.DEVELOPER: + default: + // ignored ... trash always stays in trash, offline or not, and editables are + // simply attached to online/offline photos + monitorable = null; + + return MediaMonitor.DiscoveredFile.IGNORE; + } + } + + public override Gee.Collection<Monitorable>? candidates_for_unknown_file(File file, FileInfo info, + out MediaMonitor.DiscoveredFile result) { + // reset with each call + matched_editables.clear(); + matched_developments.clear(); + + Gee.Collection<LibraryPhoto> matched_masters = new Gee.ArrayList<LibraryPhoto>(); + LibraryPhoto.global.fetch_by_matching_backing(info, matched_masters, matched_editables, + matched_developments); + if (matched_masters.size > 0) { + result = MediaMonitor.DiscoveredFile.UNKNOWN; + + return matched_masters; + } + + if (matched_editables.size == 0 && matched_developments.size == 0) { + result = MediaMonitor.DiscoveredFile.UNKNOWN; + + return null; + } + + // for editable files and raw developments, trust file characteristics alone + if (matched_editables.size > 0) { + LibraryPhoto match = matched_editables[0]; + if (matched_editables.size > 1) { + warning("Unknown file %s could be matched with %d photos; giving to %s, dropping others", + file.get_path(), matched_editables.size, match.to_string()); + for (int ctr = 1; ctr < matched_editables.size; ctr++) { + if (!matched_editables[ctr].does_editable_exist()) + matched_editables[ctr].revert_to_master(); + } + } + + update_editable_file(match, file); + } + + if (matched_developments.size > 0) { + LibraryPhoto match_raw = matched_developments[0]; + if (matched_developments.size > 1) { + warning("Unknown file %s could be matched with %d photos; giving to %s, dropping others", + file.get_path(), matched_developments.size, match_raw.to_string()); + } + + update_raw_development_file(match_raw, file); + } + + result = MediaMonitor.DiscoveredFile.IGNORE; + + return null; + } + + public override File[]? get_auxilliary_backing_files(Monitorable monitorable) { + LibraryPhoto photo = (LibraryPhoto) monitorable; + File[] files = new File[0]; + + // Editable. + if (photo.has_editable()) + files += photo.get_editable_file(); + + // Raw developments. + Gee.Collection<File>? raw_files = photo.get_raw_developer_files(); + if (raw_files != null) + foreach (File f in raw_files) + files += f; + + // Return null if no files. + return files.length > 0 ? files : null; + } + + public override void update_backing_file_info(Monitorable monitorable, File file, FileInfo? info) { + LibraryPhoto photo = (LibraryPhoto) monitorable; + + if (get_master_file(photo).equal(file)) + check_for_master_changes(photo, info); + else if (get_editable_file(photo) != null && get_editable_file(photo).equal(file)) + check_for_editable_changes(photo, info); + else if (get_raw_development_files(photo) != null) { + foreach (File f in get_raw_development_files(photo)) { + if (f.equal(file)) + check_for_raw_development_changes(photo, info); + } + } + } + + public override void notify_discovery_completing() { + matched_editables.clear(); + } + + // If filesize has changed, treat that as a full-blown modification + // and reimport ... this is problematic if only the metadata has changed, but so be it. + // + // TODO: We could do an MD5 check for more accuracy. + private void check_for_master_changes(LibraryPhoto photo, FileInfo? info) { + // if not present, offline state is already taken care of by LibraryMonitor + if (info == null) + return; + + BackingPhotoRow state = photo.get_master_photo_row(); + if (state.matches_file_info(info)) + return; + + if (state.is_touched(info)) { + update_master_file_info_altered(photo); + update_master_file_alterations_completed(photo, info); + } else { + update_reimport_master(photo); + } + } + + private void check_for_editable_changes(LibraryPhoto photo, FileInfo? info) { + if (info == null) { + update_revert_to_master(photo); + + return; + } + + // If state matches, done -- editables have no bearing on a photo's offline status. + BackingPhotoRow? state = photo.get_editable_photo_row(); + if (state == null || state.matches_file_info(info)) + return; + + if (state.is_touched(info)) { + update_editable_file_info_altered(photo); + update_editable_file_alterations_completed(photo, info); + } else { + update_reimport_editable(photo); + } + } + + private void check_for_raw_development_changes(LibraryPhoto photo, FileInfo? info) { + if (info == null) { + // Switch back to default for safety. + photo.set_raw_developer(RawDeveloper.SHOTWELL); + + return; + } + + Gee.Collection<BackingPhotoRow>? rows = photo.get_raw_development_photo_rows(); + if (rows == null) + return; + + // Look through all possible rows, if we find a file with a matching name or info, + // assume we found our man. + foreach (BackingPhotoRow row in rows) { + if (row.matches_file_info(info)) + return; + if (info.get_name() == row.filepath) { + if (row.is_touched(info)) { + update_raw_development_file_info_altered(photo); + update_raw_development_file_alterations_completed(photo); + } else { + update_reimport_raw_developments(photo); + } + + break; + } + } + } + + public override bool notify_file_created(File file, FileInfo info) { + LibraryPhotoSourceCollection.State state; + LibraryPhoto? photo = get_photo_state_by_file(file, out state); + if (photo == null) + return false; + + switch (state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.EDITABLE: + case LibraryPhotoSourceCollection.State.DEVELOPER: + // do nothing, although this is unexpected + warning("File %s created in %s state", file.get_path(), state.to_string()); + break; + + case LibraryPhotoSourceCollection.State.OFFLINE: + mdbg("Will mark %s online".printf(photo.to_string())); + update_online(photo); + break; + + default: + error("Unknown LibraryPhoto collection state %s", state.to_string()); + } + + return true; + } + + public override bool notify_file_moved(File old_file, File new_file, FileInfo info) { + LibraryPhotoSourceCollection.State old_state; + LibraryPhoto? old_photo = get_photo_state_by_file(old_file, out old_state); + + LibraryPhotoSourceCollection.State new_state; + LibraryPhoto? new_photo = get_photo_state_by_file(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_photo != null && new_photo == null) { + // 1. + switch (old_state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.OFFLINE: + mdbg("Will set new master file for %s to %s".printf(old_photo.to_string(), + new_file.get_path())); + update_master_file(old_photo, new_file); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will set new editable file for %s to %s".printf(old_photo.to_string(), + new_file.get_path())); + update_editable_file(old_photo, new_file); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + mdbg("Will set new raw development file for %s to %s".printf(old_photo.to_string(), + new_file.get_path())); + update_raw_development_file(old_photo, new_file); + break; + + default: + error("Unknown LibraryPhoto collection state %s", old_state.to_string()); + } + } else if (old_photo == null && new_photo != null) { + // 2. + switch (new_state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.OFFLINE: + mdbg("Will reimport master file for %s".printf(new_photo.to_string())); + update_reimport_master(new_photo); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will reimport editable file for %s".printf(new_photo.to_string())); + update_reimport_editable(new_photo); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + mdbg("Will reimport raw development file for %s".printf(new_photo.to_string())); + update_reimport_raw_developments(new_photo); + break; + + default: + error("Unknown LibraryPhoto collection state %s", new_state.to_string()); + } + } else if (old_photo == null && new_photo == null) { + // 3. + return false; + } else { + assert(old_photo != null && new_photo != null); + // 4. + switch (old_state) { + case LibraryPhotoSourceCollection.State.ONLINE: + mdbg("Will mark offline %s".printf(old_photo.to_string())); + update_offline(old_photo); + break; + + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.OFFLINE: + // do nothing + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will revert %s to master".printf(old_photo.to_string())); + update_revert_to_master(old_photo); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + // do nothing + break; + + default: + error("Unknown LibraryPhoto collection state %s", old_state.to_string()); + } + + switch (new_state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.OFFLINE: + mdbg("Will reimport master file for %s".printf(new_photo.to_string())); + update_reimport_master(new_photo); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will reimport editable file for %s".printf(new_photo.to_string())); + update_reimport_editable(new_photo); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + mdbg("Will reimport raw development file for %s".printf(new_photo.to_string())); + update_reimport_raw_developments(new_photo); + break; + + default: + error("Unknown LibraryPhoto collection state %s", new_state.to_string()); + } + } + + return true; + } + + public override bool notify_file_altered(File file) { + LibraryPhotoSourceCollection.State state; + LibraryPhoto? photo = get_photo_state_by_file(file, out state); + if (photo == null) + return false; + + switch (state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.OFFLINE: + case LibraryPhotoSourceCollection.State.TRASH: + mdbg("Will reimport master for %s".printf(photo.to_string())); + update_reimport_master(photo); + update_master_file_in_alteration(photo, true); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will reimport editable for %s".printf(photo.to_string())); + update_reimport_editable(photo); + update_editable_file_in_alteration(photo, true); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + mdbg("Will reimport raw development for %s".printf(photo.to_string())); + update_reimport_raw_developments(photo); + update_raw_development_file_in_alteration(photo, true); + break; + + default: + error("Unknown LibraryPhoto collection state %s", state.to_string()); + } + + return true; + } + + public override bool notify_file_attributes_altered(File file) { + LibraryPhotoSourceCollection.State state; + LibraryPhoto? photo = get_photo_state_by_file(file, out state); + if (photo == null) + return false; + + switch (state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.TRASH: + mdbg("Will update master file info for %s".printf(photo.to_string())); + update_master_file_info_altered(photo); + update_master_file_in_alteration(photo, true); + break; + + case LibraryPhotoSourceCollection.State.OFFLINE: + // do nothing, but unexpected + warning("File %s attributes altered in %s state", file.get_path(), + state.to_string()); + update_master_file_in_alteration(photo, true); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will update editable file info for %s".printf(photo.to_string())); + update_editable_file_info_altered(photo); + update_editable_file_in_alteration(photo, true); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + mdbg("Will update raw development file info for %s".printf(photo.to_string())); + update_raw_development_file_info_altered(photo); + update_raw_development_file_in_alteration(photo, true); + break; + + default: + error("Unknown LibraryPhoto collection state %s", state.to_string()); + } + + return true; + } + + public override bool notify_file_alteration_completed(File file, FileInfo info) { + LibraryPhotoSourceCollection.State state; + LibraryPhoto? photo = get_photo_state_by_file(file, out state); + if (photo == null) + return false; + + switch (state) { + case LibraryPhotoSourceCollection.State.ONLINE: + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.OFFLINE: + update_master_file_alterations_completed(photo, info); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + update_editable_file_alterations_completed(photo, info); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + update_raw_development_file_alterations_completed(photo); + break; + + default: + error("Unknown LibraryPhoto collection state %s", state.to_string()); + } + + return true; + } + + public override bool notify_file_deleted(File file) { + LibraryPhotoSourceCollection.State state; + LibraryPhoto? photo = get_photo_state_by_file(file, out state); + if (photo == null) + return false; + + switch (state) { + case LibraryPhotoSourceCollection.State.ONLINE: + mdbg("Will mark %s offline".printf(photo.to_string())); + update_offline(photo); + update_master_file_in_alteration(photo, false); + break; + + case LibraryPhotoSourceCollection.State.TRASH: + case LibraryPhotoSourceCollection.State.OFFLINE: + // do nothing / already knew this + update_master_file_in_alteration(photo, false); + break; + + case LibraryPhotoSourceCollection.State.EDITABLE: + mdbg("Will revert %s to master".printf(photo.to_string())); + update_revert_to_master(photo); + update_editable_file_in_alteration(photo, false); + break; + + case LibraryPhotoSourceCollection.State.DEVELOPER: + mdbg("Will revert %s to master".printf(photo.to_string())); + update_revert_to_master(photo); + update_editable_file_in_alteration(photo, false); + update_raw_development_file_in_alteration(photo, false); + break; + + default: + error("Unknown LibraryPhoto collection state %s", state.to_string()); + } + + return true; + } + + protected override void on_media_source_destroyed(DataSource source) { + base.on_media_source_destroyed(source); + + cancel_reimports((LibraryPhoto) source); + } + + private LibraryPhoto? get_photo_state_by_file(File file, out LibraryPhotoSourceCollection.State state) { + File? real_file = null; + if (has_pending_updates()) { + foreach (Monitorable monitorable in get_monitorables()) { + LibraryPhoto photo = (LibraryPhoto) monitorable; + + PhotoUpdates? updates = get_existing_photo_updates(photo); + if (updates == null) + continue; + + if (updates.get_master_file() != null && updates.get_master_file().equal(file)) { + real_file = photo.get_master_file(); + + break; + } + + if (updates.get_editable_file() != null && updates.get_editable_file().equal(file)) { + real_file = photo.get_editable_file(); + + // if the photo's "real" editable file is null, then this file hasn't been + // associated with it (yet) so fake the call + if (real_file == null) { + state = LibraryPhotoSourceCollection.State.EDITABLE; + + return photo; + } + + break; + } + + if (updates.get_raw_developer_files() != null) { + bool found = false; + foreach (File raw in updates.get_raw_developer_files()) { + if (raw.equal(file)) { + found = true; + + break; + } + } + + if (found) { + Gee.Collection<File>? developed = photo.get_raw_developer_files(); + if (developed != null) { + foreach (File f in developed) { + if (f.equal(file)) { + real_file = f; + state = LibraryPhotoSourceCollection.State.DEVELOPER; + + break; + } + } + + } + + break; + } + } + } + } + + return LibraryPhoto.global.get_state_by_file(real_file ?? file, out state); + } + + public PhotoUpdates fetch_photo_updates(LibraryPhoto photo) { + return (PhotoUpdates) fetch_updates(photo); + } + + public PhotoUpdates? get_existing_photo_updates(LibraryPhoto photo) { + return get_existing_updates(photo) as PhotoUpdates; + } + + public void update_reimport_master(LibraryPhoto photo) { + fetch_photo_updates(photo).set_reimport_master(true); + + // cancel outstanding reimport + if (master_reimport_pending.has_key(photo)) + master_reimport_pending.get(photo).cancel(); + } + + public void update_reimport_editable(LibraryPhoto photo) { + fetch_photo_updates(photo).set_reimport_editable(true); + + // cancel outstanding reimport + if (editable_reimport_pending.has_key(photo)) + editable_reimport_pending.get(photo).cancel(); + } + + public void update_reimport_raw_developments(LibraryPhoto photo) { + fetch_photo_updates(photo).set_reimport_raw_developments(true); + + // cancel outstanding reimport + if (raw_developments_reimport_pending.has_key(photo)) + raw_developments_reimport_pending.get(photo).cancel(); + } + + public File? get_editable_file(LibraryPhoto photo) { + PhotoUpdates? updates = get_existing_photo_updates(photo); + + return (updates != null && updates.get_editable_file() != null) ? updates.get_editable_file() + : photo.get_editable_file(); + } + + public Gee.Collection<File>? get_raw_development_files(LibraryPhoto photo) { + PhotoUpdates? updates = get_existing_photo_updates(photo); + + return (updates != null && updates.get_raw_developer_files() != null) ? + updates.get_raw_developer_files() : photo.get_raw_developer_files(); + } + + public void update_editable_file(LibraryPhoto photo, File file) { + fetch_photo_updates(photo).set_editable_file(file); + } + + public void update_editable_file_info_altered(LibraryPhoto photo) { + fetch_photo_updates(photo).set_editable_file_info_altered(true); + } + + public void update_raw_development_file(LibraryPhoto photo, File file) { + fetch_photo_updates(photo).add_raw_developer_file(file); + } + + public void update_raw_development_file_info_altered(LibraryPhoto photo) { + fetch_photo_updates(photo).set_raw_developer_file_info_altered(true); + } + + public void update_editable_file_in_alteration(LibraryPhoto photo, bool in_alteration) { + fetch_photo_updates(photo).set_editable_in_alteration(in_alteration); + } + + public void update_editable_file_alterations_completed(LibraryPhoto photo, FileInfo info) { + fetch_photo_updates(photo).set_editable_file_info(info); + fetch_photo_updates(photo).set_editable_in_alteration(false); + } + + public void update_raw_development_file_in_alteration(LibraryPhoto photo, bool in_alteration) { + fetch_photo_updates(photo).set_raw_development_in_alteration(in_alteration); + } + + public void update_raw_development_file_alterations_completed(LibraryPhoto photo) { + fetch_photo_updates(photo).set_raw_development_in_alteration(false); + } + + public void update_revert_to_master(LibraryPhoto photo) { + fetch_photo_updates(photo).set_revert_to_master(true); + } + + 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.Map<LibraryPhoto, File> set_editable_file = null; + Gee.Map<LibraryPhoto, FileInfo> set_editable_file_info = null; + Gee.Map<LibraryPhoto, Gee.Collection<File>> set_raw_developer_files = null; + Gee.ArrayList<LibraryPhoto> revert_to_master = null; + Gee.ArrayList<LibraryPhoto> reimport_master = null; + Gee.ArrayList<LibraryPhoto> reimport_editable = null; + Gee.ArrayList<LibraryPhoto> reimport_raw_developments = null; + int reimport_job_count = 0; + + foreach (MonitorableUpdates monitorable_updates in all_updates) { + if (op_count >= MAX_OPERATIONS_PER_CYCLE) + break; + + PhotoUpdates? updates = monitorable_updates as PhotoUpdates; + if (updates == null) + continue; + + if (updates.get_editable_file() != null) { + if (set_editable_file == null) + set_editable_file = new Gee.HashMap<LibraryPhoto, File>(); + + set_editable_file.set(updates.photo, updates.get_editable_file()); + updates.set_editable_file(null); + op_count++; + } + + if (updates.get_editable_file_info() != null) { + if (set_editable_file_info == null) + set_editable_file_info = new Gee.HashMap<LibraryPhoto, FileInfo>(); + + set_editable_file_info.set(updates.photo, updates.get_editable_file_info()); + updates.set_editable_file_info(null); + op_count++; + } + + if (updates.get_raw_developer_files() != null) { + if (set_raw_developer_files == null) + set_raw_developer_files = new Gee.HashMap<LibraryPhoto, Gee.Collection<File>>(); + + set_raw_developer_files.set(updates.photo, updates.get_raw_developer_files()); + updates.clear_raw_developer_files(); + op_count++; + } + + if (updates.is_revert_to_master()) { + if (revert_to_master == null) + revert_to_master = new Gee.ArrayList<LibraryPhoto>(); + + if (revert_to_master.size < MAX_REVERTS_PER_CYCLE) { + revert_to_master.add(updates.photo); + updates.set_revert_to_master(false); + } + op_count++; + } + + if (updates.is_reimport_master() && reimport_job_count < MAX_REIMPORT_JOBS_PER_CYCLE) { + if (reimport_master == null) + reimport_master = new Gee.ArrayList<LibraryPhoto>(); + + reimport_master.add(updates.photo); + updates.set_reimport_master(false); + reimport_job_count++; + op_count++; + } + + if (updates.is_reimport_editable() && reimport_job_count < MAX_REIMPORT_JOBS_PER_CYCLE) { + if (reimport_editable == null) + reimport_editable = new Gee.ArrayList<LibraryPhoto>(); + + reimport_editable.add(updates.photo); + updates.set_reimport_editable(false); + reimport_job_count++; + op_count++; + } + } + + if (set_editable_file != null) { + mdbg("Changing editable file of %d photos".printf(set_editable_file.size)); + + try { + Photo.set_many_editable_file(set_editable_file); + } catch (DatabaseError err) { + AppWindow.database_error(err); + } + } + + if (set_editable_file_info != null) { + mdbg("Updating %d editable files timestamps".printf(set_editable_file_info.size)); + + try { + Photo.update_many_editable_timestamps(set_editable_file_info); + } catch (DatabaseError err) { + AppWindow.database_error(err); + } + } + + if (revert_to_master != null) { + mdbg("Reverting %d photos to master".printf(revert_to_master.size)); + + foreach (LibraryPhoto photo in revert_to_master) + photo.revert_to_master(); + } + + // + // Now that the metadata has been updated, deal with imports and reimports + // + + if (reimport_master != null) { + mdbg("Reimporting %d masters".printf(reimport_master.size)); + + foreach (LibraryPhoto photo in reimport_master) { + assert(!master_reimport_pending.has_key(photo)); + + ReimportMasterJob job = new ReimportMasterJob(this, photo); + master_reimport_pending.set(photo, job); + workers.enqueue(job); + } + } + + if (reimport_editable != null) { + mdbg("Reimporting %d editables".printf(reimport_editable.size)); + + foreach (LibraryPhoto photo in reimport_editable) { + assert(!editable_reimport_pending.has_key(photo)); + + ReimportEditableJob job = new ReimportEditableJob(this, photo); + editable_reimport_pending.set(photo, job); + workers.enqueue(job); + } + } + + if (reimport_raw_developments != null) { + mdbg("Reimporting %d raw developments".printf(reimport_raw_developments.size)); + + foreach (LibraryPhoto photo in reimport_raw_developments) { + assert(!raw_developments_reimport_pending.has_key(photo)); + + ReimportRawDevelopmentJob job = new ReimportRawDevelopmentJob(this, photo); + raw_developments_reimport_pending.set(photo, job); + workers.enqueue(job); + } + } + } + + private void on_master_reimported(BackgroundJob j) { + ReimportMasterJob job = (ReimportMasterJob) j; + + // no longer pending + bool removed = master_reimport_pending.unset(job.photo); + assert(removed); + + if (job.err != null) { + critical("Unable to reimport %s due to master file changing: %s", job.photo.to_string(), + job.err.message); + + update_offline(job.photo); + + return; + } + + if (!job.mark_online) { + // the prepare_for_reimport_master failed, photo is now considered offline + update_offline(job.photo); + + return; + } + + try { + job.photo.finish_reimport_master(job.reimport_state); + } catch (DatabaseError err) { + AppWindow.database_error(err); + } + + // now considered online + if (job.photo.is_offline()) + update_online(job.photo); + + mdbg("Reimported master for %s".printf(job.photo.to_string())); + } + + private void on_master_reimport_cancelled(BackgroundJob j) { + bool removed = master_reimport_pending.unset(((ReimportMasterJob) j).photo); + assert(removed); + } + + private void on_editable_reimported(BackgroundJob j) { + ReimportEditableJob job = (ReimportEditableJob) j; + + // no longer pending + bool removed = editable_reimport_pending.unset(job.photo); + assert(removed); + + if (job.err != null) { + critical("Unable to reimport editable %s: %s", job.photo.to_string(), job.err.message); + + return; + } + + try { + job.photo.finish_reimport_editable(job.state); + } catch (DatabaseError err) { + AppWindow.database_error(err); + } + + mdbg("Reimported editable for %s".printf(job.photo.to_string())); + } + + private void on_editable_reimport_cancelled(BackgroundJob j) { + bool removed = editable_reimport_pending.unset(((ReimportEditableJob) j).photo); + assert(removed); + } + + private void on_raw_development_reimported(BackgroundJob j) { + ReimportRawDevelopmentJob job = (ReimportRawDevelopmentJob) j; + + // no longer pending + bool removed = raw_developments_reimport_pending.unset(job.photo); + assert(removed); + + if (job.err != null) { + critical("Unable to reimport raw development %s: %s", job.photo.to_string(), job.err.message); + + return; + } + + try { + job.photo.finish_reimport_raw_development(job.state); + } catch (DatabaseError err) { + AppWindow.database_error(err); + } + + mdbg("Reimported raw development for %s".printf(job.photo.to_string())); + } + + private void on_raw_development_reimport_cancelled(BackgroundJob j) { + bool removed = raw_developments_reimport_pending.unset(((ReimportRawDevelopmentJob) j).photo); + assert(removed); + } +} + |