|
11 | 11 |
|
12 | 12 | from gitdb.util import ( |
13 | 13 | stream_copy, |
14 | | - make_sha |
| 14 | + make_sha, |
| 15 | + FDStreamWrapper, |
| 16 | + LockedFD |
15 | 17 | ) |
16 | 18 |
|
17 | 19 |
|
@@ -271,146 +273,6 @@ def _obtain_lock(self): |
271 | 273 | break |
272 | 274 | # END endless loop |
273 | 275 |
|
274 | | - |
275 | | -class FDStreamWrapper(object): |
276 | | - """A simple wrapper providing the most basic functions on a file descriptor |
277 | | - with the fileobject interface. Cannot use os.fdopen as the resulting stream |
278 | | - takes ownership""" |
279 | | - __slots__ = ("_fd", '_pos') |
280 | | - def __init__(self, fd): |
281 | | - self._fd = fd |
282 | | - self._pos = 0 |
283 | | - |
284 | | - def write(self, data): |
285 | | - self._pos += len(data) |
286 | | - os.write(self._fd, data) |
287 | | - |
288 | | - def read(self, count=0): |
289 | | - if count == 0: |
290 | | - count = os.path.getsize(self._filepath) |
291 | | - # END handle read everything |
292 | | - |
293 | | - bytes = os.read(self._fd, count) |
294 | | - self._pos += len(bytes) |
295 | | - return bytes |
296 | | - |
297 | | - def fileno(self): |
298 | | - return self._fd |
299 | | - |
300 | | - def tell(self): |
301 | | - return self._pos |
302 | | - |
303 | | - |
304 | | -class LockedFD(LockFile): |
305 | | - """This class facilitates a safe read and write operation to a file on disk. |
306 | | - If we write to 'file', we obtain a lock file at 'file.lock' and write to |
307 | | - that instead. If we succeed, the lock file will be renamed to overwrite |
308 | | - the original file. |
309 | | - |
310 | | - When reading, we obtain a lock file, but to prevent other writers from |
311 | | - succeeding while we are reading the file. |
312 | | - |
313 | | - This type handles error correctly in that it will assure a consistent state |
314 | | - on destruction. |
315 | | - |
316 | | - :note: with this setup, parallel reading is not possible""" |
317 | | - __slots__ = ("_filepath", '_fd', '_write') |
318 | | - |
319 | | - def __init__(self, filepath): |
320 | | - """Initialize an instance with the givne filepath""" |
321 | | - self._filepath = filepath |
322 | | - self._fd = None |
323 | | - self._write = None # if True, we write a file |
324 | | - |
325 | | - def __del__(self): |
326 | | - # will do nothing if the file descriptor is already closed |
327 | | - if self._fd is not None: |
328 | | - self.rollback() |
329 | | - |
330 | | - def _lockfilepath(self): |
331 | | - return "%s.lock" % self._filepath |
332 | | - |
333 | | - def open(self, write=False, stream=False): |
334 | | - """Open the file descriptor for reading or writing, both in binary mode. |
335 | | - :param write: if True, the file descriptor will be opened for writing. Other |
336 | | - wise it will be opened read-only. |
337 | | - :param stream: if True, the file descriptor will be wrapped into a simple stream |
338 | | - object which supports only reading or writing |
339 | | - :return: fd to read from or write to. It is still maintained by this instance |
340 | | - and must not be closed directly |
341 | | - :raise IOError: if the lock could not be retrieved |
342 | | - :raise OSError: If the actual file could not be opened for reading |
343 | | - :note: must only be called once""" |
344 | | - if self._write is not None: |
345 | | - raise AssertionError("Called %s multiple times" % self.open) |
346 | | - |
347 | | - self._write = write |
348 | | - |
349 | | - # try to open the lock file |
350 | | - binary = getattr(os, 'O_BINARY', 0) |
351 | | - lockmode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | binary |
352 | | - try: |
353 | | - fd = os.open(self._lockfilepath(), lockmode) |
354 | | - if not write: |
355 | | - os.close(fd) |
356 | | - else: |
357 | | - self._fd = fd |
358 | | - # END handle file descriptor |
359 | | - except OSError: |
360 | | - raise IOError("Lock at %r could not be obtained" % self._lockfilepath()) |
361 | | - # END handle lock retrieval |
362 | | - |
363 | | - # open actual file if required |
364 | | - if self._fd is None: |
365 | | - # we could specify exlusive here, as we obtained the lock anyway |
366 | | - self._fd = os.open(self._filepath, os.O_RDONLY | binary) |
367 | | - # END open descriptor for reading |
368 | | - |
369 | | - if stream: |
370 | | - return FDStreamWrapper(self._fd) |
371 | | - else: |
372 | | - return self._fd |
373 | | - # END handle stream |
374 | | - |
375 | | - def commit(self): |
376 | | - """When done writing, call this function to commit your changes into the |
377 | | - actual file. |
378 | | - The file descriptor will be closed, and the lockfile handled. |
379 | | - :note: can be called multiple times""" |
380 | | - self._end_writing(successful=True) |
381 | | - |
382 | | - def rollback(self): |
383 | | - """Abort your operation without any changes. The file descriptor will be |
384 | | - closed, and the lock released. |
385 | | - :note: can be called multiple times""" |
386 | | - self._end_writing(successful=False) |
387 | | - |
388 | | - def _end_writing(self, successful=True): |
389 | | - """Handle the lock according to the write mode """ |
390 | | - if self._write is None: |
391 | | - raise AssertionError("Cannot end operation if it wasn't started yet") |
392 | | - |
393 | | - if self._fd is None: |
394 | | - return |
395 | | - |
396 | | - os.close(self._fd) |
397 | | - self._fd = None |
398 | | - |
399 | | - lockfile = self._lockfilepath() |
400 | | - if self._write and successful: |
401 | | - # on windows, rename does not silently overwrite the existing one |
402 | | - if sys.platform == "win32": |
403 | | - if os.path.isfile(self._filepath): |
404 | | - os.remove(self._filepath) |
405 | | - # END remove if exists |
406 | | - # END win32 special handling |
407 | | - os.rename(lockfile, self._filepath) |
408 | | - else: |
409 | | - # just delete the file so far, we failed |
410 | | - os.remove(lockfile) |
411 | | - # END successful handling |
412 | | - |
413 | | - |
414 | 276 |
|
415 | 277 | class LazyMixin(object): |
416 | 278 | """ |
|
0 commit comments