1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - ItemSupport library 4 5 @copyright: 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin.util import lock 10 import os 11 12 # Content storage support. 13 14 class GeneralItemStore: 15 16 "Common item store functionality." 17 18 def __init__(self, lock_dir): 19 20 "Initialise an item store with the given 'lock_dir' guarding access." 21 22 self.lock_dir = lock_dir 23 self.writelock = lock.WriteLock(lock_dir) 24 self.readlock = lock.ReadLock(lock_dir) 25 26 def deduce_next(self): 27 28 "Deduce the next item number from the existing item files." 29 30 return max(self.get_keys() or [-1]) + 1 31 32 # High-level methods. 33 34 def __len__(self): 35 36 """ 37 Return the number of items. 38 """ 39 40 return len(self.keys()) 41 42 def __iter__(self): 43 44 "Return an iterator over the items in the store." 45 46 return ItemIterator(self) 47 48 def keys(self): 49 50 "Return a list of keys for items in the store." 51 52 self.readlock.acquire() 53 try: 54 return self.get_keys() 55 finally: 56 self.readlock.release() 57 58 def __getitem__(self, number): 59 60 "Return the item with the given 'number'." 61 62 self.readlock.acquire() 63 try: 64 try: 65 return self.read_item(number) 66 except (IOError, OSError): 67 raise IndexError, number 68 finally: 69 self.readlock.release() 70 71 def __delitem__(self, number): 72 73 "Remove the item with the given 'number' from the store." 74 75 self.writelock.acquire() 76 try: 77 try: 78 self.remove_item(number) 79 except (IOError, OSError): 80 raise IndexError, number 81 finally: 82 self.writelock.release() 83 84 def next(self): 85 86 """ 87 Return the number of the next item (which should also be the number of 88 items if none have been deleted). 89 """ 90 91 self.writelock.acquire() 92 try: 93 return self.get_next() 94 finally: 95 self.writelock.release() 96 97 class DirectoryItemStore(GeneralItemStore): 98 99 "A directory-based item store." 100 101 def __init__(self, path, lock_dir): 102 103 "Initialise an item store for the given 'path' and 'lock_dir'." 104 105 self.path = path 106 self.next_path = os.path.join(self.path, "next") 107 self.lock_dir = lock_dir 108 self.writelock = lock.WriteLock(lock_dir) 109 self.readlock = lock.ReadLock(lock_dir) 110 111 def mtime(self): 112 113 "Return the last modified time of the item store directory." 114 115 return os.path.getmtime(self.path) 116 117 def get_next(self): 118 119 "Return the next item number." 120 121 next = self.read_next() 122 if next is None: 123 next = self.deduce_next() 124 self.write_next(next) 125 return next 126 127 def get_keys(self): 128 129 "Return the item keys." 130 131 return [int(filename) for filename in os.listdir(self.path) if filename.isdigit()] 132 133 def read_next(self): 134 135 "Read the next item number from a special file." 136 137 if not os.path.exists(self.next_path): 138 return None 139 140 f = open(self.next_path) 141 try: 142 try: 143 return int(f.read()) 144 except ValueError: 145 return None 146 finally: 147 f.close() 148 149 def write_next(self, next): 150 151 "Write the 'next' item number to a special file." 152 153 f = open(self.next_path, "w") 154 try: 155 f.write(str(next)) 156 finally: 157 f.close() 158 159 def write_item(self, item, next): 160 161 "Write the given 'item' to a file with the given 'next' item number." 162 163 f = open(self.get_item_path(next), "w") 164 try: 165 f.write(item) 166 finally: 167 f.close() 168 169 def read_item(self, number): 170 171 "Read the item with the given item 'number'." 172 173 f = open(self.get_item_path(number)) 174 try: 175 return f.read() 176 finally: 177 f.close() 178 179 def remove_item(self, number): 180 181 "Remove the item with the given item 'number'." 182 183 os.remove(self.get_item_path(number)) 184 185 def get_item_path(self, number): 186 187 "Get the path for the given item 'number'." 188 189 path = os.path.abspath(os.path.join(self.path, str(number))) 190 basepath = os.path.join(self.path, "") 191 192 if os.path.commonprefix([path, basepath]) != basepath: 193 raise OSError, path 194 195 return path 196 197 # High-level methods. 198 199 def append(self, item): 200 201 "Append the given 'item' to the store." 202 203 self.writelock.acquire() 204 try: 205 next = self.get_next() 206 self.write_item(item, next) 207 self.write_next(next + 1) 208 finally: 209 self.writelock.release() 210 211 class ItemIterator: 212 213 "An iterator over items in a store." 214 215 def __init__(self, store, direction=1): 216 self.store = store 217 self.direction = direction 218 self.reset() 219 220 def reset(self): 221 if self.direction == 1: 222 self._next = 0 223 self.final = self.store.next() 224 else: 225 self._next = self.store.next() - 1 226 self.final = 0 227 228 def more(self): 229 if self.direction == 1: 230 return self._next < self.final 231 else: 232 return self._next >= self.final 233 234 def get_next(self): 235 next = self._next 236 self._next += self.direction 237 return next 238 239 def next(self): 240 while self.more(): 241 try: 242 return self.store[self.get_next()] 243 except IndexError: 244 pass 245 246 raise StopIteration 247 248 def reverse(self): 249 self.direction = -self.direction 250 self.reset() 251 252 def reversed(self): 253 self.reverse() 254 return self 255 256 def __iter__(self): 257 return self 258 259 # vim: tabstop=4 expandtab shiftwidth=4