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