diff options
Diffstat (limited to 'src/main.vala')
-rw-r--r-- | src/main.vala | 441 |
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"); + } +} + |