# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 import ulib.base.i_need_py23 __all__ = ('SimpleStore',) import os, re from os import path from ulib.base.uio import _s, _u from ulib.base.lines import Lines from ulib.base.paths import mkdirof from ulib.base.tmpfiles import actemp, cltemp from ulib.base.flock import rdopen, wropen from base import * from consts import * from store import AbstractStore, UnsavedModificationError LASTID = 'lastid' class SimpleStore(AbstractStore): GENERIC_PROPERTIES = AbstractStore.GENERIC_PROPERTIES + [LASTID] def __init__(self, inf=None): AbstractStore.__init__(self) self.__reset() if inf is not None: self.load(inf) def __reset(self): self._tasks = [] self._lastid = 0 self._tids = {} _tasks, tasks, get_tasks = make_sprop('_tasks', None, setter=False, deleter=False) _lastid, lastid, get_lastid, set_lastid, del_lastid = make_suprop('_lastid', 0) _tids = None def _nextid(self): _u = self._uio.u nextid = int(self._lastid) + 1 while self._tids.has_key(_u(nextid)): nextid += 1 self._lastid = nextid return nextid def _ensure_tid(self, task): if task._tid is None: task.tid = self._nextid() def _task_attached(self, task, validate=False): self._ensure_tid(task) if not self._tids.has_key(task._tid): self._tids[task._tid] = task AbstractStore._task_attached(self, task, validate) def remove(self, task): if task not in self._tasks: return False del self._tids[task._tid] self._tasks.remove(task) self._modified = True return True _fdn = None _fd = None _fn = None def get_name(self): u"""Retourner le nom de ce store """ return _u(self._fn) name = property(get_name) RE_SPROPS = re.compile(r'##\s+sprops:') def load(self, infdn=None, force=False, **ignored): if self.is_modified() and not force: raise UnsavedModificationError if infdn is None: infdn = self._fdn if infdn is None: raise ValueError("Source file is required") else: self._fdn = path.abspath(infdn) self._fd, self._fn = path.split(self._fdn) self.__reset() self._valid = False lines = Lines() if path.exists(infdn): lines.readlines(infdn, open_func=rdopen) for line in lines: if self.RE_SPROPS.match(line) is not None: self.set_props(**self._line2props(line, False)) elif not line.strip() or line.startswith(u'#'): # ignorer lignes vides et commençant par "#" pass else: task = self._tf.build_task(**self._line2props(line)) self.tasks.append(task) self._task_attached(task, True) self._modified = False self._valid = True def __is_sprop(self, line): return self.RE_SPROPS.match(line) is not None def should_reload(self): infdn = self._fdn if infdn is None: return False if not path.exists(infdn): return False lines = Lines() lines.readlines(infdn, open_func=rdopen, until=self.__is_sprop) props = {} for line in lines: if self.RE_SPROPS.match(line) is not None: props = self._line2props(line, False) break return self.serial != props.get(SERIAL, None) ENCODING = u"""## -*- coding: %(encoding)s -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=%(encoding)s""" SPROP = u"""## sprops:""" def save(self, outfdn=None, force=False): if not force and not self.is_modified(): return False if outfdn is None: outfdn = self._fdn if outfdn is None: raise ValueError("Destination file is required") lines = self.get_lines(int(self.serial or 0) + 1) outf = wropen(outfdn) try: tmpfdn = outfdn + '.tmp' mkdirof(tmpfdn) li = actemp(tmpfdn) try: lines.writelines(tmpfdn) try: os.rename(tmpfdn, outfdn) except OSError: os.remove(outfdn) os.rename(tmpfdn, outfdn) finally: cltemp(li) finally: outf.close() for task in self._tasks: task.modified = False self._modified = False return True