diff options
Diffstat (limited to 'src/core/SourceHoldingTank.vala')
-rw-r--r-- | src/core/SourceHoldingTank.vala | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/src/core/SourceHoldingTank.vala b/src/core/SourceHoldingTank.vala new file mode 100644 index 0000000..adfec8b --- /dev/null +++ b/src/core/SourceHoldingTank.vala @@ -0,0 +1,209 @@ +/* Copyright 2011-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. + */ + +// A SourceHoldingTank is similar to the holding tank used by ContainerSourceCollection, but for +// non-ContainerSources to be held offline from their natural SourceCollection (i.e. PhotoSources +// being held in a trashcan, for example). It is *not* a DataCollection (important!), but rather +// a signalled collection that moves DataSources to and from their SourceCollection. +// +// DataSources can be shuttled from their SourceCollection to the SourceHoldingTank manually +// (via unlink_and_hold) or can be automatically moved by installing a HoldingPredicate. +// Only one HoldingConditional may be installed. Because of assertions in the methods, it's unwise +// to use more than one method. add() and add_many() should ONLY be used for DataSources not +// first installed in their SourceCollection (i.e. they're born in the SourceHoldingTank). +// +// NOTE: DataSources should never be in more than one SourceHoldingTank. No tests are performed +// here to verify this. This is why a filter/predicate method (which could automatically move +// them in as they're altered) is not offered; there's no easy way to keep DataSources from being +// moved into more than one holding tank, or which should have preference. The CheckToRemove +// predicate is offered only to know when to release them. + +public class SourceHoldingTank { + // Return true if the DataSource should remain in the SourceHoldingTank, false otherwise. + public delegate bool CheckToKeep(DataSource source, Alteration alteration); + + private SourceCollection sources; + private unowned CheckToKeep check_to_keep; + private DataSet tank = new DataSet(); + private Gee.HashSet<DataSource> relinks = new Gee.HashSet<DataSource>(); + private Gee.HashSet<DataSource> unlinking = new Gee.HashSet<DataSource>(); + private int64 ordinal = 0; + + public virtual signal void contents_altered(Gee.Collection<DataSource>? added, + Gee.Collection<DataSource>? removed) { + } + + public SourceHoldingTank(SourceCollection sources, CheckToKeep check_to_keep) { + this.sources = sources; + this.check_to_keep = check_to_keep; + + this.sources.item_destroyed.connect(on_source_destroyed); + this.sources.thawed.connect(on_source_collection_thawed); + } + + ~SourceHoldingTank() { + sources.item_destroyed.disconnect(on_source_destroyed); + sources.thawed.disconnect(on_source_collection_thawed); + } + + protected virtual void notify_contents_altered(Gee.Collection<DataSource>? added, + Gee.Collection<DataSource>? removed) { + if (added != null) { + foreach (DataSource source in added) + source.notify_held_in_tank(this); + } + + if (removed != null) { + foreach (DataSource source in removed) + source.notify_held_in_tank(null); + } + + contents_altered(added, removed); + } + + public int get_count() { + return tank.get_count(); + } + + public Gee.Collection<DataSource> get_all() { + return (Gee.Collection<DataSource>) tank.get_all(); + } + + public bool contains(DataSource source) { + return tank.contains(source) || unlinking.contains(source); + } + + // Only use for DataSources that have not been installed in their SourceCollection. + public void add_many(Gee.Collection<DataSource> many) { + if (many.size == 0) + return; + + foreach (DataSource source in many) + source.internal_set_ordinal(ordinal++); + + bool added = tank.add_many(many); + assert(added); + + notify_contents_altered(many, null); + } + + // Do not pass in DataSources which have already been unlinked, including into this holding + // tank. + public void unlink_and_hold(Gee.Collection<DataSource> unlink) { + if (unlink.size == 0) + return; + + // store in the unlinking collection to guard against reentrancy + unlinking.add_all(unlink); + + sources.unlink_marked(sources.mark_many(unlink)); + + foreach (DataSource source in unlink) + source.internal_set_ordinal(ordinal++); + + bool added = tank.add_many(unlink); + assert(added); + + // remove from the unlinking pool, as they're now unlinked + unlinking.remove_all(unlink); + + notify_contents_altered(unlink, null); + } + + public bool has_backlink(SourceBacklink backlink) { + int count = tank.get_count(); + for (int ctr = 0; ctr < count; ctr++) { + if (((DataSource) tank.get_at(ctr)).has_backlink(backlink)) + return true; + } + + return false; + } + + public void remove_backlink(SourceBacklink backlink) { + int count = tank.get_count(); + for (int ctr = 0; ctr < count; ctr++) + ((DataSource) tank.get_at(ctr)).remove_backlink(backlink); + } + + public void destroy_orphans(Gee.List<DataSource> destroy, bool delete_backing, + ProgressMonitor? monitor = null, Gee.List<DataSource>? not_removed = null) { + if (destroy.size == 0) + return; + + bool removed = tank.remove_many(destroy); + assert(removed); + + notify_contents_altered(null, destroy); + + int count = destroy.size; + for (int ctr = 0; ctr < count; ctr++) { + DataSource source = destroy.get(ctr); + if (!source.destroy_orphan(delete_backing)) { + if (null != not_removed) { + not_removed.add(source); + } + } + if (monitor != null) + monitor(ctr + 1, count); + } + } + + private void on_source_destroyed(DataSource source) { + if (!tank.contains(source)) + return; + + bool removed = tank.remove(source); + assert(removed); + + notify_contents_altered(null, new SingletonCollection<DataSource>(source)); + } + + // This is only called by DataSource + public void internal_notify_altered(DataSource source, Alteration alteration) { + if (!tank.contains(source)) { + debug("SourceHoldingTank.internal_notify_altered called for %s not stored in %s", + source.to_string(), to_string()); + + return; + } + + // see if it should stay put + if (check_to_keep(source, alteration)) + return; + + bool removed = tank.remove(source); + assert(removed); + + if (sources.are_notifications_frozen()) { + relinks.add(source); + + return; + } + + notify_contents_altered(null, new SingletonCollection<DataSource>(source)); + + sources.relink(source); + } + + private void on_source_collection_thawed() { + if (relinks.size == 0) + return; + + // swap out to protect against reentrancy + Gee.HashSet<DataSource> copy = relinks; + relinks = new Gee.HashSet<DataSource>(); + + notify_contents_altered(null, copy); + + sources.relink_many(copy); + } + + public string to_string() { + return "SourceHoldingTank @ 0x%p".printf(this); + } +} + |