summaryrefslogtreecommitdiff
path: root/src/MediaMonitor.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/MediaMonitor.vala')
-rw-r--r--src/MediaMonitor.vala418
1 files changed, 418 insertions, 0 deletions
diff --git a/src/MediaMonitor.vala b/src/MediaMonitor.vala
new file mode 100644
index 0000000..aeb2952
--- /dev/null
+++ b/src/MediaMonitor.vala
@@ -0,0 +1,418 @@
+/* Copyright 2010-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public class MonitorableUpdates {
+ public Monitorable monitorable;
+
+ private File? master_file = null;
+ private bool master_file_info_altered = false;
+ private FileInfo? master_file_info = null;
+ private bool master_in_alteration = false;
+ private bool online = false;
+ private bool offline = false;
+
+ public MonitorableUpdates(Monitorable monitorable) {
+ this.monitorable = monitorable;
+ }
+
+ public File? get_master_file() {
+ return master_file;
+ }
+
+ public FileInfo? get_master_file_info() {
+ return master_file_info;
+ }
+
+ public virtual bool is_in_alteration() {
+ return master_in_alteration;
+ }
+
+ public bool is_set_offline() {
+ return offline;
+ }
+
+ public bool is_set_online() {
+ return online;
+ }
+
+ public virtual void set_master_file(File? file) {
+ master_file = file;
+
+ if (file != null)
+ mark_online();
+ }
+
+ public virtual void set_master_file_info_altered(bool altered) {
+ master_file_info_altered = altered;
+
+ if (altered)
+ mark_online();
+ }
+
+ public virtual void set_master_file_info(FileInfo? info) {
+ master_file_info = info;
+
+ if (master_file_info == null)
+ set_master_file_info_altered(false);
+ }
+
+ public virtual void set_master_in_alteration(bool in_alteration) {
+ master_in_alteration = in_alteration;
+ }
+
+ public virtual void set_master_alterations_complete(FileInfo info) {
+ set_master_in_alteration(false);
+ set_master_file_info(info);
+ mark_online();
+ }
+
+ public virtual void mark_offline() {
+ online = false;
+ offline = true;
+
+ master_file_info_altered = false;
+ master_file_info = null;
+ master_in_alteration = false;
+ }
+
+ public virtual void mark_online() {
+ online = true;
+ offline = false;
+ }
+
+ public virtual void reset_online_offline() {
+ online = false;
+ offline = false;
+ }
+
+ public virtual bool is_all_updated() {
+ return master_file == null
+ && master_file_info_altered == false
+ && master_file_info == null
+ && master_in_alteration == false
+ && online == false
+ && offline == false;
+ }
+}
+
+public abstract class MediaMonitor : Object {
+ public enum DiscoveredFile {
+ REPRESENTED,
+ IGNORE,
+ UNKNOWN
+ }
+
+ protected const int MAX_OPERATIONS_PER_CYCLE = 100;
+
+ private const int FLUSH_PENDING_UPDATES_MSEC = 500;
+
+ private MediaSourceCollection sources;
+ private Cancellable cancellable;
+ private Gee.HashMap<Monitorable, MonitorableUpdates> pending_updates = new Gee.HashMap<Monitorable,
+ MonitorableUpdates>();
+ private uint pending_updates_timer_id = 0;
+
+ public MediaMonitor(MediaSourceCollection sources, Cancellable cancellable) {
+ this.sources = sources;
+ this.cancellable = cancellable;
+
+ sources.item_destroyed.connect(on_media_source_destroyed);
+ sources.unlinked_destroyed.connect(on_media_source_destroyed);
+
+ pending_updates_timer_id = Timeout.add(FLUSH_PENDING_UPDATES_MSEC, on_flush_pending_updates,
+ Priority.LOW);
+ }
+
+ ~MediaMonitor() {
+ sources.item_destroyed.disconnect(on_media_source_destroyed);
+ sources.unlinked_destroyed.disconnect(on_media_source_destroyed);
+ }
+
+ public abstract MediaSourceCollection get_media_source_collection();
+
+ public virtual void close() {
+ }
+
+ public virtual string to_string() {
+ return "MediaMonitor for %s".printf(get_media_source_collection().to_string());
+ }
+
+ protected virtual MonitorableUpdates create_updates(Monitorable monitorable) {
+ return new MonitorableUpdates(monitorable);
+ }
+
+ protected virtual void on_media_source_destroyed(DataSource source) {
+ remove_updates((Monitorable) source);
+ }
+
+ //
+ // The following are called when the startup scan is initiated.
+ //
+
+ public virtual void notify_discovery_started() {
+ }
+
+ // Returns the Monitorable represented in some form by the monitors' MediaSourceCollection.
+ // If DiscoveredFile.REPRESENTED is returns, monitorable should be set.
+ public abstract DiscoveredFile notify_file_discovered(File file, FileInfo info,
+ out Monitorable monitorable);
+
+ // Returns REPRESENTED if the file has been *definitively* associated with a Monitorable,
+ // in which case the file will no longer be considered unknown. Returns IGNORE if the file
+ // is known in some other case and should not be considered unknown. Returns UNKNOWN otherwise,
+ // with potentially a collection of candidates for the file. The collection may be zero-length.
+ //
+ // NOTE: This method may be called after the startup scan as well.
+ public abstract Gee.Collection<Monitorable>? candidates_for_unknown_file(File file, FileInfo info,
+ out DiscoveredFile result);
+
+ public virtual File[]? get_auxilliary_backing_files(Monitorable monitorable) {
+ return null;
+ }
+
+ // info is null if the file was not found. Note that master online/offline state is already
+ // set by LibraryMonitor.
+ public virtual void update_backing_file_info(Monitorable monitorable, File file, FileInfo? info) {
+ }
+
+ // Not that discovery has completed, but the MediaMonitor's role in it has finished.
+ public virtual void notify_discovery_completing() {
+ }
+
+ //
+ // The following are called after the startup scan for runtime monitoring.
+ //
+
+ public abstract bool is_file_represented(File file);
+
+ public abstract bool notify_file_created(File file, FileInfo info);
+
+ public abstract bool notify_file_moved(File old_file, File new_file, FileInfo new_file_info);
+
+ public abstract bool notify_file_altered(File file);
+
+ public abstract bool notify_file_attributes_altered(File file);
+
+ public abstract bool notify_file_alteration_completed(File file, FileInfo info);
+
+ public abstract bool notify_file_deleted(File file);
+
+ protected static void mdbg(string msg) {
+#if TRACE_MONITORING
+ debug("%s", msg);
+#endif
+ }
+
+ public bool has_pending_updates() {
+ return pending_updates.size > 0;
+ }
+
+ public Gee.Collection<Monitorable> get_monitorables() {
+ return pending_updates.keys;
+ }
+
+ // This will create a MonitorableUpdates and register it with this updater if not already
+ // exists.
+ public MonitorableUpdates fetch_updates(Monitorable monitorable) {
+ MonitorableUpdates? updates = pending_updates.get(monitorable);
+ if (updates != null)
+ return updates;
+
+ updates = create_updates(monitorable);
+ pending_updates.set(monitorable, updates);
+
+ return updates;
+ }
+
+ public MonitorableUpdates? get_existing_updates(Monitorable monitorable) {
+ return pending_updates.get(monitorable);
+ }
+
+ public void remove_updates(Monitorable monitorable) {
+ pending_updates.unset(monitorable);
+ }
+
+ public bool is_online(Monitorable monitorable) {
+ MonitorableUpdates? updates = get_existing_updates(monitorable);
+
+ return (updates != null) ? updates.is_set_online() : !monitorable.is_offline();
+ }
+
+ public bool is_offline(Monitorable monitorable) {
+ MonitorableUpdates? updates = get_existing_updates(monitorable);
+
+ return (updates != null) ? updates.is_set_offline() : monitorable.is_offline();
+ }
+
+ public File get_master_file(Monitorable monitorable) {
+ MonitorableUpdates? updates = get_existing_updates(monitorable);
+
+ return (updates != null && updates.get_master_file() != null) ? updates.get_master_file()
+ : monitorable.get_master_file();
+ }
+
+ public void update_master_file(Monitorable monitorable, File file) {
+ fetch_updates(monitorable).set_master_file(file);
+ }
+
+ public void update_master_file_info_altered(Monitorable monitorable) {
+ fetch_updates(monitorable).set_master_file_info_altered(true);
+ }
+
+ public void update_master_file_in_alteration(Monitorable monitorable, bool in_alteration) {
+ fetch_updates(monitorable).set_master_in_alteration(in_alteration);
+ }
+
+ public void update_master_file_alterations_completed(Monitorable monitorable, FileInfo info) {
+ fetch_updates(monitorable).set_master_alterations_complete(info);
+ }
+
+ public void update_online(Monitorable monitorable) {
+ fetch_updates(monitorable).mark_online();
+ }
+
+ public void update_offline(Monitorable monitorable) {
+ fetch_updates(monitorable).mark_offline();
+ }
+
+ // Children should call this method before doing their own processing. Every operation should
+ // be recorded by incrementing op_count. If it is greater than MAX_OPERATIONS_PER_CYCLE,
+ // the method should process what has been done and exit to let the operations be handled in
+ // the next cycle.
+ protected virtual void process_updates(Gee.Collection<MonitorableUpdates> all_updates,
+ TransactionController controller, ref int op_count) throws Error {
+ Gee.Map<Monitorable, File> set_master_file = null;
+ Gee.Map<Monitorable, FileInfo> set_master_file_info = null;
+ Gee.ArrayList<MediaSource> to_offline = null;
+ Gee.ArrayList<MediaSource> to_online = null;
+
+ foreach (MonitorableUpdates updates in all_updates) {
+ if (op_count >= MAX_OPERATIONS_PER_CYCLE)
+ break;
+
+ if (updates.get_master_file() != null) {
+ if (set_master_file == null)
+ set_master_file = new Gee.HashMap<Monitorable, File>();
+
+ set_master_file.set(updates.monitorable, updates.get_master_file());
+ updates.set_master_file(null);
+ op_count++;
+ }
+
+ if (updates.get_master_file_info() != null) {
+ if (set_master_file_info == null)
+ set_master_file_info = new Gee.HashMap<Monitorable, FileInfo>();
+
+ set_master_file_info.set(updates.monitorable, updates.get_master_file_info());
+ updates.set_master_file_info(null);
+ op_count++;
+ }
+
+ if (updates.is_set_offline()) {
+ if (to_offline == null)
+ to_offline = new Gee.ArrayList<LibraryPhoto>();
+
+ to_offline.add(updates.monitorable);
+ updates.reset_online_offline();
+ op_count++;
+ }
+
+ if (updates.is_set_online()) {
+ if (to_online == null)
+ to_online = new Gee.ArrayList<LibraryPhoto>();
+
+ to_online.add(updates.monitorable);
+ updates.reset_online_offline();
+ op_count++;
+ }
+ }
+
+ if (set_master_file != null) {
+ mdbg("Changing master file of %d objects in %s".printf(set_master_file.size, to_string()));
+
+ Monitorable.set_many_master_file(set_master_file, controller);
+ }
+
+ if (set_master_file_info != null) {
+ mdbg("Updating %d master files timestamps in %s".printf(set_master_file_info.size,
+ to_string()));
+
+ Monitorable.set_many_master_timestamp(set_master_file_info, controller);
+ }
+
+ if (to_offline != null || to_online != null) {
+ mdbg("Marking %d online, %d offline in %s".printf(
+ (to_online != null) ? to_online.size : 0,
+ (to_offline != null) ? to_offline.size : 0,
+ to_string()));
+
+ Monitorable.mark_many_online_offline(to_online, to_offline, controller);
+ }
+ }
+
+ private bool on_flush_pending_updates() {
+ if (cancellable.is_cancelled())
+ return false;
+
+ if (pending_updates.size == 0)
+ return true;
+
+ Timer timer = new Timer();
+
+ // build two lists: one, of MonitorableUpdates that are not in_alteration() (which
+ // simplifies matters), and two, of completed MonitorableUpdates that should be removed
+ // from the list (which would have happened after the last pass)
+ Gee.ArrayList<MonitorableUpdates> to_process = null;
+ Gee.ArrayList<Monitorable> to_remove = null;
+ foreach (MonitorableUpdates updates in pending_updates.values) {
+ if (updates.is_in_alteration())
+ continue;
+
+ if (updates.is_all_updated()) {
+ if (to_remove == null)
+ to_remove = new Gee.ArrayList<Monitorable>();
+
+ to_remove.add(updates.monitorable);
+ continue;
+ }
+
+ if (to_process == null)
+ to_process = new Gee.ArrayList<MonitorableUpdates>();
+
+ to_process.add(updates);
+ }
+
+ int op_count = 0;
+ if (to_process != null) {
+ TransactionController controller = get_media_source_collection().transaction_controller;
+
+ try {
+ controller.begin();
+ process_updates(to_process, controller, ref op_count);
+ controller.commit();
+ } catch (Error err) {
+ if (err is DatabaseError)
+ AppWindow.database_error((DatabaseError) err);
+ else
+ AppWindow.panic(_("Unable to process monitoring updates: %s").printf(err.message));
+ }
+ }
+
+ if (to_remove != null) {
+ foreach (Monitorable monitorable in to_remove)
+ remove_updates(monitorable);
+ }
+
+ double elapsed = timer.elapsed();
+ if (elapsed > 0.01 || op_count > 0) {
+ mdbg("Total pending queue time for %s: %lf (%d ops)".printf(to_string(), elapsed,
+ op_count));
+ }
+
+ return true;
+ }
+}
+