/* Copyright 2016 Software Freedom Conservancy Inc. * * This software is licensed under the GNU Lesser General Public License * (version 2.1 or later). See the COPYING file in this distribution. */ public abstract class SourceCollection : DataCollection { private class DestroyCounter : Object { public Marker remove_marker; public Gee.ArrayList notify_list = new Gee.ArrayList(); public Gee.ArrayList not_removed = new Gee.ArrayList(); public DestroyCounter(Marker remove_marker) { this.remove_marker = remove_marker; } } // When this signal is fired, the items are about to be unlinked from the collection. The // appropriate remove signals will follow. public virtual signal void items_unlinking(Gee.Collection unlinking) { } // When this signal is fired, the items are being relinked to the collection. The appropriate // add signals have already been fired. public virtual signal void items_relinked(Gee.Collection relinked) { } // When this signal is fired, the item is still part of the collection but its own destroy() // has already been called. public virtual signal void item_destroyed(DataSource source) { } // When this signal is fired, the item is still part of the collection but its own destroy() // has already been called. public virtual signal void items_destroyed(Gee.Collection destroyed) { } // When this signal is fired, the unlinked item has been unlinked from the collection previously // and its destroy() has been called. public virtual signal void unlinked_destroyed(DataSource source) { } // When this signal is fired, the backlink to the ContainerSource has already been removed. public virtual signal void backlink_removed(SourceBacklink backlink, Gee.Collection sources) { } private Gee.MultiMap? backlinks = null; public SourceCollection(string name) { base (name); } public abstract bool holds_type_of_source(DataSource source); protected virtual void notify_items_unlinking(Gee.Collection unlinking) { items_unlinking(unlinking); } protected virtual void notify_items_relinked(Gee.Collection relinked) { items_relinked(relinked); } protected virtual void notify_item_destroyed(DataSource source) { item_destroyed(source); } protected virtual void notify_items_destroyed(Gee.Collection destroyed) { items_destroyed(destroyed); } // This is only called by DataSource. public virtual void notify_unlinked_destroyed(DataSource unlinked) { unlinked_destroyed(unlinked); } protected virtual void notify_backlink_removed(SourceBacklink backlink, Gee.Collection sources) { backlink_removed(backlink, sources); } protected override bool valid_type(DataObject object) { return object is DataSource; } // Destroy all marked items and optionally have them delete their backing. Returns the // number of items which failed to delete their backing (if delete_backing is true) or zero. public int destroy_marked(Marker marker, bool delete_backing, ProgressMonitor? monitor = null, Gee.List? not_removed = null) { DestroyCounter counter = new DestroyCounter(start_marking()); if (delete_backing) act_on_marked(marker, destroy_and_delete_source, monitor, counter); else act_on_marked(marker, destroy_source, monitor, counter); // notify of destruction foreach (DataSource source in counter.notify_list) notify_item_destroyed(source); notify_items_destroyed(counter.notify_list); // remove once all destroyed remove_marked(counter.remove_marker); if (null != not_removed) { not_removed.add_all(counter.not_removed); } return counter.not_removed.size; } private bool destroy_and_delete_source(DataObject object, Object? user) { bool success = false; try { success = ((DataSource) object).internal_delete_backing(); } catch (Error err) { success = false; } if (!success && object is MediaSource) { ((DestroyCounter) user).not_removed.add((MediaSource) object); } return destroy_source(object, user) && success; } private bool destroy_source(DataObject object, Object? user) { DataSource source = (DataSource) object; source.internal_mark_for_destroy(); source.destroy(); ((DestroyCounter) user).remove_marker.mark(source); ((DestroyCounter) user).notify_list.add(source); return true; } // This is only called by DataSource. public void internal_backlink_set(DataSource source, SourceBacklink backlink) { if (backlinks == null) { backlinks = new Gee.HashMultiMap(SourceBacklink.hash_func, SourceBacklink.equal_func); } backlinks.set(backlink, source); } // This is only called by DataSource. public void internal_backlink_removed(DataSource source, SourceBacklink backlink) { assert(backlinks != null); bool removed = backlinks.remove(backlink, source); assert(removed); } public virtual bool has_backlink(SourceBacklink backlink) { return backlinks != null ? backlinks.contains(backlink) : false; } public Gee.Collection? unlink_marked(Marker marker, ProgressMonitor? monitor = null) { Gee.ArrayList list = new Gee.ArrayList(); act_on_marked(marker, prepare_for_unlink, monitor, list); if (list.size == 0) return null; notify_items_unlinking(list); remove_marked(mark_many(list)); return list; } private bool prepare_for_unlink(DataObject object, Object? user) { DataSource source = (DataSource) object; source.notify_unlinking(this); ((Gee.List) user).add(source); return true; } public void relink(DataSource source) { source.notify_relinking(this); add(source); notify_items_relinked((Gee.Collection) get_singleton(source)); source.notify_relinked(); } public void relink_many(Gee.Collection relink) { if (relink.size == 0) return; foreach (DataSource source in relink) source.notify_relinking(this); add_many(relink); notify_items_relinked(relink); foreach (DataSource source in relink) source.notify_relinked(); } public virtual void remove_backlink(SourceBacklink backlink) { if (backlinks == null) return; // create copy because the DataSources will be removing the backlinks Gee.ArrayList sources = new Gee.ArrayList(); sources.add_all(backlinks.get(backlink)); foreach (DataSource source in sources) source.remove_backlink(backlink); notify_backlink_removed(backlink, sources); } }