diff options
Diffstat (limited to 'src')
42 files changed, 504 insertions, 207 deletions
diff --git a/src/AppDirs.vala b/src/AppDirs.vala index 05e172c..fd357c2 100644 --- a/src/AppDirs.vala +++ b/src/AppDirs.vala @@ -193,12 +193,39 @@ class AppDirs { public static File get_exec_dir() { return exec_dir; } + + public enum Runtime { + NATIVE, + FLATPAK, + SNAP, + UNKNOWN + } + + private static Runtime _runtime = Runtime.UNKNOWN; + + public static Runtime get_runtime() { + if (_runtime == Runtime.UNKNOWN) { + var snap = Environment.get_variable("SNAP_NAME"); + if (snap != null) { + _runtime = Runtime.SNAP; + } + else { + var flatpak_canary = File.new_for_path("/.flatpak-info"); + if (flatpak_canary.query_exists()) { + _runtime = Runtime.FLATPAK; + } else { + _runtime = Runtime.NATIVE; + } + } + } + + return _runtime; + } public static File get_temp_dir() { if (tmp_dir == null) { var basedir = Environment.get_tmp_dir(); - var flatpak_canary = File.new_for_path("/.flatpak-info"); - if (flatpak_canary.query_exists() && basedir == "/tmp") { + if (get_runtime() == Runtime.FLATPAK && basedir == "/tmp") { basedir = Environment.get_user_cache_dir() + "/tmp"; } diff --git a/src/AppWindow.vala b/src/AppWindow.vala index 7398c74..ae8bfb9 100644 --- a/src/AppWindow.vala +++ b/src/AppWindow.vala @@ -586,6 +586,21 @@ public abstract class AppWindow : PageWindow { if (Resources.GIT_VERSION != null && Resources.GIT_VERSION != "" && Resources.GIT_VERSION != Resources.APP_VERSION) { hash = " (%s)".printf(Resources.GIT_VERSION.substring(0,7)); } + var runtime = AppDirs.get_runtime(); + switch (runtime) { + case AppDirs.Runtime.SNAP: + hash += " (Snap)"; + break; + case AppDirs.Runtime.FLATPAK: + hash += " (Flatpak)"; + break; + case AppDirs.Runtime.NATIVE: + hash += " (Native)"; + break; + default: + hash += " (Unknown)"; + break; + } string[] artists = {"Image of the Delmenhorst Town Hall by Charlie1965nrw, source: https://commons.wikimedia.org/wiki/File:Delmenhorst_Rathaus.jpg", null}; Gtk.show_about_dialog(this, "version", Resources.APP_VERSION + hash + " — Delmenhorst", diff --git a/src/Application.vala b/src/Application.vala index 59bae36..2225f7c 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -4,8 +4,77 @@ * (version 2.1 or later). See the COPYING file in this distribution. */ +[DBus(name = "org.gnome.Shotwell.Authenticate")] +public interface AuthenticationReceiver : Object { + public abstract void callback(string url) throws DBusError, IOError; +} + +[DBus(name = "org.gnome.Shotwell.Authenticate")] +internal class AuthenticatorReceiverApp : Gtk.Application, AuthenticationReceiver { + private Gee.HashMap<string, Spit.Publishing.AuthenticatedCallback> + pending_auth_requests = new Gee.HashMap<string, Spit.Publishing.AuthenticatedCallback>(); + + public AuthenticatorReceiverApp() { + Object(application_id: "org.gnome.Shotwell", flags: GLib.ApplicationFlags.HANDLES_OPEN | + GLib.ApplicationFlags.HANDLES_COMMAND_LINE); + } + public override bool dbus_register(DBusConnection connection, string object_path) throws Error { + try { + connection.register_object(object_path, this); + } catch (IOError e) { + warning("Failed to register authentication helper on session connection: %s", e.message); + } + return true; + } + + + internal void register_auth_callback(string cookie, Spit.Publishing.AuthenticatedCallback cb) { + pending_auth_requests[cookie] = cb; + } + + internal void unregister_auth_callback(string cookie) { + pending_auth_requests.unset(cookie); + } + + public void callback(string callback_url) throws DBusError, IOError { + try { + var uri = Uri.parse(callback_url, UriFlags.NONE); + debug("Got authentication callback uri: %s", callback_url); + // See if something is waiting for a pending authentication + var query = uri.get_query(); + if (query == null || query == "") { + debug("Callback does not have parameters. Not accepting"); + + return; + } + var uri_params = Uri.parse_params(uri.get_query()); + if ("sw_auth_cookie" in uri_params) { + var cookie = uri_params["sw_auth_cookie"]; + if (pending_auth_requests.has_key(cookie)) { + pending_auth_requests[cookie].authenticated(uri_params); + LibraryWindow.get_app().present(); + } else { + debug("No call-back registered for cookie %s, probably user cancelled", cookie); + } + } else if (uri.get_scheme().has_prefix("com.googleusercontent.apps")) { + if (pending_auth_requests.has_key(uri.get_scheme())) { + pending_auth_requests[uri.get_scheme()].authenticated(uri_params); + } else { + debug("No call-back registered for cookie %s, probably user cancelled", uri.get_scheme()); + } + } + } catch (Error error) { + warning("Got invalid authentication call-back: %s", callback_url); + } + } +} + public class Application { + public interface AuthCallback : Object { + public abstract void authenticated(HashTable<string, string> params); + } private static Application instance = null; + public static TimeZone timezone = null; private Gtk.Application system_app = null; private int system_app_run_retval = 0; private bool direct; @@ -36,20 +105,21 @@ public class Application { private bool running = false; private bool exiting_fired = false; + Gee.HashMap<string, AuthCallback> pending_auth_requests = new Gee.HashMap<string, AuthCallback>(); + private Application(bool is_direct) { if (is_direct) { // we allow multiple instances of ourself in direct mode, so DON'T // attempt to be unique. We don't request any command-line handling // here because this is processed elsewhere, and we don't need to handle // command lines from remote instances, since we don't care about them. - system_app = new Gtk.Application("org.gnome.Shotwell-direct", GLib.ApplicationFlags.HANDLES_OPEN | + system_app = new Gtk.Application("org.gnome.Shotwell-Viewer", GLib.ApplicationFlags.HANDLES_OPEN | GLib.ApplicationFlags.NON_UNIQUE); } else { // we've been invoked in library mode; set up for uniqueness and handling // of incoming command lines from remote instances (needed for getting // storage device and camera mounts). - system_app = new Gtk.Application("org.gnome.Shotwell", GLib.ApplicationFlags.HANDLES_OPEN | - GLib.ApplicationFlags.HANDLES_COMMAND_LINE); + system_app = new AuthenticatorReceiverApp(); } // GLib will assert if we don't do this... @@ -63,12 +133,46 @@ public class Application { if (!direct) { system_app.command_line.connect(on_command_line); + var action = new SimpleAction("authenticated", VariantType.STRING); + system_app.add_action(action); + action.activate.connect((a, p) => { + try { + var uri = Uri.parse(p.get_string(), UriFlags.NONE); + debug("Got authentication callback uri: %s", p.get_string()); + // See if something is waiting for a pending authentication + var uri_params = Uri.parse_params(uri.get_query()); + if ("sw_auth_cookie" in uri_params) { + var cookie = uri_params["sw_auth_cookie"]; + if (pending_auth_requests.has_key(cookie)) { + pending_auth_requests[cookie].authenticated(uri_params); + } else { + debug("No call-back registered for cookie %s, probably user cancelled", cookie); + } + } + } catch (Error error) { + warning("Got invalid authentication call-back: %s", p.get_string()); + } + }); } system_app.activate.connect(on_activated); system_app.startup.connect(on_activated); } + public static void register_auth_callback(string cookie, Spit.Publishing.AuthenticatedCallback cb) { + var instance = get_instance(); + if (!instance.direct) { + ((AuthenticatorReceiverApp)instance.system_app).register_auth_callback(cookie, cb); + } + } + + public static void unregister_auth_callback(string cookie) { + var instance = get_instance(); + if (!instance.direct) { + ((AuthenticatorReceiverApp)instance.system_app).unregister_auth_callback(cookie); + } + } + public static double get_scale() { var instance = get_instance().system_app; unowned GLib.List<Gtk.Window> windows = instance.get_windows(); diff --git a/src/BatchImport.vala b/src/BatchImport.vala index ae4f573..9b3e1e6 100644 --- a/src/BatchImport.vala +++ b/src/BatchImport.vala @@ -1851,7 +1851,7 @@ private class PrepareFilesJob : BackgroundImportJob { warning("Unable to perform MD5 checksum on file %s: %s", file.get_path(), err.message); - return ImportResult.convert_error(err, ImportResult.FILE_ERROR); + return ImportResult.FILE_ERROR; } // we only care about file extensions and metadata if we're importing a photo -- diff --git a/src/CheckerboardLayout.vala b/src/CheckerboardLayout.vala index 85232f3..a22412d 100644 --- a/src/CheckerboardLayout.vala +++ b/src/CheckerboardLayout.vala @@ -1070,10 +1070,10 @@ public class CheckerboardLayout : Gtk.DrawingArea { ctx.save(); ctx.add_class("view"); var val = ctx.get_property("border-color", Gtk.StateFlags.NORMAL); - focus_color = *(Gdk.RGBA*)val.get_boxed(); + border_color = *(Gdk.RGBA*)val.get_boxed(); val = ctx.get_property("border-color", Gtk.StateFlags.FOCUSED); - border_color = *(Gdk.RGBA*)val.get_boxed(); + focus_color = *(Gdk.RGBA*)val.get_boxed(); // Checked in GtkIconView - The selection is drawn using render_background val = ctx.get_property("background-color", Gtk.StateFlags.FOCUSED | Gtk.StateFlags.SELECTED); diff --git a/src/Commands.vala b/src/Commands.vala index 76aecb4..25bdbc2 100644 --- a/src/Commands.vala +++ b/src/Commands.vala @@ -321,7 +321,7 @@ public abstract class MultipleDataSourceCommand : PageCommand { private void on_source_destroyed(DataSource source) { // as with SingleDataSourceCommand, too risky to selectively remove commands from the stack, // although this could be reconsidered in the future - if (source_list.contains(source)) + if (source_list.contains(source) && get_command_manager() != null) get_command_manager().reset(); } diff --git a/src/Debug.vala b/src/Debug.vala index 799a94f..186a175 100644 --- a/src/Debug.vala +++ b/src/Debug.vala @@ -107,7 +107,7 @@ namespace Debug { stream.printf("%s %d %s [%s] %s\n", log_app_version_prefix, Posix.getpid(), - new DateTime.now_local().format("%F %T"), + new DateTime.now(Application.timezone).format("%F %T"), prefix, message ); diff --git a/src/DesktopIntegration.vala b/src/DesktopIntegration.vala index 754d9a1..d29e0f7 100644 --- a/src/DesktopIntegration.vala +++ b/src/DesktopIntegration.vala @@ -105,13 +105,13 @@ public async void files_send_to(File[] files) { } AppWindow.get_instance().set_busy_cursor(); - try{ - var portal = new Xdp.Portal(); + try { + var portal = new Xdp.Portal.initable_new(); // Use empty list for addresses instead of null to word around bug in xdg-desktop-portal-gtk yield portal.compose_email(parent, {null}, null, null, _("Send files per Mail: ") + file_names.str, null, file_paths, Xdp.EmailFlags.NONE, null); - } catch (Error e){ + } catch (Error e) { // Translators: The first %s is the name of the file, the second %s is the reason why it could not be sent AppWindow.error_message(_("Unable to send file %s, %s").printf( file_names.str, e.message)); @@ -175,12 +175,16 @@ public void set_background(Photo photo, bool desktop, bool screensaver) { } var parent = Xdp.parent_new_gtk(AppWindow.get_instance()); - var portal = new Xdp.Portal(); Xdp.WallpaperFlags flags = Xdp.WallpaperFlags.PREVIEW; if (desktop) flags |= Xdp.WallpaperFlags.BACKGROUND; if (screensaver) flags |= Xdp.WallpaperFlags.LOCKSCREEN; - portal.set_wallpaper.begin(parent, save_as.get_uri(), flags, null); + try { + var portal = new Xdp.Portal.initable_new(); + portal.set_wallpaper.begin(parent, save_as.get_uri(), flags, null); + } catch (Error err) { + AppWindow.error_message(_("Unable to set background: %s").printf(err.message)); + } GLib.FileUtils.chmod(save_as.get_parse_name(), 0644); } @@ -313,12 +317,16 @@ private void on_desktop_slideshow_exported(Exporter exporter, bool is_cancelled) } var parent = Xdp.parent_new_gtk(AppWindow.get_instance()); - var portal = new Xdp.Portal(); Xdp.WallpaperFlags flags = Xdp.WallpaperFlags.PREVIEW; if (set_desktop_background) flags |= Xdp.WallpaperFlags.BACKGROUND; if (set_screensaver) flags |= Xdp.WallpaperFlags.LOCKSCREEN; - portal.set_wallpaper.begin(parent, xml_file.get_uri(), flags, null); + try { + var portal = new Xdp.Portal.initable_new(); + portal.set_wallpaper.begin(parent, xml_file.get_uri(), flags, null); + } catch (Error err) { + AppWindow.error_message(_("Unable to set background: %s").printf(err.message)); + } } } diff --git a/src/Event.vala b/src/Event.vala index 69d27d0..4375aad 100644 --- a/src/Event.vala +++ b/src/Event.vala @@ -601,10 +601,11 @@ public class Event : EventSource, ContainerSource, Proxyable, Indexable { // media sources are stored in ViewCollection from earliest to latest MediaSource earliest_media = (MediaSource) ((DataView) view.get_at(0)).get_source(); - var earliest_tm = earliest_media.get_exposure_time().to_local(); + var earliest_tm = earliest_media.get_exposure_time().to_timezone(Application.timezone); // use earliest to generate the boundary hour for that day - var start_boundary = new DateTime.local(earliest_tm.get_year(), + var start_boundary = new DateTime(Application.timezone, + earliest_tm.get_year(), earliest_tm.get_month(), earliest_tm.get_day_of_month(), EVENT_BOUNDARY_HOUR, diff --git a/src/Photo.vala b/src/Photo.vala index f636a32..2b90361 100644 --- a/src/Photo.vala +++ b/src/Photo.vala @@ -405,9 +405,7 @@ public abstract class Photo : PhotoSource, Dateable, Positionable { readers.master = row.master.file_format.create_reader(row.master.filepath); // get the file title of the Photo without using a File object, skipping the separator itself - string? basename = String.sliced_at_last_char(row.master.filepath, Path.DIR_SEPARATOR); - if (basename != null) - file_title = String.sliced_at(basename, 1); + file_title = Path.get_basename(row.master.filepath); if (is_string_empty(file_title)) file_title = row.master.filepath; diff --git a/src/PhotoPage.vala b/src/PhotoPage.vala index 5e94c24..3ab0f6b 100644 --- a/src/PhotoPage.vala +++ b/src/PhotoPage.vala @@ -835,7 +835,9 @@ public abstract class EditingHostPage : SinglePhotoPage { photo_changing(photo); DataView view = get_view().get_view_for_source(photo); - assert(view != null); + if (view == null) { + return; + } // Select photo. get_view().unselect_all(); @@ -966,7 +968,7 @@ public abstract class EditingHostPage : SinglePhotoPage { return photo.has_transformations() || photo.has_editable(); } - private void on_pixbuf_fetched(Photo photo, owned Gdk.Pixbuf? pixbuf, Error? err) { + private void on_pixbuf_fetched(Photo photo, Gdk.Pixbuf? pixbuf, Error? err) { // if not of the current photo, nothing more to do if (!photo.equals(get_photo())) return; @@ -987,6 +989,7 @@ public abstract class EditingHostPage : SinglePhotoPage { if (tool_pixbuf != null) { pixbuf = tool_pixbuf; + pixbuf.ref(); max_dim = tool_pixbuf_dim; } } catch(Error err) { @@ -1254,6 +1257,10 @@ public abstract class EditingHostPage : SinglePhotoPage { } private void quick_update_pixbuf() { + if (get_photo() == null) { + return; + } + Gdk.Pixbuf? pixbuf = cache.get_ready_pixbuf(get_photo()); if (pixbuf != null) { set_pixbuf(pixbuf, get_photo().get_dimensions()); diff --git a/src/PixbufCache.vala b/src/PixbufCache.vala index cee33c6..76fdbd3 100644 --- a/src/PixbufCache.vala +++ b/src/PixbufCache.vala @@ -80,7 +80,7 @@ public class PixbufCache : Object { private Gee.ArrayList<Photo> lru = new Gee.ArrayList<Photo>(); private Gee.HashMap<Photo, FetchJob> in_progress = new Gee.HashMap<Photo, FetchJob>(); - public signal void fetched(Photo photo, owned Gdk.Pixbuf? pixbuf, Error? err); + public signal void fetched(Photo photo, Gdk.Pixbuf? pixbuf, Error? err); public PixbufCache(SourceCollection sources, PhotoType type, Scaling scaling, int max_count, CacheFilter? filter = null) { @@ -120,7 +120,11 @@ public class PixbufCache : Object { } // This call never blocks. Returns null if the pixbuf is not present. - public Gdk.Pixbuf? get_ready_pixbuf(Photo photo) { + public Gdk.Pixbuf? get_ready_pixbuf(Photo? photo) { + if (photo == null) { + return null; + } + return get_cached(photo); } diff --git a/src/Properties.vala b/src/Properties.vala index 7c6ab89..8eb5742 100644 --- a/src/Properties.vala +++ b/src/Properties.vala @@ -97,7 +97,7 @@ private abstract class Properties : Gtk.Box { protected string get_prettyprint_date(DateTime date) { string date_string = null; - var today = new DateTime.now_local(); + var today = new DateTime.now(Application.timezone); if (date.get_day_of_year() == today.get_day_of_year() && date.get_year() == today.get_year()) { date_string = _("Today"); } else if (date.get_day_of_year() == (today.get_day_of_year() - 1) && date.get_year() == today.get_year()) { diff --git a/src/Resources.vala b/src/Resources.vala index d03a214..0bd8512 100644 --- a/src/Resources.vala +++ b/src/Resources.vala @@ -853,6 +853,7 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc., /// Locale-specific starting date format for multi-date strings, /// i.e. the "Tue Mar 08" in "Tue Mar 08 - 10, 2006" /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format + /// xgettext:no-c-format START_MULTIDAY_DATE_FORMAT_STRING = C_("MultidayFormat", "%a %b %d"); /// Locale-specific ending date format for multi-date strings, @@ -863,6 +864,7 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc., /// Locale-specific calendar date format for multi-month strings, /// i.e. the "Tue Mar 08" in "Tue Mar 08 to Mon Apr 06, 2006" /// See http://developer.gnome.org/glib/2.32/glib-GDateTime.html#g-date-time-format + /// xgettext:no-c-format START_MULTIMONTH_DATE_FORMAT_STRING = C_("MultimonthFormat", "%a %b %d"); /// Locale-specific calendar date format for multi-month strings, diff --git a/src/TimedQueue.vala b/src/TimedQueue.vala index 4ea6a23..ac1aab6 100644 --- a/src/TimedQueue.vala +++ b/src/TimedQueue.vala @@ -18,7 +18,7 @@ public delegate void DequeuedCallback<G>(G item); -public class TimedQueue<G> { +public class HashTimedQueue<G> { private class Element<G> { public G item; public ulong ready; @@ -42,6 +42,7 @@ public class TimedQueue<G> { private uint dequeue_spacing_msec = 0; private ulong last_dequeue = 0; private bool paused_state = false; + private Gee.HashMap<G, int> item_count; public virtual signal void paused(bool is_paused) { } @@ -49,7 +50,8 @@ public class TimedQueue<G> { // Initial design was to have a signal that passed the dequeued G, but bug in valac meant // finding a workaround, namely using a delegate: // https://bugzilla.gnome.org/show_bug.cgi?id=628639 - public TimedQueue(uint hold_msec, DequeuedCallback<G> callback, + public HashTimedQueue(uint hold_msec, DequeuedCallback<G> callback, + owned Gee.HashDataFunc<G>? hash_func = null, owned Gee.EqualDataFunc<G>? equal_func = null, int priority = Priority.DEFAULT) { this.hold_msec = hold_msec; this.callback = callback; @@ -64,9 +66,10 @@ public class TimedQueue<G> { queue = new SortedList<Element<G>>(Element.comparator); timer_id = Timeout.add(get_heartbeat_timeout(), on_heartbeat, priority); + item_count = new Gee.HashMap<G, int>((owned) hash_func, (owned) equal_func); } - ~TimedQueue() { + ~HashTimedQueue() { if (timer_id != 0) Source.remove(timer_id); } @@ -93,10 +96,6 @@ public class TimedQueue<G> { : (dequeue_spacing_msec / 2)).clamp(10, uint.MAX); } - protected virtual void notify_dequeued(G item) { - callback(item); - } - public bool is_paused() { return paused_state; } @@ -119,50 +118,80 @@ public class TimedQueue<G> { paused(false); } - public virtual void clear() { - queue.clear(); + public void clear() { + lock(queue) { + item_count.clear(); + queue.clear(); + } } - public virtual bool contains(G item) { - foreach (Element<G> e in queue) { - if (equal_func(item, e.item)) - return true; + public bool contains(G item) { + lock(queue) { + return item_count.has_key(item); } - - return false; } - public virtual bool enqueue(G item) { - return queue.add(new Element<G>(item, calc_ready_time())); + public bool enqueue(G item) { + lock(queue) { + if (!queue.add(new Element<G>(item, calc_ready_time()))) { + return false; + } + item_count.set(item, item_count.has_key(item) ? item_count.get(item) + 1 : 1); + + return true; + } } - public virtual bool enqueue_many(Gee.Collection<G> items) { + public bool enqueue_many(Gee.Collection<G> items) { ulong ready_time = calc_ready_time(); Gee.ArrayList<Element<G>> elements = new Gee.ArrayList<Element<G>>(); foreach (G item in items) elements.add(new Element<G>(item, ready_time)); - return queue.add_list(elements); + lock(queue) { + if (!queue.add_list(elements)) { + return false; + } + + foreach (G item in items) { + item_count.set(item, item_count.has_key(item) ? item_count.get(item) + 1 : 1); + } + } + + return true; + } - public virtual bool remove_first(G item) { - Gee.Iterator<Element<G>> iter = queue.iterator(); - while (iter.next()) { - Element<G> e = iter.get(); - if (equal_func(item, e.item)) { - iter.remove(); - - return true; + public bool remove_first(G item) { + lock(queue) { + var item_removed = false; + var iter = queue.iterator(); + while (iter.next()) { + Element<G> e = iter.get(); + if (equal_func(item, e.item)) { + iter.remove(); + + item_removed = true; + break; + } + } + + if (!item_removed) { + return false; } + + removed(item); } - - return false; + + return true; } - public virtual int size { + public int size { get { - return queue.size; + lock(queue) { + return queue.size; + } } } @@ -180,23 +209,28 @@ public class TimedQueue<G> { if (queue.size == 0) break; - Element<G>? head = queue.get_at(0); - assert(head != null); - - if (now == 0) - now = now_ms(); - - if (head.ready > now) - break; - - // if a space of time is required between dequeues, check now - if ((dequeue_spacing_msec != 0) && ((now - last_dequeue) < dequeue_spacing_msec)) - break; - - Element<G>? h = queue.remove_at(0); - assert(head == h); - - notify_dequeued(head.item); + G? item = null; + lock(queue) { + Element<G>? head = queue.get_at(0); + assert(head != null); + + if (now == 0) + now = now_ms(); + + if (head.ready > now) + break; + + // if a space of time is required between dequeues, check now + if ((dequeue_spacing_msec != 0) && ((now - last_dequeue) < dequeue_spacing_msec)) + break; + + Element<G>? h = queue.remove_at(0); + assert(head == h); + + removed(head.item); + item = head.item; + } + callback(item); last_dequeue = now; // if a dequeue spacing is in place, it's a lock that only one item is dequeued per @@ -207,65 +241,8 @@ public class TimedQueue<G> { return true; } -} - -// HashTimedQueue uses a HashMap for quick lookups of elements via contains(). -public class HashTimedQueue<G> : TimedQueue<G> { - private Gee.HashMap<G, int> item_count; - - public HashTimedQueue(uint hold_msec, DequeuedCallback<G> callback, - owned Gee.HashDataFunc<G>? hash_func = null, owned Gee.EqualDataFunc<G>? equal_func = null, - int priority = Priority.DEFAULT) { - base (hold_msec, callback, (owned) equal_func, priority); - - item_count = new Gee.HashMap<G, int>((owned) hash_func, (owned) equal_func); - } - - protected override void notify_dequeued(G item) { - removed(item); - - base.notify_dequeued(item); - } - - public override void clear() { - item_count.clear(); - - base.clear(); - } - - public override bool contains(G item) { - return item_count.has_key(item); - } - - public override bool enqueue(G item) { - if (!base.enqueue(item)) - return false; - - item_count.set(item, item_count.has_key(item) ? item_count.get(item) + 1 : 1); - - return true; - } - - public override bool enqueue_many(Gee.Collection<G> items) { - if (!base.enqueue_many(items)) - return false; - - foreach (G item in items) - item_count.set(item, item_count.has_key(item) ? item_count.get(item) + 1 : 1); - - return true; - } - - public override bool remove_first(G item) { - if (!base.remove_first(item)) - return false; - - removed(item); - - return true; - } - + // Not locking. This is always called with the lock hold private void removed(G item) { // item in question is either already removed // or was never added, safe to do nothing here diff --git a/src/authenticator.vala b/src/authenticator.vala new file mode 100644 index 0000000..55f3321 --- /dev/null +++ b/src/authenticator.vala @@ -0,0 +1,40 @@ +// SPDX-License-Identifer: LGPL-2.1-or-later +// SPDX-FileCopyrightText: 2022 Jens Georg <mail@jensge.org> + +[DBus(name = "org.gnome.Shotwell.Authenticate")] +public interface AuthenticationReceiver : Object { + public abstract void callback(string url) throws DBusError, IOError; +} + +static int main(string[] args) { + AuthenticationReceiver receiver; + + if (args.length != 2) { + print("Usage: %s <callback-uri>\n", args[0]); + return 1; + } + + try { + var uri = Uri.parse(args[1], UriFlags.NONE); + var scheme = uri.get_scheme(); + + if (scheme != "shotwell-oauth2" && !scheme.has_prefix("com.googleusercontent.apps")) { + critical("Invalid scheme in callback URI \"%s\"", args[1]); + return 1; + } + } catch (Error e) { + critical("Invalid uri: \"%s\": %s", args[1], e.message); + return 1; + } + + try { + receiver = Bus.get_proxy_sync (BusType.SESSION, "org.gnome.Shotwell", "/org/gnome/Shotwell"); + receiver.callback(args[1]); + } catch (Error e) { + critical("Could not connect to remote shotwell instance: %s", e.message); + + return 1; + } + + return 0; +}
\ No newline at end of file diff --git a/src/camera/ImportPage.vala b/src/camera/ImportPage.vala index 463317b..20a6a58 100644 --- a/src/camera/ImportPage.vala +++ b/src/camera/ImportPage.vala @@ -1086,7 +1086,7 @@ public class ImportPage : CheckerboardPage { progress_bar.set_text(""); progress_bar.visible = false; - try_refreshing_camera(true); + Timeout.add_seconds(3, () => { try_refreshing_camera(true); return false; }); } private void clear_all_import_sources() { diff --git a/src/db/DatabaseTable.vala b/src/db/DatabaseTable.vala index dea797a..be45e5e 100644 --- a/src/db/DatabaseTable.vala +++ b/src/db/DatabaseTable.vala @@ -29,10 +29,61 @@ public abstract class DatabaseTable { public string table_name = null; + static Gee.HashMap<string, Regex> regex_map; + + private static void regexp_replace(Sqlite.Context context, Sqlite.Value[] args) { + var pattern = args[0].to_text(); + if (pattern == null) { + context.result_error("Missing regular expression", Sqlite.ERROR); + return; + } + + var text = args[1].to_text(); + if (text == null) { + return; + } + + var replacement = args[2].to_text(); + if (replacement == null) { + context.result_value(args[1]); + return; + } + + Regex re; + if (regex_map == null) { + regex_map = new Gee.HashMap<string, Regex>(); + } + if (regex_map.has_key(pattern)) { + re = regex_map[pattern]; + } else { + try { + re = new Regex(pattern, RegexCompileFlags.DEFAULT, RegexMatchFlags.DEFAULT); + regex_map[pattern] = re; + } catch (Error err) { + context.result_error("Invalid pattern: %s".printf(err.message), Sqlite.ERROR); + return; + } + } + + try { + var result = re.replace(text, -1, 0, replacement, RegexMatchFlags.DEFAULT); + context.result_text(result); + } catch (Error err) { + context.result_error("Replacement failed: %s".printf(err.message), Sqlite.ERROR); + } + } + + [CCode (cname="SQLITE_DETERMINISTIC", cheader_filename="sqlite3.h")] + extern static int SQLITE_DETERMINISTIC; + private static void prepare_db(string filename) { // Open DB. int res = Sqlite.Database.open_v2(filename, out db, Sqlite.OPEN_READWRITE | Sqlite.OPEN_CREATE, null); + + db.create_function("regexp_replace", 3, Sqlite.UTF8 | SQLITE_DETERMINISTIC, null, + DatabaseTable.regexp_replace, null, null); + if (res != Sqlite.OK) AppWindow.panic(_("Unable to open/create photo database %s: error code %d").printf(filename, res)); diff --git a/src/db/Db.vala b/src/db/Db.vala index 5072967..7f76f2d 100644 --- a/src/db/Db.vala +++ b/src/db/Db.vala @@ -55,6 +55,10 @@ public VerifyResult verify_database(out string app_version, out int schema_versi if (result != VerifyResult.OK) return result; } + + PhotoTable.clean_comments(); + VideoTable.clean_comments(); + return VerifyResult.OK; } diff --git a/src/db/PhotoTable.vala b/src/db/PhotoTable.vala index 420b209..d74cbd1 100644 --- a/src/db/PhotoTable.vala +++ b/src/db/PhotoTable.vala @@ -1123,6 +1123,13 @@ public class PhotoTable : DatabaseTable { throw_error("PhotoTable.upgrade_for_unset_timestamp", res); } } + + public static void clean_comments() throws DatabaseError { + var result = db.exec("UPDATE PhotoTable SET comment = regexp_replace('^charset=\\w+\\s*', comment, '') WHERE comment like 'charset=%'"); + if (result != Sqlite.OK) { + throw_error("Cleaning comments from charset", result); + } + } } diff --git a/src/db/VideoTable.vala b/src/db/VideoTable.vala index 8af1278..67c50ba 100644 --- a/src/db/VideoTable.vala +++ b/src/db/VideoTable.vala @@ -480,5 +480,12 @@ public class VideoTable : DatabaseTable { } } + public static void clean_comments() throws DatabaseError { + var result = db.exec("UPDATE VideoTable SET comment = regexp_replace('^charset=\\w+\\s*', comment, '') WHERE comment like 'charset=%'"); + if (result != Sqlite.OK) { + throw_error("Cleaning comments from charset", result); + } + } + } diff --git a/src/dialogs/AdjustDateTimeDialog.vala b/src/dialogs/AdjustDateTimeDialog.vala index f475773..2b6ae45 100644 --- a/src/dialogs/AdjustDateTimeDialog.vala +++ b/src/dialogs/AdjustDateTimeDialog.vala @@ -231,7 +231,7 @@ public class AdjustDateTimeDialog : Gtk.Dialog { uint year, month, day; calendar.get_date(out year, out month, out day); - return new DateTime.local((int)year, (int)month + 1, (int)day, hour, (int)minute.get_value(), (int)second.get_value()); + return new DateTime(Application.timezone, (int)year, (int)month + 1, (int)day, hour, (int)minute.get_value(), (int)second.get_value()); } public bool execute(out TimeSpan time_shift, out bool keep_relativity, diff --git a/src/dialogs/ProgressDialog.vala b/src/dialogs/ProgressDialog.vala index 9368764..9d28551 100644 --- a/src/dialogs/ProgressDialog.vala +++ b/src/dialogs/ProgressDialog.vala @@ -37,6 +37,8 @@ public class ProgressDialog : Gtk.Window { cancel_button = new Gtk.Button.with_mnemonic(Resources.CANCEL_LABEL); cancel_button.clicked.connect(on_cancel); delete_event.connect(on_window_closed); + } else { + delete_event.connect(hide_on_delete); } Gtk.Box hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 8); diff --git a/src/direct/DirectWindow.vala b/src/direct/DirectWindow.vala index baf6124..d39e83d 100644 --- a/src/direct/DirectWindow.vala +++ b/src/direct/DirectWindow.vala @@ -40,7 +40,7 @@ public class DirectWindow : AppWindow { } public void update_title(File file, bool modified) { - title = "%s%s (%s) - %s".printf((modified) ? "*" : "", file.get_basename(), + title = "%s%s (%s) - %s".printf((modified) ? "•" : "", file.get_basename(), get_display_pathname(file.get_parent()), Resources.APP_TITLE); } @@ -66,6 +66,9 @@ public class DirectWindow : AppWindow { } protected override void on_quit() { + if (!get_direct_page().check_quit()) + return; + Config.Facade.get_instance().set_direct_window_state(maximized, dimensions); base.on_quit(); diff --git a/src/editing_tools/EditingTools.vala b/src/editing_tools/EditingTools.vala index 3345a3f..594281e 100644 --- a/src/editing_tools/EditingTools.vala +++ b/src/editing_tools/EditingTools.vala @@ -2126,7 +2126,6 @@ public class RedeyeTool : EditingTool { if (coord_in_rectangle((int)Math.lround(x * scale), (int)Math.lround(y * scale), bounds_rect)) { - print("Motion in progress!!\n"); is_reticle_move_in_progress = true; reticle_move_mouse_start_point.x = (int)Math.lround(x * scale); reticle_move_mouse_start_point.y = (int)Math.lround(y * scale); diff --git a/src/events/EventsBranch.vala b/src/events/EventsBranch.vala index 0550eb7..1a3ac69 100644 --- a/src/events/EventsBranch.vala +++ b/src/events/EventsBranch.vala @@ -174,8 +174,10 @@ public class Events.Branch : Sidebar.Branch { private void on_config_changed() { bool value = Config.Facade.get_instance().get_events_sort_ascending(); - sort_ascending = value; - reorder_all(); + if (value != sort_ascending) { + sort_ascending = value; + reorder_all(); + } } private void on_events_added_removed(Gee.Iterable<DataObject>? added, @@ -456,7 +458,7 @@ public class Events.UndatedDirectoryEntry : Events.DirectoryEntry { protected override Page create_page() { return new SubEventsDirectoryPage(SubEventsDirectoryPage.DirectoryType.UNDATED, - new DateTime.now_local()); + new DateTime.now(Application.timezone)); } } diff --git a/src/folders/FoldersBranch.vala b/src/folders/FoldersBranch.vala index 49b2d97..bfa461d 100644 --- a/src/folders/FoldersBranch.vala +++ b/src/folders/FoldersBranch.vala @@ -9,7 +9,7 @@ public class Folders.Branch : Sidebar.Branch { new Gee.HashMap<File, Folders.SidebarEntry>(file_hash, file_equal); private File home_dir; - public class Branch() { + public Branch() { base (new Folders.Root(), Sidebar.Branch.Options.STARTUP_OPEN_GROUPING | Sidebar.Branch.Options.HIDE_IF_EMPTY, diff --git a/src/import-roll/ImportRollBranch.vala b/src/import-roll/ImportRollBranch.vala index 0c582ac..dd3edfa 100644 --- a/src/import-roll/ImportRollBranch.vala +++ b/src/import-roll/ImportRollBranch.vala @@ -1,7 +1,7 @@ public class ImportRoll.Branch : Sidebar.Branch { private Gee.HashMap<int64?, ImportRoll.SidebarEntry> entries; - public class Branch() { + public Branch() { base (new ImportRoll.Root(), Sidebar.Branch.Options.HIDE_IF_EMPTY, ImportRoll.Branch.comparator); diff --git a/src/library/LibraryWindow.vala b/src/library/LibraryWindow.vala index 280a50b..1b3f4b3 100644 --- a/src/library/LibraryWindow.vala +++ b/src/library/LibraryWindow.vala @@ -155,13 +155,14 @@ public class LibraryWindow : AppWindow { sidebar_tree.selected_entry_removed.connect(on_sidebar_selected_entry_removed); sidebar_tree.graft(library_branch, SidebarRootPosition.LIBRARY); - sidebar_tree.graft(tags_branch, SidebarRootPosition.TAGS); - sidebar_tree.graft(folders_branch, SidebarRootPosition.FOLDERS); - sidebar_tree.graft(faces_branch, SidebarRootPosition.FACES); - sidebar_tree.graft(events_branch, SidebarRootPosition.EVENTS); sidebar_tree.graft(camera_branch, SidebarRootPosition.CAMERAS); sidebar_tree.graft(saved_search_branch, SidebarRootPosition.SAVED_SEARCH); + sidebar_tree.graft(events_branch, SidebarRootPosition.EVENTS); sidebar_tree.graft(import_roll_branch, SidebarRootPosition.IMPORT_ROLL); + sidebar_tree.graft(folders_branch, SidebarRootPosition.FOLDERS); + sidebar_tree.graft(faces_branch, SidebarRootPosition.FACES); + sidebar_tree.graft(tags_branch, SidebarRootPosition.TAGS); + sidebar_tree.finish(); properties_scheduler = new OneShotScheduler("LibraryWindow properties", on_update_properties_now); diff --git a/src/main.vala b/src/main.vala index d07b7f5..e619178 100644 --- a/src/main.vala +++ b/src/main.vala @@ -373,7 +373,6 @@ void editing_exec(string filename, bool fullscreen) { DirectWindow direct_window = new DirectWindow(initial_file); direct_window.show_all(); - direct_window.maximize(); debug("%lf seconds to Gtk.main()", startup_timer.elapsed()); @@ -421,6 +420,8 @@ const OptionEntry[] entries = { } void main(string[] args) { + Application.timezone = new TimeZone.local(); + // Call AppDirs init *before* calling Gtk.init_with_args, as it will strip the // exec file from the array AppDirs.init(args[0]); @@ -564,17 +565,20 @@ void main(string[] args) { message("Shotwell %s %s", is_string_empty(filename) ? Resources.APP_LIBRARY_ROLE : Resources.APP_DIRECT_ROLE, Resources.APP_VERSION); - debug ("Shotwell is running in timezone %s", new - DateTime.now_local().get_timezone_abbreviation ()); - + + debug ("Shotwell is running in timezone %s", Application.timezone.get_identifier()); + message ("Shotwell is runing inside %s", AppDirs.get_runtime().to_string()); // Have a filename here? If so, configure ourselves for direct // mode, otherwise, default to library mode. Application.init(!is_string_empty(filename)); // set custom data directory if it's been supplied - if (CommandlineOptions.data_dir != null) + if (CommandlineOptions.data_dir != null) { + if (CommandlineOptions.profile == null) { + AppWindow.error_message("Using the --datadir option without passing --profile and --create is deprecated\n. Plesae migrate to a proper profile instead."); + } AppDirs.set_data_dir(CommandlineOptions.data_dir); - else + } else AppDirs.try_migrate_data(); // Verify the private data directory before continuing @@ -589,6 +593,7 @@ void main(string[] args) { // set up GLib environment GLib.Environment.set_application_name(Resources.APP_TITLE); + GLib.Environment.set_prgname("org.gnome.Shotwell"); // in both the case of running as the library or an editor, Resources is always // initialized diff --git a/src/meson.build b/src/meson.build index 460092e..25f967a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,17 +12,25 @@ sw_graphics_processor = static_library('shotwell-graphics-processor', vala_args : '--disable-assert', install : false) -processor = executable('shotwell-graphics-processor', +executable('shotwell-graphics-processor', ['graphics-processor.vala'], dependencies: [gio, gdk, gee], link_with: sw_graphics_processor) +executable('shotwell-authenticator', + [ + 'authenticator.vala' + ], + dependencies: [gio], + include_directories: config_incdir, + install: true, + install_dir : join_paths(get_option('libexecdir'), 'shotwell') +) + shotwell_deps = [gio, gee, sqlite, gtk, sqlite, posix, gphoto2, gstreamer_pbu, gudev, gexiv2, gmodule, libraw, libexif, sw_plugin] -shotwell_libs = [sw_graphics_processor] - face_sources = (['faces/FacesBranch.vala', 'faces/FacePage.vala', 'faces/FaceShape.vala', diff --git a/src/metadata/MetadataDateTime.vala b/src/metadata/MetadataDateTime.vala index 9dae99b..648d44d 100644 --- a/src/metadata/MetadataDateTime.vala +++ b/src/metadata/MetadataDateTime.vala @@ -6,6 +6,7 @@ public errordomain MetadataDateTimeError { public class MetadataDateTime { private DateTime timestamp; + private static TimeZone local = new TimeZone.local(); public MetadataDateTime(DateTime timestamp) { this.timestamp = timestamp; @@ -63,7 +64,7 @@ public class MetadataDateTime { if (tm.year <= 1900 || tm.month <= 0 || tm.day < 0 || tm.hour < 0 || tm.minute < 0 || tm.second < 0) return false; - timestamp = new DateTime.local(tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second); + timestamp = new DateTime(local, tm.year, tm.month, tm.day, tm.hour, tm.minute, tm.second); return true; } diff --git a/src/photos/PhotoMetadata.vala b/src/photos/PhotoMetadata.vala index 3bf77d6..0624b41 100644 --- a/src/photos/PhotoMetadata.vala +++ b/src/photos/PhotoMetadata.vala @@ -1043,7 +1043,13 @@ public class PhotoMetadata : MediaMetadata { }; public override string? get_comment() { - return get_first_string_interpreted (COMMENT_TAGS); + var comment = get_first_string_interpreted (COMMENT_TAGS); + try { + var re = new Regex("^charset=\\w+\\s*"); + return re.replace(comment, -1, 0, "", RegexMatchFlags.DEFAULT); + } catch (Error err) { + return comment; + } } public void set_comment(string? comment, diff --git a/src/photos/WebPSupport.vala b/src/photos/WebPSupport.vala index 2f4723c..543c889 100644 --- a/src/photos/WebPSupport.vala +++ b/src/photos/WebPSupport.vala @@ -183,7 +183,13 @@ private class WebpSniffer : PhotoFileSniffer { if (calc_md5) detected.md5 = md5_checksum.get_string(); + // We have never reached the header parsing state, but also didn't encounter any error + if (detected.file_format != PhotoFileFormat.WEBP) { + return null; + } + return detected; + } } @@ -203,8 +209,18 @@ private class WebpReader : PhotoFileReader { uint8[] buffer; FileUtils.get_data(this.get_filepath(), out buffer); + var features = WebP.BitstreamFeatures(); + WebP.GetFeatures(buffer, out features); + + if (features.has_animation) { + throw new IOError.INVALID_DATA("Animated WebP files are not yet supported"); + } + int width, height; var pixdata = WebP.DecodeRGBA(buffer, out width, out height); + if (pixdata == null) { + throw new IOError.INVALID_DATA("Failed to decode WebP file"); + } pixdata.length = width * height * 4; return new Gdk.Pixbuf.from_data(pixdata, Gdk.Colorspace.RGB, true, 8, width, height, width * 4); diff --git a/src/plugins/PublishingInterfaces.vala b/src/plugins/PublishingInterfaces.vala index 05b161f..84cb943 100644 --- a/src/plugins/PublishingInterfaces.vala +++ b/src/plugins/PublishingInterfaces.vala @@ -92,6 +92,9 @@ public errordomain PublishingError { SSL_FAILED } +public interface AuthenticatedCallback : Object { + public abstract void authenticated(HashTable<string, string> params); +} /** * Represents a connection to a publishing service. * @@ -503,6 +506,10 @@ public interface PluginHost : GLib.Object, Spit.HostInterface { */ public abstract Spit.Publishing.Publisher.MediaType get_publishable_media_type(); + + public abstract void register_auth_callback(string cookie, AuthenticatedCallback callback); + public abstract void unregister_auth_callback(string cookie); + // // For future expansion. // diff --git a/src/publishing/PublishingPluginHost.vala b/src/publishing/PublishingPluginHost.vala index 7804924..88b99e7 100644 --- a/src/publishing/PublishingPluginHost.vala +++ b/src/publishing/PublishingPluginHost.vala @@ -33,6 +33,14 @@ public class ConcretePublishingHost : Plugins.StandardHostInterface, this.active_publisher = service.create_publisher_with_account(this, account); } + public void register_auth_callback(string cookie, AuthenticatedCallback callback) { + Application.register_auth_callback(cookie, callback); + } + + public void unregister_auth_callback(string cookie) { + Application.unregister_auth_callback(cookie); + } + public string get_current_profile_id() { return Shotwell.ProfileManager.get_instance().id(); } diff --git a/src/searches/SavedSearchDialog.vala b/src/searches/SavedSearchDialog.vala index b08c8a8..b96a324 100644 --- a/src/searches/SavedSearchDialog.vala +++ b/src/searches/SavedSearchDialog.vala @@ -557,11 +557,11 @@ public class SavedSearchDialog : Gtk.Dialog { } private DateTime get_date_one() { - return new DateTime.local(cal_one.year, cal_one.month + 1, cal_one.day, 0, 0, 0.0); + return new DateTime(Application.timezone, cal_one.year, cal_one.month + 1, cal_one.day, 0, 0, 0.0); } private DateTime get_date_two() { - return new DateTime.local(cal_two.year, cal_two.month + 1, cal_two.day, 0, 0, 0.0); + return new DateTime(Application.timezone, cal_two.year, cal_two.month + 1, cal_two.day, 0, 0, 0.0); } private void set_date_one(DateTime date) { diff --git a/src/sidebar/Tree.vala b/src/sidebar/Tree.vala index aae81a0..b6c7f6f 100644 --- a/src/sidebar/Tree.vala +++ b/src/sidebar/Tree.vala @@ -75,6 +75,8 @@ public class Sidebar.Tree : Gtk.TreeView { private bool is_internal_drag_in_progress = false; private Sidebar.Entry? internal_drag_source_entry = null; private Gtk.TreeRowReference? old_path_ref = null; + private Gee.ArrayList<unowned Branch> expand_to_child = new Gee.ArrayList<unowned Branch>(); + private Gee.ArrayList<unowned Branch> expand_to_element = new Gee.ArrayList<unowned Branch>(); public signal void entry_selected(Sidebar.SelectableEntry selectable); @@ -92,8 +94,7 @@ public class Sidebar.Tree : Gtk.TreeView { public Tree(Gtk.TargetEntry[] target_entries, Gdk.DragAction actions, ExternalDropHandler drop_handler) { - set_model(store); - + Gtk.TreeViewColumn text_column = new Gtk.TreeViewColumn(); text_column.set_expand(true); Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf(); @@ -155,6 +156,18 @@ public class Sidebar.Tree : Gtk.TreeView { text_renderer.editing_canceled.disconnect(on_editing_canceled); text_renderer.editing_started.disconnect(on_editing_started); } + + public void finish() { + set_model(store); + foreach (var branch in expand_to_child) { + expand_to_first_child(branch.get_root()); + } + expand_to_child.clear(); + foreach (var branch in expand_to_element) { + expand_to_entry(branch.get_root()); + } + expand_to_element.clear(); + } public void icon_renderer_function(Gtk.CellLayout layout, Gtk.CellRenderer renderer, Gtk.TreeModel model, Gtk.TreeIter iter) { EntryWrapper? wrapper = get_wrapper_at_iter(iter); @@ -399,11 +412,14 @@ public class Sidebar.Tree : Gtk.TreeView { if (branch.get_show_branch()) { associate_branch(branch); - if (branch.is_startup_expand_to_first_child()) - expand_to_first_child(branch.get_root()); + if (branch.is_startup_expand_to_first_child()) { + expand_to_child.add(branch); + + } - if (branch.is_startup_open_grouping()) - expand_to_entry(branch.get_root()); + if (branch.is_startup_open_grouping()) { + expand_to_element.add(branch); + } } branch.entry_added.connect(on_branch_entry_added); @@ -587,9 +603,8 @@ public class Sidebar.Tree : Gtk.TreeView { selected_wrapper = null; Sidebar.Entry entry = wrapper.entry; - entry.pruned(this); - + entry.sidebar_tooltip_changed.disconnect(on_sidebar_tooltip_changed); entry.sidebar_icon_changed.disconnect(on_sidebar_icon_changed); diff --git a/src/threads/Workers.vala b/src/threads/Workers.vala index 60751a9..42d696c 100644 --- a/src/threads/Workers.vala +++ b/src/threads/Workers.vala @@ -18,7 +18,6 @@ public class Workers { private ThreadPool<void *> thread_pool; private AsyncQueue<BackgroundJob> queue = new AsyncQueue<BackgroundJob>(); private EventSemaphore empty_event = new EventSemaphore(); - private int enqueued = 0; public Workers(uint max_threads, bool exclusive) { if (max_threads <= 0 && max_threads != UNLIMITED_THREADS) @@ -51,10 +50,7 @@ public class Workers { public void enqueue(BackgroundJob job) { empty_event.reset(); - lock (queue) { - queue.push_sorted(job, BackgroundJob.priority_compare_func); - enqueued++; - } + queue.push_sorted(job, BackgroundJob.priority_compare_func); try { thread_pool.add(job); @@ -76,21 +72,19 @@ public class Workers { // Returns the number of BackgroundJobs on the queue, not including active jobs. public int get_pending_job_count() { - lock (queue) { - return enqueued; - } + return queue.length(); } private void thread_start(void *ignored) { BackgroundJob? job; bool empty; - lock (queue) { - job = queue.try_pop(); - assert(job != null); + + queue.lock(); + job = queue.try_pop_unlocked(); + assert(job != null); - assert(enqueued > 0); - empty = (--enqueued == 0); - } + empty = queue.length_unlocked() == 0; + queue.unlock(); if (!job.is_cancelled()) job.execute(); diff --git a/src/util/image.vala b/src/util/image.vala index 95ac998..5b78a50 100644 --- a/src/util/image.vala +++ b/src/util/image.vala @@ -343,7 +343,7 @@ private Cairo.Surface get_background_surface() { string color_b; var config = Config.Facade.get_instance(); - var type = "checkered"; //config.get_transparent_background_type(); + var type = config.get_transparent_background_type(); switch (type) { case "checkered": color_a = "#808080"; diff --git a/src/util/string.vala b/src/util/string.vala index 976f8ee..5ca4680 100644 --- a/src/util/string.vala +++ b/src/util/string.vala @@ -177,30 +177,6 @@ public inline bool contains_char(string haystack, unichar needle) { return haystack.index_of_char(needle) >= 0; } -public inline bool contains_str(string haystack, string needle) { - return haystack.index_of(needle) >= 0; -} - -public inline string? sliced_at(string str, int index) { - return (index >= 0) ? str[index:str.length] : null; -} - -public inline string? sliced_at_first_str(string haystack, string needle, int start_index = 0) { - return sliced_at(haystack, haystack.index_of(needle, start_index)); -} - -public inline string? sliced_at_last_str(string haystack, string needle, int start_index = 0) { - return sliced_at(haystack, haystack.last_index_of(needle, start_index)); -} - -public inline string? sliced_at_first_char(string haystack, unichar ch, int start_index = 0) { - return sliced_at(haystack, haystack.index_of_char(ch, start_index)); -} - -public inline string? sliced_at_last_char(string haystack, unichar ch, int start_index = 0) { - return sliced_at(haystack, haystack.last_index_of_char(ch, start_index)); -} - // Note that this method currently turns a word of all zeros into empty space ("000" -> "") public string strip_leading_zeroes(string str) { StringBuilder stripped = new StringBuilder(); diff --git a/src/video-support/AVIMetadataLoader.vala b/src/video-support/AVIMetadataLoader.vala index 2b507e2..31945e9 100644 --- a/src/video-support/AVIMetadataLoader.vala +++ b/src/video-support/AVIMetadataLoader.vala @@ -1,5 +1,7 @@ public class AVIMetadataLoader { + public static TimeZone local = new TimeZone.local(); + private File file = null; // A numerical date string, i.e 2010:01:28 14:54:25 @@ -167,7 +169,7 @@ public class AVIMetadataLoader { out min, out sec, out year)) { return null; // Error } - parsed_date = new DateTime.local(year, month_from_string((string)monthstr), day, hour, min, sec); + parsed_date = new DateTime(AVIMetadataLoader.local, year, month_from_string((string)monthstr), day, hour, min, sec); } return parsed_date; |