summaryrefslogtreecommitdiff
path: root/src/main.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.vala')
-rw-r--r--src/main.vala441
1 files changed, 441 insertions, 0 deletions
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 0000000..8c045fd
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,441 @@
+/* Copyright 2009-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+enum ShotwellCommand {
+ // user-defined commands must be positive ints
+ MOUNTED_CAMERA = 1
+}
+
+private Timer startup_timer = null;
+private bool was_already_running = false;
+
+void library_exec(string[] mounts) {
+ was_already_running = Application.get_is_remote();
+
+ if (was_already_running) {
+ // Send attached cameras out to the primary instance.
+ // The primary instance will get a 'command-line' signal with mounts[]
+ // as an argument, and an 'activate', which will present the window.
+ //
+ // This will also take care of killing us when it sees that another
+ // instance was already registered.
+ Application.present_primary_instance();
+ Application.send_to_primary_instance(mounts);
+ return;
+ }
+
+ // preconfigure units
+ Db.preconfigure(AppDirs.get_data_subdir("data").get_child("photo.db"));
+
+ // initialize units
+ try {
+ Library.app_init();
+ } catch (Error err) {
+ AppWindow.panic(err.message);
+
+ return;
+ }
+
+ // validate the databases prior to using them
+ message("Verifying database ...");
+ string errormsg = null;
+ string app_version;
+ int schema_version;
+ Db.VerifyResult result = Db.verify_database(out app_version, out schema_version);
+ switch (result) {
+ case Db.VerifyResult.OK:
+ // do nothing; no problems
+ break;
+
+ case Db.VerifyResult.FUTURE_VERSION:
+ errormsg = _("Your photo library is not compatible with this version of Shotwell. It appears it was created by Shotwell %s (schema %d). This version is %s (schema %d). Please use the latest version of Shotwell.").printf(
+ app_version, schema_version, Resources.APP_VERSION, DatabaseTable.SCHEMA_VERSION);
+ break;
+
+ case Db.VerifyResult.UPGRADE_ERROR:
+ errormsg = _("Shotwell was unable to upgrade your photo library from version %s (schema %d) to %s (schema %d). For more information please check the Shotwell Wiki at %s").printf(
+ app_version, schema_version, Resources.APP_VERSION, DatabaseTable.SCHEMA_VERSION,
+ Resources.HOME_URL);
+ break;
+
+ case Db.VerifyResult.NO_UPGRADE_AVAILABLE:
+ errormsg = _("Your photo library is not compatible with this version of Shotwell. It appears it was created by Shotwell %s (schema %d). This version is %s (schema %d). Please clear your library by deleting %s and re-import your photos.").printf(
+ app_version, schema_version, Resources.APP_VERSION, DatabaseTable.SCHEMA_VERSION,
+ AppDirs.get_data_dir().get_path());
+ break;
+
+ default:
+ errormsg = _("Unknown error attempting to verify Shotwell's database: %s").printf(
+ result.to_string());
+ break;
+ }
+
+ if (errormsg != null) {
+ Gtk.MessageDialog dialog = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, "%s", errormsg);
+ dialog.title = Resources.APP_TITLE;
+ dialog.run();
+ dialog.destroy();
+
+ DatabaseTable.terminate();
+
+ return;
+ }
+
+ Upgrades.init();
+
+ ProgressDialog progress_dialog = null;
+ AggregateProgressMonitor aggregate_monitor = null;
+ ProgressMonitor monitor = null;
+
+ if (!CommandlineOptions.no_startup_progress) {
+ // only throw up a startup progress dialog if over a reasonable amount of objects ... multiplying
+ // photos by two because there's two heavy-duty operations on them: creating the LibraryPhoto
+ // objects and then populating the initial page with them.
+ uint64 grand_total = PhotoTable.get_instance().get_row_count()
+ + EventTable.get_instance().get_row_count()
+ + TagTable.get_instance().get_row_count()
+ + VideoTable.get_instance().get_row_count()
+ + Upgrades.get_instance().get_step_count();
+ if (grand_total > 5000) {
+ progress_dialog = new ProgressDialog(null, _("Loading Shotwell"));
+ progress_dialog.update_display_every(100);
+ progress_dialog.set_minimum_on_screen_time_msec(250);
+ try {
+ string icon_path = AppDirs.get_resources_dir().get_child("icons").get_child("shotwell.svg").get_path();
+ progress_dialog.icon = new Gdk.Pixbuf.from_file(icon_path);
+ } catch (Error err) {
+ debug("Warning - could not load application icon for loading window: %s", err.message);
+ }
+
+ aggregate_monitor = new AggregateProgressMonitor(grand_total, progress_dialog.monitor);
+ monitor = aggregate_monitor.monitor;
+ }
+ }
+
+ ThumbnailCache.init();
+ Tombstone.init();
+
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("LibraryPhoto.init");
+ LibraryPhoto.init(monitor);
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("Video.init");
+ Video.init(monitor);
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("Upgrades.execute");
+ Upgrades.get_instance().execute();
+
+ LibraryMonitorPool.init();
+ MediaCollectionRegistry.init();
+ MediaCollectionRegistry registry = MediaCollectionRegistry.get_instance();
+ registry.register_collection(LibraryPhoto.global);
+ registry.register_collection(Video.global);
+
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("Event.init");
+ Event.init(monitor);
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("Tag.init");
+ Tag.init(monitor);
+
+ MetadataWriter.init();
+ DesktopIntegration.init();
+
+ Application.get_instance().init_done();
+
+ // create main library application window
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("LibraryWindow");
+ LibraryWindow library_window = new LibraryWindow(monitor);
+
+ if (aggregate_monitor != null)
+ aggregate_monitor.next_step("done");
+
+ // destroy and tear down everything ... no need for them to stick around the lifetime of the
+ // application
+
+ monitor = null;
+ aggregate_monitor = null;
+ if (progress_dialog != null)
+ progress_dialog.destroy();
+ progress_dialog = null;
+
+ // report mount points
+ foreach (string mount in mounts)
+ library_window.mounted_camera_shell_notification(mount, true);
+
+ library_window.show_all();
+
+ WelcomeServiceEntry[] selected_import_entries = new WelcomeServiceEntry[0];
+ if (Config.Facade.get_instance().get_show_welcome_dialog() &&
+ LibraryPhoto.global.get_count() == 0) {
+ WelcomeDialog welcome = new WelcomeDialog(library_window);
+ Config.Facade.get_instance().set_show_welcome_dialog(welcome.execute(out selected_import_entries,
+ out do_system_pictures_import));
+ } else {
+ Config.Facade.get_instance().set_show_welcome_dialog(false);
+ }
+
+ if (selected_import_entries.length > 0) {
+ do_external_import = true;
+ foreach (WelcomeServiceEntry entry in selected_import_entries)
+ entry.execute();
+ }
+ if (do_system_pictures_import) {
+ /* Do the system import even if other plugins have run as some plugins may not
+ as some plugins may not import pictures from the system folder.
+ */
+ run_system_pictures_import();
+ }
+
+ debug("%lf seconds to Gtk.main()", startup_timer.elapsed());
+
+ Application.get_instance().start();
+
+ DesktopIntegration.terminate();
+ MetadataWriter.terminate();
+ Tag.terminate();
+ Event.terminate();
+ LibraryPhoto.terminate();
+ MediaCollectionRegistry.terminate();
+ LibraryMonitorPool.terminate();
+ Tombstone.terminate();
+ ThumbnailCache.terminate();
+ Video.terminate();
+ Library.app_terminate();
+}
+
+private bool do_system_pictures_import = false;
+private bool do_external_import = false;
+
+public void run_system_pictures_import(ImportManifest? external_exclusion_manifest = null) {
+ if (!do_system_pictures_import)
+ return;
+
+ Gee.ArrayList<FileImportJob> jobs = new Gee.ArrayList<FileImportJob>();
+ jobs.add(new FileImportJob(AppDirs.get_import_dir(), false));
+
+ LibraryWindow library_window = (LibraryWindow) AppWindow.get_instance();
+
+ BatchImport batch_import = new BatchImport(jobs, "startup_import",
+ report_system_pictures_import, null, null, null, null, external_exclusion_manifest);
+ library_window.enqueue_batch_import(batch_import, true);
+
+ library_window.switch_to_import_queue_page();
+}
+
+private void report_system_pictures_import(ImportManifest manifest, BatchImportRoll import_roll) {
+ /* Don't report the manifest to the user if exteral import was done and the entire manifest
+ is empty. An empty manifest in this case results from files that were already imported
+ in the external import phase being skipped. Note that we are testing against manifest.all,
+ not manifest.success; manifest.all is zero when no files were enqueued for import in the
+ first place and the only way this happens is if all files were skipped -- even failed
+ files are counted in manifest.all */
+ if (do_external_import && (manifest.all.size == 0))
+ return;
+
+ ImportUI.report_manifest(manifest, true);
+}
+
+void editing_exec(string filename) {
+ File initial_file = File.new_for_commandline_arg(filename);
+
+ // preconfigure units
+ Direct.preconfigure(initial_file);
+ Db.preconfigure(null);
+
+ // initialize units for direct-edit mode
+ try {
+ Direct.app_init();
+ } catch (Error err) {
+ AppWindow.panic(err.message);
+
+ return;
+ }
+
+ // init modules direct-editing relies on
+ DesktopIntegration.init();
+
+ // TODO: At some point in the future, to support mixed-media in direct-edit mode, we will
+ // refactor DirectPhotoSourceCollection to be a MediaSourceCollection. At that point,
+ // we'll need to register DirectPhoto.global with the MediaCollectionRegistry
+
+ DirectWindow direct_window = new DirectWindow(initial_file);
+ direct_window.show_all();
+
+ debug("%lf seconds to Gtk.main()", startup_timer.elapsed());
+
+ Application.get_instance().start();
+
+ DesktopIntegration.terminate();
+
+ // terminate units for direct-edit mode
+ Direct.app_terminate();
+}
+
+namespace CommandlineOptions {
+
+bool no_startup_progress = false;
+string data_dir = null;
+bool show_version = false;
+bool no_runtime_monitoring = false;
+
+private OptionEntry[]? entries = null;
+
+public OptionEntry[] get_options() {
+ if (entries != null)
+ return entries;
+
+ OptionEntry datadir = { "datadir", 'd', 0, OptionArg.FILENAME, &data_dir,
+ _("Path to Shotwell's private data"), _("DIRECTORY") };
+ entries += datadir;
+
+ OptionEntry no_monitoring = { "no-runtime-monitoring", 0, 0, OptionArg.NONE, &no_runtime_monitoring,
+ _("Do not monitor library directory at runtime for changes"), null };
+ entries += no_monitoring;
+
+ OptionEntry no_startup = { "no-startup-progress", 0, 0, OptionArg.NONE, &no_startup_progress,
+ _("Don't display startup progress meter"), null };
+ entries += no_startup;
+
+ OptionEntry version = { "version", 'V', 0, OptionArg.NONE, &show_version,
+ _("Show the application's version"), null };
+ entries += version;
+
+ OptionEntry terminator = { null, 0, 0, 0, null, null, null };
+ entries += terminator;
+
+ return entries;
+}
+
+}
+
+void main(string[] args) {
+ // Call AppDirs init *before* calling Gtk.init_with_args, as it will strip the
+ // exec file from the array
+ AppDirs.init(args[0]);
+
+ // This has to be done before the AppWindow is created in order to ensure the XMP
+ // parser is initialized in a thread-safe fashion; please see
+ // http://redmine.yorba.org/issues/4120 for details.
+ GExiv2.initialize();
+
+ // following the GIO programming guidelines at http://developer.gnome.org/gio/2.26/ch03.html,
+ // set the GSETTINGS_SCHEMA_DIR environment variable to allow us to load GSettings schemas from
+ // the build directory. this allows us to access local GSettings schemas without having to
+ // muck with the user's XDG_... directories, which is seriously frowned upon
+ if (AppDirs.get_install_dir() == null) {
+ GLib.Environment.set_variable("GSETTINGS_SCHEMA_DIR", AppDirs.get_exec_dir().get_path() +
+ "/misc", true);
+ }
+
+ // init GTK (valac has already called g_threads_init())
+ try {
+ Gtk.init_with_args(ref args, _("[FILE]"), CommandlineOptions.get_options(),
+ Resources.APP_GETTEXT_PACKAGE);
+ } catch (Error e) {
+ print(e.message + "\n");
+ print(_("Run '%s --help' to see a full list of available command line options.\n"), args[0]);
+ AppDirs.terminate();
+ return;
+ }
+
+ if (CommandlineOptions.show_version) {
+ if (Resources.GIT_VERSION != null)
+ print("%s %s (%s)\n", Resources.APP_TITLE, Resources.APP_VERSION, Resources.GIT_VERSION);
+ else
+ print("%s %s\n", Resources.APP_TITLE, Resources.APP_VERSION);
+
+ AppDirs.terminate();
+
+ return;
+ }
+
+ // init debug prior to anything else (except Gtk, which it relies on, and AppDirs, which needs
+ // to be set ASAP) ... since we need to know what mode we're in, examine the command-line
+ // first
+
+ // walk command-line arguments for camera mounts or filename for direct editing ... only one
+ // filename supported for now, so take the first one and drop the rest ... note that URIs for
+ // filenames are currently not permitted, to differentiate between mount points
+ string[] mounts = new string[0];
+ string filename = null;
+
+ for (int ctr = 1; ctr < args.length; ctr++) {
+ string arg = args[ctr];
+
+ if (LibraryWindow.is_mount_uri_supported(arg)) {
+ mounts += arg;
+ } else if (is_string_empty(filename) && !arg.contains("://")) {
+ filename = arg;
+ }
+ }
+
+ Debug.init(is_string_empty(filename) ? Debug.LIBRARY_PREFIX : Debug.VIEWER_PREFIX);
+
+ if (Resources.GIT_VERSION != null)
+ message("Shotwell %s %s (%s)",
+ is_string_empty(filename) ? Resources.APP_LIBRARY_ROLE : Resources.APP_DIRECT_ROLE,
+ Resources.APP_VERSION, Resources.GIT_VERSION);
+ else
+ message("Shotwell %s %s",
+ is_string_empty(filename) ? Resources.APP_LIBRARY_ROLE : Resources.APP_DIRECT_ROLE,
+ Resources.APP_VERSION);
+
+ // 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)
+ AppDirs.set_data_dir(CommandlineOptions.data_dir);
+ else
+ AppDirs.try_migrate_data();
+
+ // Verify the private data directory before continuing
+ AppDirs.verify_data_dir();
+ AppDirs.verify_cache_dir();
+
+ // init internationalization with the default system locale
+ InternationalSupport.init(Resources.APP_GETTEXT_PACKAGE, args);
+
+ startup_timer = new Timer();
+ startup_timer.start();
+
+ // set up GLib environment
+ GLib.Environment.set_application_name(Resources.APP_TITLE);
+
+ // in both the case of running as the library or an editor, Resources is always
+ // initialized
+ Resources.init();
+
+ // since it's possible for a mount name to be passed that's not supported (and hence an empty
+ // mount list), or for nothing to be on the command-line at all, only go to direct editing if a
+ // filename is spec'd
+ if (is_string_empty(filename))
+ library_exec(mounts);
+ else
+ editing_exec(filename);
+
+ // terminate mode-inspecific modules
+ Resources.terminate();
+ Application.terminate();
+ Debug.terminate();
+ AppDirs.terminate();
+
+ // Back up db on successful run so we have something to roll back to if
+ // it gets corrupted in the next session. Don't do this if another shotwell
+ // is open or if we're in direct mode.
+ if (is_string_empty(filename) && !was_already_running) {
+ string orig_path = AppDirs.get_data_subdir("data").get_child("photo.db").get_path();
+ string backup_path = orig_path + ".bak";
+ string cmdline = "cp " + orig_path + " " + backup_path;
+ Posix.system(cmdline);
+ Posix.system("sync");
+ }
+}
+