summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/AppDirs.vala31
-rw-r--r--src/AppWindow.vala15
-rw-r--r--src/Application.vala110
-rw-r--r--src/BatchImport.vala2
-rw-r--r--src/CheckerboardLayout.vala4
-rw-r--r--src/Commands.vala2
-rw-r--r--src/Debug.vala2
-rw-r--r--src/DesktopIntegration.vala22
-rw-r--r--src/Event.vala5
-rw-r--r--src/Photo.vala4
-rw-r--r--src/PhotoPage.vala11
-rw-r--r--src/PixbufCache.vala8
-rw-r--r--src/Properties.vala2
-rw-r--r--src/Resources.vala2
-rw-r--r--src/TimedQueue.vala189
-rw-r--r--src/authenticator.vala40
-rw-r--r--src/camera/ImportPage.vala2
-rw-r--r--src/db/DatabaseTable.vala51
-rw-r--r--src/db/Db.vala4
-rw-r--r--src/db/PhotoTable.vala7
-rw-r--r--src/db/VideoTable.vala7
-rw-r--r--src/dialogs/AdjustDateTimeDialog.vala2
-rw-r--r--src/dialogs/ProgressDialog.vala2
-rw-r--r--src/direct/DirectWindow.vala5
-rw-r--r--src/editing_tools/EditingTools.vala1
-rw-r--r--src/events/EventsBranch.vala8
-rw-r--r--src/folders/FoldersBranch.vala2
-rw-r--r--src/import-roll/ImportRollBranch.vala2
-rw-r--r--src/library/LibraryWindow.vala9
-rw-r--r--src/main.vala17
-rw-r--r--src/meson.build14
-rw-r--r--src/metadata/MetadataDateTime.vala3
-rw-r--r--src/photos/PhotoMetadata.vala8
-rw-r--r--src/photos/WebPSupport.vala16
-rw-r--r--src/plugins/PublishingInterfaces.vala7
-rw-r--r--src/publishing/PublishingPluginHost.vala8
-rw-r--r--src/searches/SavedSearchDialog.vala4
-rw-r--r--src/sidebar/Tree.vala31
-rw-r--r--src/threads/Workers.vala22
-rw-r--r--src/util/image.vala2
-rw-r--r--src/util/string.vala24
-rw-r--r--src/video-support/AVIMetadataLoader.vala4
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;