~ubuntu-branches/ubuntu/raring/banshee/raring

« back to all changes in this revision

Viewing changes to .pc/0008-Revert-ArtworkManager-Cache-null-artwork-for-fast-lo.patch/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs

  • Committer: Package Import Robot
  • Author(s): Chow Loong Jin
  • Date: 2012-01-23 23:16:49 UTC
  • mfrom: (6.3.22 experimental)
  • Revision ID: package-import@ubuntu.com-20120123231649-safm1f8eycltcgsf
Tags: 2.3.4.ds-1ubuntu1
* Merge from Debian Experimental, remaining changes:
  + Enable and recommend SoundMenu and Disable NotificationArea by default
  + Disable boo and karma extensions
  + Enable and suggest u1ms
  + Move desktop file for Meego UI to /usr/share/une/applications
  + Change the url for the Amazon store redirector
  + [08dea2c] Revert "Fix invalid cast causing ftbfs with libgpod"
* [b617fe0] Convert Ubuntu-specific patches to gbp-pq patches
* Also fixes Launchpad bugs:
  - Fixes race condition while starting (LP: #766303)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
// ArtworkManager.cs
 
3
//
 
4
// Author:
 
5
//   Aaron Bockover <abockover@novell.com>
 
6
//
 
7
// Copyright (C) 2007-2008 Novell, Inc.
 
8
//
 
9
// Permission is hereby granted, free of charge, to any person obtaining
 
10
// a copy of this software and associated documentation files (the
 
11
// "Software"), to deal in the Software without restriction, including
 
12
// without limitation the rights to use, copy, modify, merge, publish,
 
13
// distribute, sublicense, and/or sell copies of the Software, and to
 
14
// permit persons to whom the Software is furnished to do so, subject to
 
15
// the following conditions:
 
16
//
 
17
// The above copyright notice and this permission notice shall be
 
18
// included in all copies or substantial portions of the Software.
 
19
//
 
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
27
//
 
28
 
 
29
using System;
 
30
using System.Collections.Generic;
 
31
using System.Text.RegularExpressions;
 
32
 
 
33
using Mono.Unix;
 
34
 
 
35
using Gdk;
 
36
 
 
37
using Hyena;
 
38
using Hyena.Gui;
 
39
using Hyena.Collections;
 
40
using Hyena.Data.Sqlite;
 
41
 
 
42
using Banshee.Base;
 
43
using Banshee.IO;
 
44
using Banshee.Configuration;
 
45
using Banshee.ServiceStack;
 
46
 
 
47
namespace Banshee.Collection.Gui
 
48
{
 
49
    public class ArtworkManager : IService
 
50
    {
 
51
        private Dictionary<int, SurfaceCache> scale_caches  = new Dictionary<int, SurfaceCache> ();
 
52
        private HashSet<int> cacheable_cover_sizes = new HashSet<int> ();
 
53
        private HashSet<string> null_artwork_ids = new HashSet<string> ();
 
54
 
 
55
        private class SurfaceCache : LruCache<string, Cairo.ImageSurface>
 
56
        {
 
57
            public SurfaceCache (int max_items) : base (max_items)
 
58
            {
 
59
            }
 
60
 
 
61
            protected override void ExpireItem (Cairo.ImageSurface item)
 
62
            {
 
63
                if (item != null) {
 
64
                    ((IDisposable)item).Dispose ();
 
65
                }
 
66
            }
 
67
        }
 
68
 
 
69
        public ArtworkManager ()
 
70
        {
 
71
            AddCachedSize (36);
 
72
            AddCachedSize (40);
 
73
            AddCachedSize (42);
 
74
            AddCachedSize (48);
 
75
            AddCachedSize (64);
 
76
            AddCachedSize (90);
 
77
            AddCachedSize (300);
 
78
 
 
79
            try {
 
80
                MigrateCacheDir ();
 
81
            } catch (Exception e) {
 
82
                Log.Exception ("Could not migrate album artwork cache directory", e);
 
83
            }
 
84
 
 
85
            if (ApplicationContext.CommandLine.Contains ("fetch-artwork")) {
 
86
                ResetScanResultCache ();
 
87
            }
 
88
 
 
89
            Banshee.Metadata.MetadataService.Instance.ArtworkUpdated += OnArtworkUpdated;
 
90
        }
 
91
 
 
92
        public void Dispose ()
 
93
        {
 
94
            Banshee.Metadata.MetadataService.Instance.ArtworkUpdated -= OnArtworkUpdated;
 
95
        }
 
96
 
 
97
        private void OnArtworkUpdated (IBasicTrackInfo track)
 
98
        {
 
99
            ClearCacheFor (track.ArtworkId, true);
 
100
        }
 
101
 
 
102
        public Cairo.ImageSurface LookupSurface (string id)
 
103
        {
 
104
            return LookupScaleSurface (id, 0);
 
105
        }
 
106
 
 
107
        public Cairo.ImageSurface LookupScaleSurface (string id, int size)
 
108
        {
 
109
            return LookupScaleSurface (id, size, false);
 
110
        }
 
111
 
 
112
        public Cairo.ImageSurface LookupScaleSurface (string id, int size, bool useCache)
 
113
        {
 
114
            SurfaceCache cache = null;
 
115
            Cairo.ImageSurface surface = null;
 
116
 
 
117
            if (id == null) {
 
118
                return null;
 
119
            }
 
120
 
 
121
            if (useCache && scale_caches.TryGetValue (size, out cache) && cache.TryGetValue (id, out surface)) {
 
122
                return surface;
 
123
            }
 
124
 
 
125
            if (null_artwork_ids.Contains (id)) {
 
126
                return null;
 
127
            }
 
128
 
 
129
            Pixbuf pixbuf = LookupScalePixbuf (id, size);
 
130
            if (pixbuf == null || pixbuf.Handle == IntPtr.Zero) {
 
131
                null_artwork_ids.Add (id);
 
132
                return null;
 
133
            }
 
134
 
 
135
            try {
 
136
                surface = PixbufImageSurface.Create (pixbuf);
 
137
                if (surface == null) {
 
138
                    return null;
 
139
                }
 
140
 
 
141
                if (!useCache) {
 
142
                    return surface;
 
143
                }
 
144
 
 
145
                if (cache == null) {
 
146
                    int bytes = 4 * size * size;
 
147
                    int max = (1 << 20) / bytes;
 
148
 
 
149
                    ChangeCacheSize (size, max);
 
150
                    cache = scale_caches[size];
 
151
                }
 
152
 
 
153
                cache.Add (id, surface);
 
154
                return surface;
 
155
            } finally {
 
156
                DisposePixbuf (pixbuf);
 
157
            }
 
158
        }
 
159
 
 
160
        public Pixbuf LookupPixbuf (string id)
 
161
        {
 
162
            return LookupScalePixbuf (id, 0);
 
163
        }
 
164
 
 
165
        public Pixbuf LookupScalePixbuf (string id, int size)
 
166
        {
 
167
            if (id == null || (size != 0 && size < 10)) {
 
168
                return null;
 
169
            }
 
170
 
 
171
            if (null_artwork_ids.Contains (id)) {
 
172
                return null;
 
173
            }
 
174
 
 
175
            // Find the scaled, cached file
 
176
            string path = CoverArtSpec.GetPathForSize (id, size);
 
177
            if (File.Exists (new SafeUri (path))) {
 
178
                try {
 
179
                    return new Pixbuf (path);
 
180
                } catch {
 
181
                    null_artwork_ids.Add (id);
 
182
                    return null;
 
183
                }
 
184
            }
 
185
 
 
186
            string orig_path = CoverArtSpec.GetPathForSize (id, 0);
 
187
            bool orig_exists = File.Exists (new SafeUri (orig_path));
 
188
 
 
189
            if (!orig_exists) {
 
190
                // It's possible there is an image with extension .cover that's waiting
 
191
                // to be converted into a jpeg
 
192
                string unconverted_path = System.IO.Path.ChangeExtension (orig_path, "cover");
 
193
                if (File.Exists (new SafeUri (unconverted_path))) {
 
194
                    try {
 
195
                        Pixbuf pixbuf = new Pixbuf (unconverted_path);
 
196
                        if (pixbuf.Width < 50 || pixbuf.Height < 50) {
 
197
                            Hyena.Log.DebugFormat ("Ignoring cover art {0} because less than 50x50", unconverted_path);
 
198
                            null_artwork_ids.Add (id);
 
199
                            return null;
 
200
                        }
 
201
 
 
202
                        pixbuf.Save (orig_path, "jpeg");
 
203
                        orig_exists = true;
 
204
                    } catch {
 
205
                    } finally {
 
206
                        File.Delete (new SafeUri (unconverted_path));
 
207
                    }
 
208
                }
 
209
            }
 
210
 
 
211
            if (orig_exists && size >= 10) {
 
212
                try {
 
213
                    Pixbuf pixbuf = new Pixbuf (orig_path);
 
214
 
 
215
                    // Make it square if width and height difference is within 20%
 
216
                    const double max_ratio = 1.2;
 
217
                    double ratio = (double)pixbuf.Height / pixbuf.Width;
 
218
                    int width = size, height = size;
 
219
                    if (ratio > max_ratio) {
 
220
                        width = (int)Math.Round (size / ratio);
 
221
                    }else if (ratio < 1d / max_ratio) {
 
222
                        height = (int)Math.Round (size * ratio);
 
223
                    }
 
224
 
 
225
                    Pixbuf scaled_pixbuf = pixbuf.ScaleSimple (width, height, Gdk.InterpType.Bilinear);
 
226
 
 
227
                    if (IsCachedSize (size)) {
 
228
                        Directory.Create (System.IO.Path.GetDirectoryName (path));
 
229
                        scaled_pixbuf.Save (path, "jpeg");
 
230
                    } else {
 
231
                        Log.InformationFormat ("Uncached artwork size {0} requested", size);
 
232
                    }
 
233
 
 
234
                    DisposePixbuf (pixbuf);
 
235
                    return scaled_pixbuf;
 
236
                } catch {}
 
237
            }
 
238
 
 
239
            null_artwork_ids.Add (id);
 
240
            return null;
 
241
        }
 
242
 
 
243
        public void ClearCacheFor (string id)
 
244
        {
 
245
            ClearCacheFor (id, false);
 
246
        }
 
247
 
 
248
        public void ClearCacheFor (string id, bool inMemoryCacheOnly)
 
249
        {
 
250
            if (String.IsNullOrEmpty (id)) {
 
251
                return;
 
252
            }
 
253
 
 
254
            // Clear from the in-memory cache
 
255
            foreach (int size in scale_caches.Keys) {
 
256
                scale_caches[size].Remove (id);
 
257
            }
 
258
 
 
259
            null_artwork_ids.Remove (id);
 
260
 
 
261
            if (inMemoryCacheOnly) {
 
262
                return;
 
263
            }
 
264
 
 
265
            // And delete from disk
 
266
            foreach (int size in CachedSizes ()) {
 
267
                var uri = new SafeUri (CoverArtSpec.GetPathForSize (id, size));
 
268
                if (File.Exists (uri)) {
 
269
                    File.Delete (uri);
 
270
                }
 
271
            }
 
272
        }
 
273
 
 
274
        public void AddCachedSize (int size)
 
275
        {
 
276
            cacheable_cover_sizes.Add (size);
 
277
        }
 
278
 
 
279
        public bool IsCachedSize (int size)
 
280
        {
 
281
            return cacheable_cover_sizes.Contains (size);
 
282
        }
 
283
 
 
284
        public IEnumerable<int> CachedSizes ()
 
285
        {
 
286
            return cacheable_cover_sizes;
 
287
        }
 
288
 
 
289
        public void ChangeCacheSize (int size, int max_count)
 
290
        {
 
291
            SurfaceCache cache;
 
292
            if (scale_caches.TryGetValue (size, out cache)) {
 
293
                if (max_count > cache.MaxCount) {
 
294
                    Log.DebugFormat (
 
295
                        "Growing surface cache for {0}px images to {1:0.00} MiB ({2} items)",
 
296
                        size, 4 * size * size * max_count / 1048576d, max_count);
 
297
                    cache.MaxCount = max_count;
 
298
                }
 
299
            } else {
 
300
                Log.DebugFormat (
 
301
                    "Creating new surface cache for {0}px images, capped at {1:0.00} MiB ({2} items)",
 
302
                    size, 4 * size * size * max_count / 1048576d, max_count);
 
303
                scale_caches.Add (size, new SurfaceCache (max_count));
 
304
            }
 
305
        }
 
306
 
 
307
        private static int dispose_count = 0;
 
308
        public static void DisposePixbuf (Pixbuf pixbuf)
 
309
        {
 
310
            if (pixbuf != null && pixbuf.Handle != IntPtr.Zero) {
 
311
                pixbuf.Dispose ();
 
312
                pixbuf = null;
 
313
 
 
314
                // There is an issue with disposing Pixbufs where we need to explicitly
 
315
                // call the GC otherwise it doesn't get done in a timely way.  But if we
 
316
                // do it every time, it slows things down a lot; so only do it every 100th.
 
317
                if (++dispose_count % 100 == 0) {
 
318
                    System.GC.Collect ();
 
319
                    dispose_count = 0;
 
320
                }
 
321
            }
 
322
        }
 
323
 
 
324
        string IService.ServiceName {
 
325
            get { return "ArtworkManager"; }
 
326
        }
 
327
 
 
328
#region Cache Directory Versioning/Migration
 
329
 
 
330
        private const int CUR_VERSION = 3;
 
331
        private void MigrateCacheDir ()
 
332
        {
 
333
            int version = CacheVersion;
 
334
            if (version == CUR_VERSION) {
 
335
                return;
 
336
            }
 
337
 
 
338
            var legacy_root_path = CoverArtSpec.LegacyRootPath;
 
339
 
 
340
            if (version < 1) {
 
341
                string legacy_artwork_path = Paths.Combine (LegacyPaths.ApplicationData, "covers");
 
342
 
 
343
                if (!Directory.Exists (legacy_root_path)) {
 
344
                    Directory.Create (legacy_root_path);
 
345
 
 
346
                    if (Directory.Exists (legacy_artwork_path)) {
 
347
                        Directory.Move (new SafeUri (legacy_artwork_path), new SafeUri (legacy_root_path));
 
348
                    }
 
349
                }
 
350
 
 
351
                if (Directory.Exists (legacy_artwork_path)) {
 
352
                    Log.InformationFormat ("Deleting old (Banshee < 1.0) artwork cache directory {0}", legacy_artwork_path);
 
353
                    Directory.Delete (legacy_artwork_path, true);
 
354
                }
 
355
            }
 
356
 
 
357
            if (version < 2) {
 
358
                int deleted = 0;
 
359
                foreach (string dir in Directory.GetDirectories (legacy_root_path)) {
 
360
                    int size;
 
361
                    string dirname = System.IO.Path.GetFileName (dir);
 
362
                    if (Int32.TryParse (dirname, out size) && !IsCachedSize (size)) {
 
363
                        Directory.Delete (dir, true);
 
364
                        deleted++;
 
365
                    }
 
366
                }
 
367
 
 
368
                if (deleted > 0) {
 
369
                    Log.InformationFormat ("Deleted {0} extraneous album-art cache directories", deleted);
 
370
                }
 
371
            }
 
372
 
 
373
            if (version < 3) {
 
374
                Log.Information ("Migrating album-art cache directory");
 
375
                var started = DateTime.Now;
 
376
                int count = 0;
 
377
 
 
378
                var root_path = CoverArtSpec.RootPath;
 
379
                if (!Directory.Exists (root_path)) {
 
380
                    Directory.Create (root_path);
 
381
                }
 
382
 
 
383
                string sql = "SELECT Title, ArtistName FROM CoreAlbums";
 
384
                using (var reader = new HyenaDataReader (ServiceManager.DbConnection.Query (sql))) {
 
385
                    while (reader.Read ()) {
 
386
                        var album = reader.Get<string>(0);
 
387
                        var artist = reader.Get<string>(1);
 
388
                        var old_file = CoverArtSpec.CreateLegacyArtistAlbumId (artist, album);
 
389
                        var new_file = CoverArtSpec.CreateArtistAlbumId (artist, album);
 
390
 
 
391
                        if (String.IsNullOrEmpty (old_file) || String.IsNullOrEmpty (new_file)) {
 
392
                            continue;
 
393
                        }
 
394
 
 
395
                        old_file = String.Format ("{0}.jpg", old_file);
 
396
                        new_file = String.Format ("{0}.jpg", new_file);
 
397
 
 
398
                        var old_path = new SafeUri (Paths.Combine (legacy_root_path, old_file));
 
399
                        var new_path = new SafeUri (Paths.Combine (root_path, new_file));
 
400
 
 
401
                        if (Banshee.IO.File.Exists (old_path) && !Banshee.IO.File.Exists (new_path)) {
 
402
                            Banshee.IO.File.Move (old_path, new_path);
 
403
                            count++;
 
404
                        }
 
405
                    }
 
406
                }
 
407
 
 
408
                if (ServiceManager.DbConnection.TableExists ("PodcastSyndications")) {
 
409
                    sql = "SELECT Title FROM PodcastSyndications";
 
410
                    foreach (var title in ServiceManager.DbConnection.QueryEnumerable<string> (sql)) {
 
411
                        var old_digest = CoverArtSpec.LegacyEscapePart (title);
 
412
                        var new_digest = CoverArtSpec.Digest (title);
 
413
 
 
414
                        if (String.IsNullOrEmpty (old_digest) || String.IsNullOrEmpty (new_digest)) {
 
415
                            continue;
 
416
                        }
 
417
 
 
418
                        var old_file = String.Format ("podcast-{0}.jpg", old_digest);
 
419
                        var new_file = String.Format ("podcast-{0}.jpg", new_digest);
 
420
 
 
421
                        var old_path = new SafeUri (Paths.Combine (legacy_root_path, old_file));
 
422
                        var new_path = new SafeUri (Paths.Combine (root_path, new_file));
 
423
 
 
424
                        if (Banshee.IO.File.Exists (old_path) && !Banshee.IO.File.Exists (new_path)) {
 
425
                            Banshee.IO.File.Move (old_path, new_path);
 
426
                            count++;
 
427
                        }
 
428
                    }
 
429
                }
 
430
 
 
431
                if (count == 0) {
 
432
                    ResetScanResultCache ();
 
433
                }
 
434
 
 
435
                Directory.Delete (legacy_root_path, true);
 
436
                Log.InformationFormat ("Migrated {0} files in {1}s", count, DateTime.Now.Subtract(started).TotalSeconds);
 
437
            }
 
438
 
 
439
            CacheVersion = CUR_VERSION;
 
440
        }
 
441
 
 
442
        private void ResetScanResultCache ()
 
443
        {
 
444
            try {
 
445
                ServiceManager.DbConnection.Execute ("DELETE FROM CoverArtDownloads");
 
446
                DatabaseConfigurationClient.Client.Set<DateTime> ("last_cover_art_scan", DateTime.MinValue);
 
447
                Log.InformationFormat ("Reset CoverArtDownloads table so missing artwork will get fetched");
 
448
            } catch {}
 
449
        }
 
450
 
 
451
        private static SafeUri cache_version_file = new SafeUri (Paths.Combine (CoverArtSpec.RootPath, ".cache_version"));
 
452
        private static int CacheVersion {
 
453
            get {
 
454
                var file = cache_version_file;
 
455
                if (!Banshee.IO.File.Exists (file)) {
 
456
                    file = new SafeUri (Paths.Combine (CoverArtSpec.LegacyRootPath, ".cache_version"));
 
457
                    if (!Banshee.IO.File.Exists (file)) {
 
458
                        file = null;
 
459
                    }
 
460
                }
 
461
 
 
462
                if (file != null) {
 
463
                    using (var reader = new System.IO.StreamReader (Banshee.IO.File.OpenRead (file))) {
 
464
                        int version;
 
465
                        if (Int32.TryParse (reader.ReadLine (), out version)) {
 
466
                            return version;
 
467
                        }
 
468
                    }
 
469
                }
 
470
 
 
471
                return 0;
 
472
            }
 
473
            set {
 
474
                using (var writer = new System.IO.StreamWriter (Banshee.IO.File.OpenWrite (cache_version_file, true))) {
 
475
                    writer.Write (value.ToString ());
 
476
                }
 
477
            }
 
478
        }
 
479
 
 
480
#endregion
 
481
 
 
482
    }
 
483
}