1.1 --- a/WebStack/Repositories/Directory.py Sun Nov 20 21:18:09 2005 +0000
1.2 +++ b/WebStack/Repositories/Directory.py Sun Nov 20 21:19:02 2005 +0000
1.3 @@ -26,6 +26,8 @@
1.4
1.5 "A directory repository providing session-like access to files."
1.6
1.7 + new_filename = "__new__"
1.8 +
1.9 def __init__(self, path, fsencoding=None, delay=1):
1.10
1.11 """
1.12 @@ -43,11 +45,18 @@
1.13 for editing.
1.14 """
1.15
1.16 + # Convert the path to an absolute path.
1.17 +
1.18 + self.path = os.path.abspath(path)
1.19 + self.fsencoding = fsencoding
1.20 + self.delay = delay
1.21 +
1.22 + # Create a directory and initialise it with a special file.
1.23 +
1.24 if not os.path.exists(path):
1.25 os.mkdir(path)
1.26 - self.path = path
1.27 - self.fsencoding = fsencoding
1.28 - self.delay = delay
1.29 + f = open(self.full_path(self.new_filename), "wb")
1.30 + f.close()
1.31
1.32 # Guess the filesystem encoding.
1.33
1.34 @@ -64,18 +73,30 @@
1.35 self.fsencoding = fsencoding
1.36
1.37 def _convert_name(self, name):
1.38 +
1.39 + "Convert the given 'name' to a plain string in the filesystem encoding."
1.40 +
1.41 if self.fsencoding:
1.42 return name.encode(self.fsencoding)
1.43 else:
1.44 return name
1.45
1.46 def _convert_fsname(self, name):
1.47 +
1.48 + """
1.49 + Convert the given 'name' as a plain string in the filesystem encoding to
1.50 + a Unicode object.
1.51 + """
1.52 +
1.53 if self.fsencoding:
1.54 return unicode(name, self.fsencoding)
1.55 else:
1.56 return name
1.57
1.58 def keys(self):
1.59 +
1.60 + "Return the names of the files stored in the repository."
1.61 +
1.62 # NOTE: Special names converted using a simple rule.
1.63 l = []
1.64 for name in os.listdir(self.path):
1.65 @@ -85,31 +106,65 @@
1.66 l.append(name)
1.67 return map(self._convert_fsname, l)
1.68
1.69 - def full_path(self, key):
1.70 + def full_path(self, key, edit=0):
1.71 +
1.72 + """
1.73 + Return the full path to the 'key' in the filesystem. If the optional
1.74 + 'edit' parameter is set to a true value, the returned path will refer to
1.75 + the editable version of the file.
1.76 + """
1.77 +
1.78 + # NOTE: Special names converted using a simple rule.
1.79 path = os.path.abspath(os.path.join(self.path, self._convert_name(key)))
1.80 + if edit:
1.81 + path = path + ".edit"
1.82 if not path.startswith(self.path):
1.83 raise ValueError, key
1.84 else:
1.85 return path
1.86
1.87 + def edit_path(self, key):
1.88 +
1.89 + """
1.90 + Return the full path to the 'key' in the filesystem provided that the
1.91 + file associated with the 'key' is locked for editing.
1.92 + """
1.93 +
1.94 + return self.full_path(key, edit=1)
1.95 +
1.96 def has_key(self, key):
1.97 +
1.98 + """
1.99 + Return whether a file with the name specified by 'key is stored in the
1.100 + repository.
1.101 + """
1.102 +
1.103 return key in self.keys()
1.104
1.105 # NOTE: Methods very similar to Helpers.Session.Wrapper.
1.106
1.107 def items(self):
1.108 +
1.109 + """
1.110 + Return a list of (name, value) tuples for the files stored in the
1.111 + repository.
1.112 + """
1.113 +
1.114 results = []
1.115 for key in self.keys():
1.116 results.append((key, self[key]))
1.117 return results
1.118
1.119 def values(self):
1.120 +
1.121 + "Return the contents of the files stored in the repository."
1.122 +
1.123 results = []
1.124 for key in self.keys():
1.125 results.append(self[key])
1.126 return results
1.127
1.128 - def lock(self, key, create=0):
1.129 + def lock(self, key, create=0, opener=None):
1.130
1.131 """
1.132 Lock the file associated with the given 'key'. If the optional 'create'
1.133 @@ -117,24 +172,47 @@
1.134 created if it did not already exist; otherwise, a KeyError will be
1.135 raised.
1.136
1.137 - Return the full path to the file.
1.138 + If the optional 'opener' parameter is specified, it will be used to
1.139 + create any new file in the case where 'create' is set to a true value;
1.140 + otherwise, the standard 'open' function will be used to create the file.
1.141 +
1.142 + Return the full path to the editable file.
1.143 """
1.144
1.145 - filename = self.full_path(key)
1.146 - if os.path.exists(filename) or os.path.exists(filename + ".edit"):
1.147 + path = self.full_path(key)
1.148 + edit_path = self.edit_path(key)
1.149 +
1.150 + # Attempt to lock the file by renaming it.
1.151 + # NOTE: This assumes that renaming is an atomic operation.
1.152 +
1.153 + if os.path.exists(path) or os.path.exists(edit_path):
1.154 while 1:
1.155 try:
1.156 - os.rename(filename, filename + ".edit")
1.157 + os.rename(path, edit_path)
1.158 except OSError:
1.159 time.sleep(self.delay)
1.160 else:
1.161 break
1.162 +
1.163 + # Where a file does not exist, attempt to create a new file.
1.164 + # Since file creation is probably not atomic, we use the renaming of a
1.165 + # special file in an attempt to impose some kind of atomic "bottleneck".
1.166 +
1.167 elif create:
1.168 - f = open(filename + ".edit", "wb")
1.169 - f.close()
1.170 + self.lock(self.new_filename)
1.171 + try:
1.172 + if opener is None:
1.173 + f = open(edit_path, "wb")
1.174 + f.close()
1.175 + else:
1.176 + f = opener(edit_path)
1.177 + f.close()
1.178 + finally:
1.179 + self.unlock(self.new_filename)
1.180 else:
1.181 raise KeyError, key
1.182 - return filename
1.183 +
1.184 + return edit_path
1.185
1.186 def unlock(self, key):
1.187
1.188 @@ -146,15 +224,20 @@
1.189 because an unhandled exception was raised.
1.190 """
1.191
1.192 - filename = self.full_path(key)
1.193 - os.rename(filename + ".edit", filename)
1.194 + path = self.full_path(key)
1.195 + edit_path = self.edit_path(key)
1.196 + os.rename(edit_path, path)
1.197
1.198 def __delitem__(self, key):
1.199 - filename = self.full_path(key)
1.200 - if os.path.exists(filename) or os.path.exists(filename + ".edit"):
1.201 +
1.202 + "Remove the file associated with the given 'key'."
1.203 +
1.204 + path = self.full_path(key)
1.205 + edit_path = self.edit_path(key)
1.206 + if os.path.exists(path) or os.path.exists(edit_path):
1.207 while 1:
1.208 try:
1.209 - os.remove(filename)
1.210 + os.remove(path)
1.211 except OSError:
1.212 time.sleep(self.delay)
1.213 else:
1.214 @@ -163,9 +246,12 @@
1.215 raise KeyError, key
1.216
1.217 def __getitem__(self, key):
1.218 - filename = self.lock(key, create=0)
1.219 +
1.220 + "Return the contents of the file associated with the given 'key'."
1.221 +
1.222 + edit_path = self.lock(key, create=0)
1.223 try:
1.224 - f = open(filename + ".edit", "rb")
1.225 + f = open(edit_path, "rb")
1.226 s = ""
1.227 try:
1.228 s = f.read()
1.229 @@ -177,9 +263,15 @@
1.230 return s
1.231
1.232 def __setitem__(self, key, value):
1.233 - filename = self.lock(key, create=1)
1.234 +
1.235 + """
1.236 + Set the contents of the file associated with the given 'key' using the
1.237 + given 'value'.
1.238 + """
1.239 +
1.240 + edit_path = self.lock(key, create=1)
1.241 try:
1.242 - f = open(filename + ".edit", "wb")
1.243 + f = open(edit_path, "wb")
1.244 try:
1.245 f.write(value)
1.246 finally: