Merge lp:~jderose/filestore/purge_tmp into lp:filestore

Proposed by Jason Gerard DeRose
Status: Merged
Approved by: James Raymond
Approved revision: 248
Merged at revision: 243
Proposed branch: lp:~jderose/filestore/purge_tmp
Merge into: lp:filestore
Diff against target: 159 lines (+106/-1)
3 files modified
doc/filestore.rst (+12/-1)
filestore.py (+22/-0)
test_filestore.py (+72/-0)
To merge this branch: bzr merge lp:~jderose/filestore/purge_tmp
Reviewer Review Type Date Requested Status
James Raymond Approve
Review via email: mp+110676@code.launchpad.net

Description of the change

Adds FileStore.purge_tmp() method, its test, and its documentation.

For more info, see the bug:

https://bugs.launchpad.net/filestore/+bug/1014214

To post a comment you must log in.
Revision history for this message
James Raymond (jamesmr) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'doc/filestore.rst'
2--- doc/filestore.rst 2012-03-22 17:07:54 +0000
3+++ doc/filestore.rst 2012-06-17 07:47:20 +0000
4@@ -539,7 +539,7 @@
5 *batch* must be a :class:`Batch` namedtuple as returned by
6 :func:`scandir()`.
7
8- You also must supply one of more *filestores*, which must be
9+ You also must supply one or more *filestores*, which must be
10 :class:`FileStore` instances.
11
12 This function is a Python generator. For each non-empty file in *batch*, it
13@@ -761,6 +761,17 @@
14 .. method:: remove(_id)
15
16 Remove the file with *_id*.
17+
18+ .. method:: purge_tmp()
19+
20+ Remove all files in .dmedia/tmp/
21+
22+ This method is for periodically purging stale temporary files. The
23+ return value is a list with a :class:`File` namedtuple for each file
24+ removed.
25+
26+ Symlinks are ignored and directories are not traversed, neither of which
27+ should ever be created by the :class:`FileStore` itself.
28
29
30 .. method:: verify(_id, return_fp=False)
31
32=== modified file 'filestore.py'
33--- filestore.py 2012-06-16 02:11:24 +0000
34+++ filestore.py 2012-06-17 07:47:20 +0000
35@@ -1252,6 +1252,28 @@
36 log.info('Removing %r from %r', _id, self)
37 os.remove(filename)
38
39+ def purge_tmp(self):
40+ """
41+ Remove all files in .dmedia/tmp/
42+
43+ This method is for periodically purging stale temporary files. The
44+ return value is a list with a `File` namedtuple for each file removed.
45+
46+ Symlinks are ignored and directories are not traversed, neither of which
47+ should ever be created by the `FileStore` itself.
48+ """
49+ purged = []
50+ for name in sorted(os.listdir(self.tmp)):
51+ fullname = path.join(self.tmp, name)
52+ st = os.lstat(fullname)
53+ if stat.S_ISREG(st.st_mode):
54+ purged.append(File(fullname, st.st_size, st.st_mtime))
55+ log.info('Removing stale temporary file %r', fullname)
56+ os.remove(fullname)
57+ else:
58+ log.info('Unexpected non-regular file %r', fullname)
59+ return purged
60+
61 def allocate_tmp(self, size=None):
62 """
63 Allocate randomly named temporary file for an import or render.
64
65=== modified file 'test_filestore.py'
66--- test_filestore.py 2012-06-16 02:13:09 +0000
67+++ test_filestore.py 2012-06-17 07:47:20 +0000
68@@ -32,12 +32,14 @@
69 from subprocess import check_call
70 import tempfile
71 import shutil
72+from random import SystemRandom
73
74 from skein import skein512
75
76 import filestore
77
78
79+random = SystemRandom()
80 TYPE_ERROR = '{}: need a {!r}; got a {!r}: {!r}'
81
82 B32NAMES = []
83@@ -2137,6 +2139,76 @@
84 fs.remove(_id)
85 self.assertFalse(path.exists(canonical))
86
87+ def test_purge_tmp(self):
88+ tmp = TempDir()
89+ t = tmp.join('.dmedia', 'tmp')
90+ fs = filestore.FileStore(tmp.dir)
91+ self.assertTrue(path.isdir(t))
92+
93+ # Test when nothing is in tmp:
94+ self.assertEqual(os.listdir(t), [])
95+ self.assertEqual(fs.purge_tmp(), [])
96+ self.assertEqual(os.listdir(t), [])
97+
98+ # Test when only regular files are in tmp:
99+ purged = []
100+ for i in range(20):
101+ size = random.randint(1, 1024 * 1024)
102+ fp = fs.allocate_tmp(size)
103+ purged.append(
104+ filestore.File(fp.name, size, path.getmtime(fp.name))
105+ )
106+ purged.sort(key=lambda f: f.name)
107+ self.assertEqual(fs.purge_tmp(), purged)
108+ self.assertEqual(os.listdir(t), [])
109+
110+ # Test that directories aren't traversed:
111+ nope = []
112+ for d in ['aye', 'bee', 'see']:
113+ for n in 'abcd':
114+ nope.append(tmp.touch('.dmedia', 'tmp', d, n))
115+ self.assertEqual(sorted(os.listdir(t)), ['aye', 'bee', 'see'])
116+ self.assertEqual(fs.purge_tmp(), [])
117+ self.assertEqual(sorted(os.listdir(t)), ['aye', 'bee', 'see'])
118+ for f in nope:
119+ self.assertTrue(path.isfile(f))
120+
121+ # Test that symlinks are ignored:
122+ tmp2 = TempDir()
123+ fs2 = filestore.FileStore(tmp2.dir)
124+ names = ['aye', 'bee', 'see']
125+ for i in range(10):
126+ size = random.randint(1, 1024 * 1024)
127+ fp = fs2.allocate_tmp(size)
128+ name = path.basename(fp.name)
129+ link = path.join(t, name)
130+ os.symlink(fp.name, link)
131+ names.append(name)
132+ nope.append(link)
133+ names.sort()
134+ self.assertEqual(sorted(os.listdir(t)), names)
135+ for f in nope:
136+ self.assertTrue(path.isfile(f))
137+
138+ # Add some regular files, including zero-sized files:
139+ purged = []
140+ for i in range(20):
141+ size = random.randint(1, 1024 * 1024)
142+ fp = fs.allocate_tmp(size)
143+ purged.append(
144+ filestore.File(fp.name, size, path.getmtime(fp.name))
145+ )
146+ for i in range(10):
147+ fp = fs.allocate_tmp()
148+ purged.append(
149+ filestore.File(fp.name, 0, path.getmtime(fp.name))
150+ )
151+ purged.sort(key=lambda f: f.name)
152+ self.assertEqual(fs.purge_tmp(), purged)
153+ self.assertEqual(sorted(os.listdir(t)), names)
154+ for f in nope:
155+ self.assertTrue(path.isfile(f))
156+
157 def test_allocate_tmp(self):
158 tmp = TempDir()
159 t = tmp.join('.dmedia', 'tmp')

Subscribers

People subscribed via source and target branches