root / trunk / twisted / vfs / adapters / ftp.py

Revision 21793, 9.5 kB (checked in by therve, 2 years ago)

Merge vfs-ftp-1264+2735-2

Author: therve
Reviewers: jml, exarkun, cablehead
Fixes #1264
Fixes #2735

Refactor tests for the vfs ftp adapter, using the new IFTPShellTestsMixin,
and fixe behavior both in the ftp adapter and in the inmem backend.

Line 
1 # -*- test-case-name: twisted.vfs.test.test_ftp -*-
2 # Copyright (c) 2007 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 Adapter for FTP protocol.
7 """
8
9 import os, time
10
11 from twisted.protocols import ftp
12 from twisted.internet import defer
13 from twisted.internet.interfaces import IConsumer
14 from twisted.web2.stream import StreamProducer, IByteStream
15
16 from zope.interface import implements
17
18 from twisted.python import components
19 from twisted.vfs import ivfs, pathutils
20
21 # XXX: Import this to make sure the adapter registration has happened.
22 from twisted.vfs.adapters import stream
23
24
25
26 def vfsToFtpError(vfsError):
27     """
28     Map vfs errors to the corresponding ftp errors, if it exists.
29     """
30     if isinstance(vfsError, ivfs.NotFoundError):
31         return defer.fail(ftp.FileNotFoundError(vfsError))
32     elif isinstance(vfsError, ivfs.AlreadyExistsError):
33         return defer.fail(ftp.FileExistsError(vfsError))
34     return defer.fail(vfsError)
35
36
37
38 class FileSystemToIFTPShellAdaptor(object):
39     """
40     Wrap a VFS filesystem to an L{ftpIFTPShell} interface.
41     """
42     implements(ftp.IFTPShell)
43
44     def __init__(self, filesystem):
45         """
46         @param filesystem: the root of the FTP server.
47         @type filesystem: L{ivfs.IFileSystemContainer}.
48         """
49         self.filesystem = filesystem
50
51
52     def _makePath(segments):
53         """
54         Make a path from its segments.
55         """
56         return '/'.join(segments)
57     _makePath = staticmethod(_makePath)
58
59
60     def makeDirectory(self, path):
61         """
62         Create a directory named C{path}.
63         """
64         if len(path) == 1:
65             path = ('.', path[0])
66         dirname, basename = path[:-1], path[-1]
67         parent = self.filesystem.fetch(self._makePath(dirname))
68         try:
69             parent.createDirectory(basename)
70         except ivfs.VFSError, e:
71             return vfsToFtpError(e)
72         except:
73             return defer.fail()
74         else:
75             return defer.succeed(None)
76
77
78     def removeDirectory(self, path):
79         """
80         Remove the given directory.
81         """
82         try:
83             node = self.filesystem.fetch(self._makePath(path))
84             if not ivfs.IFileSystemContainer.providedBy(node):
85                 raise ftp.IsNotADirectoryError(
86                     "removeDirectory can only remove directories.")
87             node.remove()
88         except ivfs.VFSError, e:
89             return vfsToFtpError(e)
90         except:
91             return defer.fail()
92         else:
93             return defer.succeed(None)
94
95
96     def removeFile(self, path):
97         """
98         Remove the file at C{path}.
99         """
100         try:
101             node = self.filesystem.fetch(self._makePath(path))
102             if not ivfs.IFileSystemLeaf.providedBy(node):
103                 raise ftp.IsADirectoryError(
104                     "removeFile can only remove files.")
105             node.remove()
106         except ivfs.VFSError, e:
107             return vfsToFtpError(e)
108         except:
109             return defer.fail()
110         else:
111             return defer.succeed(None)
112
113
114     def list(self, path, keys=()):
115         """
116         List all files and directories in C{path}.
117
118         @param keys: the name of the metadatas to return.
119         @type keys: C{tuple} of C{str}.
120         """
121         try:
122             node = self.filesystem.fetch(self._makePath(path))
123         except ivfs.VFSError, e:
124             return vfsToFtpError(e)
125         except:
126             return defer.fail()
127
128         result = []
129         try:
130             if ivfs.IFileSystemContainer.providedBy(node):
131                 for childName, childNode in node.children()[2:]:
132                     attrs = self._attrify(childNode, keys)
133                     result.append((childName, attrs))
134             else:
135                 attrs = self._attrify(node, keys)
136                 result.append((node.name, attrs))
137         except KeyError, e:
138             return defer.fail(AttributeError(e.args[0]))
139
140         return defer.succeed(result)
141
142
143     # XXX - this should probably go in a helper somewhere
144     def _attrify(self, node, keys):
145         meta = node.getMetadata()
146         permissions = meta.get('permissions', None)
147         directory = ivfs.IFileSystemContainer.providedBy(node)
148         if permissions is None:
149             # WTF
150             if ivfs.IFileSystemContainer.providedBy(node):
151                 permissions = 16877
152             else:
153                 permissions = 33188
154
155         d = {'permissions': permissions,
156              'directory': directory,
157              'size': meta.get('size', 0),
158              'owner': str(meta.get('uid', 'user')),
159              'group': str(meta.get('gid', 'user')),
160              'modified': meta.get('mtime', time.time()),
161              'hardlinks': meta.get('nlink', 1)
162              }
163         return [d[k] for k in keys]
164
165
166     def access(self, path):
167         """
168         Check if C{path} is accessible.
169         """
170         # XXX: we only fetch it for now, that's a first step, but it's not
171         # sufficient to say if the path is really accessible.
172         # See ticket 2875 in the tracker
173         try:
174             node = self.filesystem.fetch(self._makePath(path))
175         except ivfs.VFSError, e:
176             return vfsToFtpError(e)
177         except:
178             return defer.fail()
179         else:
180             return defer.succeed(None)
181
182
183     def openForReading(self, path):
184         """
185         Open file at C{path} for reading.
186         """
187         try:
188             node = self.filesystem.fetch(self._makePath(path))
189             if ivfs.IFileSystemContainer.providedBy(node):
190                 raise ftp.IsADirectoryError("Can only open file for reading.")
191         except ivfs.VFSError, e:
192             return vfsToFtpError(e)
193         except:
194             return defer.fail()
195         else:
196             frvfs = FTPReadVFS(node)
197             return defer.succeed(frvfs)
198
199
200     def openForWriting(self, path):
201         """
202         Open file at C{path} for writing.
203         """
204         if len(path) == 1:
205             path = ('.', path[0])
206         dirname, basename = path[:-1], path[-1]
207         try:
208             node = self.filesystem.fetch(self._makePath(dirname))
209             if (node.exists(basename) and
210                  ivfs.IFileSystemContainer.providedBy(node.child(basename))):
211                 raise ftp.IsADirectoryError("Can only open file for writing.")
212             node = node.createFile(basename, exclusive=False)
213         except ivfs.VFSError, e:
214             return vfsToFtpError(e)
215         except:
216             return defer.fail()
217         else:
218             fwvfs = FTPWriteVFS(node)
219             return defer.succeed(fwvfs)
220
221
222     def stat(self, path, keys=()):
223         """
224         Stat C{path} for metadatas C{keys}.
225         """
226         try:
227             node = self.filesystem.fetch(self._makePath(path))
228         except ivfs.VFSError, e:
229             return vfsToFtpError(e)
230         except:
231             return defer.fail()
232         else:
233             try:
234                 attrs = self._attrify(node, keys)
235             except KeyError, e:
236                 return defer.fail(AttributeError(e.args[0]))
237             return defer.succeed(attrs)
238
239
240     def rename(self, fromPath, toPath):
241         """
242         Rename C{fromPath} to C{toPath}.
243         """
244         if len(toPath) != 1:
245             return defer.fail(NotImplementedError(
246                 "Renaming into other directories isn't supported yet."))
247         try:
248             self.filesystem.fetch(self._makePath(fromPath)).rename(toPath[0])
249         except ivfs.VFSError, e:
250             return vfsToFtpError(e)
251         except:
252             return defer.fail()
253         else:
254             return defer.succeed(None)
255
256
257
258 class FTPReadVFS(object):
259     """
260     Read a file source.
261     """
262     implements(ftp.IReadFile)
263
264     def __init__(self, node):
265         """
266         Use C{node} as source of data.
267         """
268         self.node = node
269
270
271     def send(self, consumer):
272         """
273         Start producing data using L{StreamProducer}.
274         """
275         return StreamProducer(IByteStream(self.node)).beginProducing(consumer)
276
277
278
279 class FTPWriteVFS(object):
280     """
281     Write to a file source.
282     """
283     implements(ftp.IWriteFile)
284
285     def __init__(self, node):
286         """
287         Use C{node} as destination of data.
288         """
289         self.node = node
290
291
292     def receive(self):
293         """
294         Return an consumer object able to write the data.
295         """
296         return defer.succeed(IConsumer(self.node))
297
298
299
300 class _FileToConsumerAdapter(object):
301     """
302     An adapter for writing to a VFS file.
303     """
304     implements(IConsumer)
305
306     def __init__(self, original):
307         """
308         @param original: the vfs node.
309         @type original: C{ivfs.IFileSystemLeaf}.
310         """
311         self.original = original
312         self.offset = 0
313
314
315     def registerProducer(self, producer, streaming):
316         """
317         Register the producer, and open original resource.
318         """
319         if not streaming:
320             raise NotImplementedError("Non-streaming producer not supported.")
321         self.producer = producer
322         self.original.open(os.O_WRONLY)
323
324
325     def unregisterProducer(self):
326         """
327         Unregister producer and close original resource.
328         """
329         self.producer = None
330         self.original.close()
331
332
333     def write(self, bytes):
334         """
335         Write data to the resource.
336         """
337         self.original.writeChunk(self.offset, bytes)
338         self.offset += len(bytes)
339
340
341
342 components.registerAdapter(FileSystemToIFTPShellAdaptor,
343                            pathutils.IFileSystem, ftp.IFTPShell)
344
345 components.registerAdapter(_FileToConsumerAdapter,
346                            ivfs.IFileSystemLeaf, IConsumer)
347
348
Note: See TracBrowser for help on using the browser.