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