2 * Copyright (C) 2011 Collabora Ltd.
4 * This library is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 2.1 of the License, or
7 * (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
18 * Philip Withnall <philip.withnall@collabora.co.uk>
24 * A singleton persistent cache object for avatars used across backends in
25 * folks. Avatars may be added to the cache, and referred to by a persistent
26 * URI from that point onwards.
30 public class Folks.AvatarCache : Object
32 private static weak AvatarCache? _instance = null; /* needs to be locked */
33 private File _cache_directory;
36 * Private constructor for an instance of the avatar cache. The singleton
37 * instance should be retrieved by calling {@link AvatarCache.dup()} instead.
41 private AvatarCache ()
48 this._cache_directory =
49 File.new_for_path (Environment.get_user_cache_dir ())
51 .get_child ("avatars");
55 * Create or return the singleton {@link AvatarCache} class instance.
56 * If the instance doesn't exist already, it will be created.
58 * This function is thread-safe.
60 * @return Singleton {@link AvatarCache} instance
63 public static AvatarCache dup ()
65 lock (AvatarCache._instance)
67 var _retval = AvatarCache._instance;
72 /* use an intermediate variable to force a strong reference */
73 retval = new AvatarCache ();
74 AvatarCache._instance = retval;
87 /* Manually clear the singleton _instance */
88 lock (AvatarCache._instance)
90 AvatarCache._instance = null;
95 * Fetch an avatar from the cache by its globally unique ID.
97 * @param id the globally unique ID for the avatar
98 * @return Avatar from the cache, or `null` if it doesn't exist in the cache
99 * @throws GLib.Error if checking for existence of the cache file failed
102 public async LoadableIcon? load_avatar (string id) throws GLib.Error
104 var avatar_file = this._get_avatar_file (id);
106 debug ("Loading avatar '%s' from file '%s'.", id, avatar_file.get_uri ());
108 // Return null if the avatar doesn't exist
109 if (avatar_file.query_exists () == false)
114 return new FileIcon (avatar_file);
118 * Store an avatar in the cache, assigning the given globally unique ID to it,
119 * which can later be used to load and remove the avatar from the cache. For
120 * example, this ID could be the UID of a persona. The URI of the cached
121 * avatar file will be returned.
123 * @param id the globally unique ID for the avatar
124 * @param avatar the avatar data to cache
125 * @return a URI for the file storing the cached avatar
126 * @throws GLib.Error if the avatar data couldn't be loaded, or if creating
127 * the avatar directory or cache file failed
130 public async string store_avatar (string id, LoadableIcon avatar)
133 var dest_avatar_file = this._get_avatar_file (id);
135 debug ("Storing avatar '%s' in file '%s'.", id,
136 dest_avatar_file.get_uri ());
138 InputStream src_avatar_stream =
139 yield avatar.load_async (-1, null, null);
141 // Copy the icon data into a file
144 OutputStream? dest_avatar_stream = null;
149 yield dest_avatar_file.replace_async (null, false,
150 FileCreateFlags.PRIVATE);
151 yield ((!) dest_avatar_stream).splice_async (src_avatar_stream,
152 OutputStreamSpliceFlags.NONE);
153 yield ((!) dest_avatar_stream).close_async ();
159 /* If the parent directory wasn't found, create it and loop
160 * round to try again. */
161 if (e is IOError.NOT_FOUND)
163 this._create_cache_directory ();
167 if (dest_avatar_stream != null)
169 yield ((!) dest_avatar_stream).close_async ();
176 yield src_avatar_stream.close_async ();
178 return this.build_uri_for_avatar (id);
182 * Remove an avatar from the cache, if it exists in the cache. If the avatar
183 * exists in the cache but there is a problem in removing it, a
184 * {@link GLib.Error} will be thrown.
186 * @param id the globally unique ID for the avatar
187 * @throws GLib.Error if deleting the cache file failed
190 public async void remove_avatar (string id) throws GLib.Error
192 var avatar_file = this._get_avatar_file (id);
194 debug ("Removing avatar '%s' in file '%s'.", id, avatar_file.get_uri ());
198 avatar_file.delete (null);
202 // Ignore file not found errors
203 if (!(e is IOError.NOT_FOUND))
211 * Build the URI of an avatar file in the cache from a globally unique ID.
212 * This will always succeed, even if the avatar doesn't exist in the cache.
214 * @param id the globally unique ID for the avatar
215 * @return URI of the avatar file with the given globally unique ID
218 public string build_uri_for_avatar (string id)
220 return this._get_avatar_file (id).get_uri ();
223 private File _get_avatar_file (string id)
225 var escaped_uri = Uri.escape_string (id, "", false);
226 var file = this._cache_directory.get_child (escaped_uri);
228 assert (file.has_parent (this._cache_directory) == true);
233 private void _create_cache_directory () throws GLib.Error
237 this._cache_directory.make_directory_with_parents ();
241 // Ignore errors caused by the directory existing already
242 if (!(e is IOError.EXISTS))