summaryrefslogtreecommitdiff
path: root/src/direct/DirectPhoto.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/direct/DirectPhoto.vala')
-rw-r--r--src/direct/DirectPhoto.vala316
1 files changed, 316 insertions, 0 deletions
diff --git a/src/direct/DirectPhoto.vala b/src/direct/DirectPhoto.vala
new file mode 100644
index 0000000..98886ba
--- /dev/null
+++ b/src/direct/DirectPhoto.vala
@@ -0,0 +1,316 @@
+/* Copyright 2009-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.
+ */
+
+public class DirectPhoto : Photo {
+ private const int PREVIEW_BEST_FIT = 360;
+
+ public static DirectPhotoSourceCollection global = null;
+
+ public signal void can_rotate_changed(bool b);
+
+ private Gdk.Pixbuf preview = null;
+ private bool loaded = false;
+
+ private DirectPhoto(PhotoRow row) {
+ base (row);
+ }
+
+ /**
+ * @brief Because all transformations are discarded on reimport by design, including
+ * Orientation, a JFIF file that is only rotated or flipped, then saved, has the orientation
+ * change the user made before saving removed (recall that fetch() remembers which images it
+ * has seen before and will only add a file to the file map once; every time it sees it
+ * again after this is considered a reimport). This will set the orientation to the
+ * specified value, fixing up both the row and the backing row.
+ *
+ * @warning Only reimported JFIF files should need this; non-lossy image types have their
+ * actual pixels physically rotated in the file when they're exported.
+ *
+ * @param dest The orientation to set the photo to; usually, this should be a value
+ * obtained by calling get_orientation() prior to export()ing a DirectPhoto.
+ */
+ public void fixup_orientation_after_reimport(Orientation dest) {
+ row.orientation = dest;
+ backing_photo_row.original_orientation = dest;
+ }
+
+ public static void init(File initial_file) {
+ global = new DirectPhotoSourceCollection(initial_file);
+ DirectPhoto photo;
+ string? reason = global.fetch(initial_file, out photo, false);
+ if (reason != null)
+ warning("fetch error: %s", reason);
+ global.add(photo);
+ }
+
+ public static void terminate() {
+ }
+
+ // Gets the dimensions of this photo's pixbuf when scaled to original
+ // size and saves them where get_raw_dimensions can find them.
+ private void save_dims() {
+ try {
+ backing_photo_row.dim = Dimensions.for_pixbuf(get_pixbuf_with_options(Scaling.for_original(),
+ Exception.CROP | Exception.STRAIGHTEN | Exception.ORIENTATION));
+ } catch (Error e) {
+ warning("Dimensions for image %s could not be gotten.", to_string());
+ }
+ }
+
+ // Loads a photo on demand.
+ public ImportResult demand_load() {
+ if (loaded) {
+ save_dims();
+ return ImportResult.SUCCESS;
+ }
+
+ Photo.ReimportMasterState reimport_state;
+ try {
+ prepare_for_reimport_master(out reimport_state);
+ finish_reimport_master(reimport_state);
+ } catch (Error err) {
+ warning("Database error on re-importing image: %s", err.message);
+ return ImportResult.DATABASE_ERROR;
+ }
+
+ loaded = true;
+ save_dims();
+ return ImportResult.SUCCESS;
+ }
+
+ // This method should only be called by DirectPhotoSourceCollection. Use
+ // DirectPhoto.global.fetch to import files into the system.
+ public static ImportResult internal_import(File file, out DirectPhoto photo) {
+ PhotoImportParams params = new PhotoImportParams.create_placeholder(file, ImportID.generate());
+ Photo.create_pre_import(params);
+ PhotoTable.get_instance().add(params.row);
+
+ photo = new DirectPhoto(params.row);
+
+ return ImportResult.SUCCESS;
+ }
+
+ public override Gdk.Pixbuf get_preview_pixbuf(Scaling scaling) throws Error {
+ if (preview == null) {
+ preview = get_thumbnail(PREVIEW_BEST_FIT);
+
+ if (preview == null)
+ preview = get_pixbuf(scaling);
+ }
+
+ return scaling.perform_on_pixbuf(preview, Gdk.InterpType.BILINEAR, true);
+ }
+
+ public override void rotate(Rotation rotation) {
+ can_rotate_now = false;
+ can_rotate_changed(false);
+ base.rotate(rotation);
+ }
+
+ public override Gdk.Pixbuf get_pixbuf(Scaling scaling) throws Error {
+ Gdk.Pixbuf ret = base.get_pixbuf(scaling);
+ can_rotate_changed(true);
+ can_rotate_now = true;
+ return ret;
+ }
+
+ public override Gdk.Pixbuf? get_thumbnail(int scale) throws Error {
+ return (get_metadata().get_preview_count() == 0) ? null :
+ get_orientation().rotate_pixbuf(get_metadata().get_preview(0).get_pixbuf());
+ }
+
+ protected override void notify_altered(Alteration alteration) {
+ preview = null;
+
+ base.notify_altered(alteration);
+ }
+
+ protected override bool has_user_generated_metadata() {
+ // TODO: implement this method
+ return false;
+ }
+
+ protected override void set_user_metadata_for_export(PhotoMetadata metadata) {
+ // TODO: implement this method, see ticket
+ }
+
+ protected override void apply_user_metadata_for_reimport(PhotoMetadata metadata) {
+ }
+
+ public override bool is_trashed() {
+ // always returns false -- direct-edit mode has no concept of the trash can
+ return false;
+ }
+
+ public override bool is_offline() {
+ // always returns false -- direct-edit mode has no concept of offline photos
+ return false;
+ }
+
+ public override void trash() {
+ // a no-op -- direct-edit mode has no concept of the trash can
+ }
+
+ public override void untrash() {
+ // a no-op -- direct-edit mode has no concept of the trash can
+ }
+
+ public override void mark_offline() {
+ // a no-op -- direct-edit mode has no concept of offline photos
+ }
+
+ public override void mark_online() {
+ // a no-op -- direct-edit mode has no concept of offline photos
+ }
+}
+
+public class DirectPhotoSourceCollection : DatabaseSourceCollection {
+ private const int DISCOVERED_FILES_BATCH_ADD = 500;
+ private Gee.Collection<DirectPhoto> prepared_photos = new Gee.ArrayList<DirectPhoto>();
+ private Gee.HashMap<File, DirectPhoto> file_map = new Gee.HashMap<File, DirectPhoto>(file_hash,
+ file_equal);
+ private DirectoryMonitor monitor;
+
+ public DirectPhotoSourceCollection(File initial_file) {
+ base("DirectPhotoSourceCollection", get_direct_key);
+
+ // only use the monitor for discovery in the specified directory, not its children
+ monitor = new DirectoryMonitor(initial_file.get_parent(), false, false);
+ monitor.file_discovered.connect(on_file_discovered);
+ monitor.discovery_completed.connect(on_discovery_completed);
+
+ monitor.start_discovery();
+ }
+
+ public override bool holds_type_of_source(DataSource source) {
+ return source is DirectPhoto;
+ }
+
+ private static int64 get_direct_key(DataSource source) {
+ DirectPhoto photo = (DirectPhoto) source;
+ PhotoID photo_id = photo.get_photo_id();
+
+ return photo_id.id;
+ }
+
+ public override void notify_items_added(Gee.Iterable<DataObject> added) {
+ foreach (DataObject object in added) {
+ DirectPhoto photo = (DirectPhoto) object;
+ File file = photo.get_file();
+
+ assert(!file_map.has_key(file));
+
+ file_map.set(file, photo);
+ }
+
+ base.notify_items_added(added);
+ }
+
+ public override void notify_items_removed(Gee.Iterable<DataObject> removed) {
+ foreach (DataObject object in removed) {
+ DirectPhoto photo = (DirectPhoto) object;
+ File file = photo.get_file();
+
+ bool is_removed = file_map.unset(file);
+ assert(is_removed);
+ }
+
+ base.notify_items_removed(removed);
+ }
+
+ public bool has_source_for_file(File file) {
+ return file_map.has_key(file);
+ }
+
+ private void on_file_discovered(File file, FileInfo info) {
+ // skip already-seen files
+ if (has_source_for_file(file))
+ return;
+
+ // only add files that look like photo files we support
+ if (!PhotoFileFormat.is_file_supported(file))
+ return;
+
+ DirectPhoto photo;
+ string? reason = fetch(file, out photo, false);
+ if (reason != null)
+ warning("Error fetching file: %s", reason);
+ prepared_photos.add(photo);
+ if (prepared_photos.size >= DISCOVERED_FILES_BATCH_ADD)
+ flush_prepared_photos();
+ }
+
+ private void on_discovery_completed() {
+ flush_prepared_photos();
+ }
+
+ private void flush_prepared_photos() {
+ add_many(prepared_photos);
+ prepared_photos.clear();
+ }
+
+ public bool has_file(File file) {
+ return file_map.has_key(file);
+ }
+
+ public void reimport_photo(DirectPhoto photo) {
+ photo.discard_prefetched(true);
+ DirectPhoto reimported_photo;
+ fetch(photo.get_file(), out reimported_photo, true);
+ }
+
+ // Returns an error string if unable to fetch, null otherwise
+ public string? fetch(File file, out DirectPhoto photo, bool reimport) {
+ // fetch from the map first, which ensures that only one DirectPhoto exists for each file
+ photo = file_map.get(file);
+ if (photo != null) {
+ string? reason = null;
+
+ if (reimport) {
+ try {
+ Orientation ori_tmp = Orientation.TOP_LEFT;
+ bool should_restore_ori = false;
+
+ if ((photo.only_metadata_changed()) ||
+ (photo.get_file_format() == PhotoFileFormat.JFIF)) {
+ ori_tmp = photo.get_orientation();
+ should_restore_ori = true;
+ }
+
+ Photo.ReimportMasterState reimport_state;
+ if (photo.prepare_for_reimport_master(out reimport_state)) {
+ photo.finish_reimport_master(reimport_state);
+ if (should_restore_ori) {
+ photo.fixup_orientation_after_reimport(ori_tmp);
+ }
+ }
+ else {
+ reason = ImportResult.FILE_ERROR.to_string();
+ }
+ } catch (Error err) {
+ reason = err.message;
+ }
+ }
+
+ return reason;
+ }
+
+ // for DirectPhoto, a fetch on an unknown file is an implicit import into the in-memory
+ // database (which automatically adds the new DirectPhoto object to DirectPhoto.global)
+ ImportResult result = DirectPhoto.internal_import(file, out photo);
+
+ return (result == ImportResult.SUCCESS) ? null : result.to_string();
+ }
+
+ public bool has_file_source(File file) {
+ return file_map.has_key(file);
+ }
+
+ public DirectPhoto? get_file_source(File file) {
+ return file_map.get(file);
+ }
+}
+