1379 try: os.unlink(temp) |
1379 try: os.unlink(temp) |
1380 except OSError: pass |
1380 except OSError: pass |
1381 raise |
1381 raise |
1382 return temp |
1382 return temp |
1383 |
1383 |
|
1384 class filestat(object): |
|
1385 """help to exactly detect change of a file |
|
1386 |
|
1387 'stat' attribute is result of 'os.stat()' if specified 'path' |
|
1388 exists. Otherwise, it is None. This can avoid preparative |
|
1389 'exists()' examination on client side of this class. |
|
1390 """ |
|
1391 def __init__(self, path): |
|
1392 try: |
|
1393 self.stat = os.stat(path) |
|
1394 except OSError as err: |
|
1395 if err.errno != errno.ENOENT: |
|
1396 raise |
|
1397 self.stat = None |
|
1398 |
|
1399 __hash__ = object.__hash__ |
|
1400 |
|
1401 def __eq__(self, old): |
|
1402 try: |
|
1403 # if ambiguity between stat of new and old file is |
|
1404 # avoided, comparision of size, ctime and mtime is enough |
|
1405 # to exactly detect change of a file regardless of platform |
|
1406 return (self.stat.st_size == old.stat.st_size and |
|
1407 self.stat.st_ctime == old.stat.st_ctime and |
|
1408 self.stat.st_mtime == old.stat.st_mtime) |
|
1409 except AttributeError: |
|
1410 return False |
|
1411 |
|
1412 def isambig(self, old): |
|
1413 """Examine whether new (= self) stat is ambiguous against old one |
|
1414 |
|
1415 "S[N]" below means stat of a file at N-th change: |
|
1416 |
|
1417 - S[n-1].ctime < S[n].ctime: can detect change of a file |
|
1418 - S[n-1].ctime == S[n].ctime |
|
1419 - S[n-1].ctime < S[n].mtime: means natural advancing (*1) |
|
1420 - S[n-1].ctime == S[n].mtime: is ambiguous (*2) |
|
1421 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care) |
|
1422 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care) |
|
1423 |
|
1424 Case (*2) above means that a file was changed twice or more at |
|
1425 same time in sec (= S[n-1].ctime), and comparison of timestamp |
|
1426 is ambiguous. |
|
1427 |
|
1428 Base idea to avoid such ambiguity is "advance mtime 1 sec, if |
|
1429 timestamp is ambiguous". |
|
1430 |
|
1431 But advancing mtime only in case (*2) doesn't work as |
|
1432 expected, because naturally advanced S[n].mtime in case (*1) |
|
1433 might be equal to manually advanced S[n-1 or earlier].mtime. |
|
1434 |
|
1435 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be |
|
1436 treated as ambiguous regardless of mtime, to avoid overlooking |
|
1437 by confliction between such mtime. |
|
1438 |
|
1439 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime != |
|
1440 S[n].mtime", even if size of a file isn't changed. |
|
1441 """ |
|
1442 try: |
|
1443 return (self.stat.st_ctime == old.stat.st_ctime) |
|
1444 except AttributeError: |
|
1445 return False |
|
1446 |
1384 class atomictempfile(object): |
1447 class atomictempfile(object): |
1385 '''writable file object that atomically updates a file |
1448 '''writable file object that atomically updates a file |
1386 |
1449 |
1387 All writes will go to a temporary copy of the original file. Call |
1450 All writes will go to a temporary copy of the original file. Call |
1388 close() when you are done writing, and atomictempfile will rename |
1451 close() when you are done writing, and atomictempfile will rename |