summaryrefslogtreecommitdiffstats
path: root/src/track/globaltrackcache.h
blob: 56a7a8a4cababcdf55502fba205ab2e6192cc993 (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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#pragma once


#include <map>
#include <unordered_map>

#include "track/track_decl.h"
#include "track/trackref.h"
#include "util/sandbox.h"

// forward declaration(s)
class GlobalTrackCache;

enum class GlobalTrackCacheLookupResult {
    None,
    Hit,
    Miss,
    ConflictCanonicalLocation
};

// Find the updated location of a track in the database when
// the canonical location is no longer valid or accessible.
class /*interface*/ GlobalTrackCacheRelocator {
private:
    friend class GlobalTrackCache;
    // Try to determine and return the relocated file info
    // or otherwise return just the provided file info.
    virtual TrackFile relocateCachedTrack(
            TrackId trackId,
            TrackFile fileInfo) = 0;

protected:
  virtual ~GlobalTrackCacheRelocator() = default;
};

typedef void (*deleteTrackFn_t)(Track*);

class GlobalTrackCacheEntry final {
    // We need to hold two shared pointers, the deletingPtr is
    // responsible for the lifetime of the Track object itself.
    // The second one counts the references outside Mixxx, if it
    // is not longer referenced, the track is saved and evicted
    // from the cache.
  public:
    class TrackDeleter {
    public:
        explicit TrackDeleter(deleteTrackFn_t deleteTrackFn = nullptr)
                : m_deleteTrackFn(deleteTrackFn) {
        }

        void operator()(Track* pTrack) const;

    private:
        deleteTrackFn_t m_deleteTrackFn;
    };

    explicit GlobalTrackCacheEntry(
            std::unique_ptr<Track, TrackDeleter> deletingPtr)
        : m_deletingPtr(std::move(deletingPtr)) {
    }
    GlobalTrackCacheEntry(const GlobalTrackCacheEntry& other) = delete;
    GlobalTrackCacheEntry(GlobalTrackCacheEntry&&) = default;

    void init(TrackWeakPointer savingWeakPtr) {
        // Uninitialized or expired
        DEBUG_ASSERT(!m_savingWeakPtr.lock());
        m_savingWeakPtr = std::move(savingWeakPtr);
    }

    Track* getPlainPtr() const {
        return m_deletingPtr.get();
    }

    TrackPointer lock() const {
        return m_savingWeakPtr.lock();
    }
    bool expired() const {
        return m_savingWeakPtr.expired();
    }

  private:
    std::unique_ptr<Track, TrackDeleter> m_deletingPtr;
    TrackWeakPointer m_savingWeakPtr;
};

typedef std::shared_ptr<GlobalTrackCacheEntry> GlobalTrackCacheEntryPointer;

class GlobalTrackCacheLocker {
public:
    GlobalTrackCacheLocker();
    GlobalTrackCacheLocker(const GlobalTrackCacheLocker&) = delete;
    GlobalTrackCacheLocker(GlobalTrackCacheLocker&&);
    virtual ~GlobalTrackCacheLocker();

    GlobalTrackCacheLocker& operator=(const GlobalTrackCacheLocker&) = delete;
    GlobalTrackCacheLocker& operator=(GlobalTrackCacheLocker&&) = delete;

    void unlockCache();

    void relocateCachedTracks(
            GlobalTrackCacheRelocator* /*nullable*/ pRelocator) const;

    void purgeTrackId(const TrackId& trackId);

    // Enforces the eviction of all cached tracks including invocation
    // of the callback and disables the cache permanently.
    void deactivateCache() const;

    bool isEmpty() const;

    // Lookup an existing Track object in the cache
    TrackPointer lookupTrackById(
            const TrackId& trackId) const;
    TrackPointer lookupTrackByRef(
            const TrackRef& trackRef) const;
    QSet<TrackId> getCachedTrackIds() const;

  private:
    friend class GlobalTrackCache;

    void lockCache();

protected:
    GlobalTrackCacheLocker(
            GlobalTrackCacheLocker&& moveable,
            GlobalTrackCacheLookupResult lookupResult,
            TrackPointer&& strongPtr,
            TrackRef&& trackRef);

    GlobalTrackCache* m_pInstance;
};

class GlobalTrackCacheResolver final: public GlobalTrackCacheLocker {
public:
    GlobalTrackCacheResolver(
                TrackFile fileInfo,
                SecurityTokenPointer pSecurityToken = SecurityTokenPointer());
    GlobalTrackCacheResolver(
                TrackFile fileInfo,
                TrackId trackId,
                SecurityTokenPointer pSecurityToken = SecurityTokenPointer());
    GlobalTrackCacheResolver(const GlobalTrackCacheResolver&) = delete;
    GlobalTrackCacheResolver(GlobalTrackCacheResolver&&) = default;

    GlobalTrackCacheLookupResult getLookupResult() const {
        return m_lookupResult;
    }

    const TrackPointer& getTrack() const {
        return m_strongPtr;
    }

    const TrackRef& getTrackRef() const {
        return m_trackRef;
    }

    void initTrackIdAndUnlockCache(TrackId trackId);

    GlobalTrackCacheResolver& operator=(const GlobalTrackCacheResolver&) = delete;
    GlobalTrackCacheResolver& operator=(GlobalTrackCacheResolver&&) = delete;

private:
    friend class GlobalTrackCache;
    GlobalTrackCacheResolver();

    void initLookupResult(
            GlobalTrackCacheLookupResult lookupResult,
            TrackPointer&& strongPtr,
            TrackRef&& trackRef);

    GlobalTrackCacheLookupResult m_lookupResult;

    TrackPointer m_strongPtr;

    TrackRef m_trackRef;
};

/// Callback interface for pre-delete actions
class /*interface*/ GlobalTrackCacheSaver {
private:
    friend class GlobalTrackCache;

    /// Perform actions that are necessary to save any pending
    /// modifications of a Track object before it finally gets
    /// deleted.
    ///
    /// GlobalTrackCache ensures that the given pointer is valid
    /// and the last and only reference to this Track object.
    /// While invoked the GlobalTrackCache is locked to ensure
    /// that this particular track is not accessible while
    /// saving the Track object, e.g. by updating the database
    /// and exporting file tags.
    ///
    /// This callback method will always be invoked from the
    /// event loop thread of the owning GlobalTrackCache instance.
    /// Typically the GlobalTrackCache lives on the main thread
    /// that also controls access to the database.
    /// NOTE(2020-06-06): If these assumptions about thread affinity
    /// are no longer valid the design decisions need to be revisited
    /// carefully!
    virtual void saveEvictedTrack(
            Track* pEvictedTrack) noexcept = 0;

  protected:
    virtual ~GlobalTrackCacheSaver() = default;
};

class GlobalTrackCache : public QObject {
    Q_OBJECT

  public:
    static void createInstance(
            GlobalTrackCacheSaver* pSaver,
            // A custom deleter is only needed for tests without an event loop!
            deleteTrackFn_t deleteTrackFn = nullptr);
    // NOTE(uklotzde, 2018-02-20): We decided not to destroy the singular
    // instance during shutdown, because we are not able to guarantee that
    // all track references have been released before. Instead the singular
    // instance is only deactivated. The following function has only been
    // preserved for completeness.
    // See also: GlobalTrackCacheLocker::deactivateCache()
    static void destroyInstance();

    // Deleter callbacks for the smart-pointer
    static void evictAndSaveCachedTrack(GlobalTrackCacheEntryPointer cacheEntryPtr);

  private slots:
    void slotEvictAndSave(GlobalTrackCacheEntryPointer cacheEntryPtr);

  private:
    friend class GlobalTrackCacheLocker;
    friend class GlobalTrackCacheResolver;

    GlobalTrackCache(
            GlobalTrackCacheSaver* pSaver,
            deleteTrackFn_t deleteTrackFn);
    ~GlobalTrackCache() override;

    void relocateTracks(
            GlobalTrackCacheRelocator* /*nullable*/ pRelocator);

    TrackPointer lookupById(
            const TrackId& trackId);
    TrackPointer lookupByCanonicalLocation(
            const QString& canonicalLocation);

    /// Lookup the track either by id (primary) or by
    /// canonical location (secondary). The id of the
    /// returned track might differ from the requested
    /// id due to file system aliasing!!
    TrackPointer lookupByRef(
            const TrackRef& trackRef);

    QSet<TrackId> getCachedTrackIds() const;

    TrackPointer revive(GlobalTrackCacheEntryPointer entryPtr);

    void resolve(
            GlobalTrackCacheResolver* /*in/out*/ pCacheResolver,
            TrackFile /*in*/ fileInfo,
            TrackId /*in*/ trackId,
            SecurityTokenPointer /*in*/ pSecurityToken);

    TrackRef initTrackId(
            const TrackPointer& strongPtr,
            const TrackRef& trackRef,
            TrackId trackId);

    void purgeTrackId(TrackId trackId);

    bool tryEvict(Track* plainPtr);
    bool isCached(Track* plainPtr) const;

    bool isEmpty() const;

    void deactivate();

    void saveEvictedTrack(Track* pEvictedTrack) const;

    // Managed by GlobalTrackCacheLocker
    mutable QMutex m_mutex;

    GlobalTrackCacheSaver* m_pSaver;

    deleteTrackFn_t m_deleteTrackFn;

    // This caches the unsaved Tracks by ID
    typedef std::unordered_map<TrackId, GlobalTrackCacheEntryPointer, TrackId::hash_fun_t> TracksById;
    TracksById m_tracksById;

    // This caches the unsaved Tracks by location
    typedef std::map<QString, GlobalTrackCacheEntryPointer> TracksByCanonicalLocation;
    TracksByCanonicalLocation m_tracksByCanonicalLocation;
};