summaryrefslogtreecommitdiff
path: root/src/core/SourceHoldingTank.vala
blob: 0c2083d24044066c11a2f77beb950b0bf3d0fc4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/* 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.
 */

// 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);
    }
}