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