1
2 '''Persistent token cache management for the Flickr API'''
3
4 import os.path
5 import logging
6 import time
7
8 from flickrapi.exceptions import LockingError
9
10 logging.basicConfig()
11 LOG = logging.getLogger(__name__)
12 LOG.setLevel(logging.INFO)
13
14 __all__ = ('TokenCache', 'SimpleTokenCache')
18 '''In-memory token cache.'''
19
22
24 '''Removes the cached token'''
25
26 self.token = None
27
30 '''On-disk persistent token cache for a single application.
31
32 The application is identified by the API key used. Per
33 application multiple users are supported, with a single
34 token per user.
35 '''
36
37 - def __init__(self, api_key, username=None):
38 '''Creates a new token cache instance'''
39
40 self.api_key = api_key
41 self.username = username
42 self.memory = {}
43 self.path = os.path.join("~", ".flickr")
44
46 """Return the directory holding the app data."""
47 return os.path.expanduser(os.path.join(self.path, self.api_key))
48
50 """Return the full pathname of the cached token file."""
51
52 if self.username:
53 filename = 'auth-%s.token' % self.username
54 else:
55 filename = 'auth.token'
56
57 return os.path.join(self.get_cached_token_path(), filename)
58
60 """Read and return a cached token, or None if not found.
61
62 The token is read from the cached token file.
63 """
64
65
66 if self.username in self.memory:
67 return self.memory[self.username]
68
69 try:
70 f = open(self.get_cached_token_filename(), "r")
71 token = f.read()
72 f.close()
73
74 return token.strip()
75 except IOError:
76 return None
77
91
93 '''Removes the cached token'''
94
95 if self.username in self.memory:
96 del self.memory[self.username]
97 filename = self.get_cached_token_filename()
98 if os.path.exists(filename):
99 os.unlink(filename)
100
101 token = property(get_cached_token, set_cached_token,
102 forget, "The cached token")
103
106 '''Locks the token cache when reading or updating it, so that
107 multiple processes can safely use the same API key.
108 '''
109
111 '''Returns the filename of the lock.'''
112
113 token_name = self.get_cached_token_filename()
114 return '%s-lock' % token_name
115 lock = property(get_lock_name)
116
118 '''Returns the name of the pidfile in the lock directory.'''
119
120 return os.path.join(self.lock, 'pid')
121 pidfile_name = property(get_pidfile_name)
122
124 '''Returns the PID that is stored in the lock directory, or
125 None if there is no such file.
126 '''
127
128 filename = self.pidfile_name
129 if not os.path.exists(filename):
130 return None
131
132 pidfile = open(filename)
133 try:
134 pid = pidfile.read()
135 if pid:
136 return int(pid)
137 finally:
138 pidfile.close()
139
140 return None
141
143 '''Locks the token cache for this key and username.
144
145 If the token cache is already locked, waits until it is
146 released. Throws an exception when the lock cannot be acquired
147 after ``timeout`` seconds.
148 '''
149
150
151
152 lockpid = self.get_lock_pid()
153 if lockpid == os.getpid():
154 LOG.debug('The lock is ours, continuing')
155 return
156
157
158 lock = self.get_lock_name()
159 LOG.debug('Acquiring lock %s' % lock)
160
161
162 start_time = time.time()
163 while True:
164 try:
165 os.makedirs(lock)
166 break
167 except OSError:
168
169
170
171 if not os.path.exists(lock):
172 LOG.error('Unable to acquire lock %s, aborting' %
173 lock)
174 raise
175
176 if time.time() - start_time >= timeout:
177
178 raise LockingError('Unable to acquire lock ' +
179 '%s, aborting' % lock)
180
181
182 LOG.debug('Unable to acquire lock, waiting')
183 time.sleep(0.1)
184
185
186 LOG.debug('Lock acquired, writing our PID')
187 pidfile = open(self.pidfile_name, 'w')
188 try:
189 pidfile.write('%s' % os.getpid())
190 finally:
191 pidfile.close()
192
194 '''Unlocks the token cache for this key.'''
195
196
197 lock = self.get_lock_name()
198 if not os.path.exists(lock):
199 LOG.warn('Trying to release non-existing lock %s' % lock)
200 return
201
202
203 lockpid = self.get_lock_pid()
204 if lockpid and lockpid != os.getpid():
205 raise LockingError(('Lock %s is NOT ours, but belongs ' +
206 'to PID %i, unable to release.') % (lock,
207 lockpid))
208
209 LOG.debug('Releasing lock %s' % lock)
210
211
212 pidfile = self.pidfile_name
213 if os.path.exists(pidfile):
214 os.remove(pidfile)
215 os.removedirs(lock)
216
218 '''Cleans up any existing lock.'''
219
220
221 lock = self.get_lock_name()
222 if not os.path.exists(lock):
223 return
224
225
226 lockpid = self.get_lock_pid()
227 if lockpid and lockpid != os.getpid():
228 return
229
230
231 self.release()
232
234 '''Decorator, ensures the method runs in a locked cache.'''
235
236 def locker(self, *args, **kwargs):
237 self.acquire()
238 try:
239 return method(self, *args, **kwargs)
240 finally:
241 self.release()
242
243 return locker
244
245 @locked
247 """Read and return a cached token, or None if not found.
248
249 The token is read from the cached token file.
250 """
251
252 return TokenCache.get_cached_token(self)
253
254 @locked
259
260 @locked
265
266 token = property(get_cached_token, set_cached_token,
267 forget, "The cached token")
268