1""" 2fs.expose.xmlrpc 3================ 4 5Server to expose an FS via XML-RPC 6 7This module provides the necessary infrastructure to expose an FS object 8over XML-RPC. The main class is 'RPCFSServer', a SimpleXMLRPCServer subclass 9designed to expose an underlying FS. 10 11If you need to use a more powerful server than SimpleXMLRPCServer, you can 12use the RPCFSInterface class to provide an XML-RPC-compatible wrapper around 13an FS object, which can then be exposed using whatever server you choose 14(e.g. Twisted's XML-RPC server). 15 16""" 17 18import xmlrpclib 19from SimpleXMLRPCServer import SimpleXMLRPCServer 20from datetime import datetime 21import base64 22 23import six 24from six import PY3 25 26 27class RPCFSInterface(object): 28 """Wrapper to expose an FS via a XML-RPC compatible interface. 29 30 The only real trick is using xmlrpclib.Binary objects to transport 31 the contents of files. 32 """ 33 34 # info keys are restricted to a subset known to work over xmlrpc 35 # This fixes an issue with transporting Longs on Py3 36 _allowed_info = ["size", 37 "created_time", 38 "modified_time", 39 "accessed_time", 40 "st_size", 41 "st_mode", 42 "type"] 43 44 def __init__(self, fs): 45 super(RPCFSInterface, self).__init__() 46 self.fs = fs 47 48 def encode_path(self, path): 49 """Encode a filesystem path for sending over the wire. 50 51 Unfortunately XMLRPC only supports ASCII strings, so this method 52 must return something that can be represented in ASCII. The default 53 is base64-encoded UTF-8. 54 """ 55 #return path 56 return six.text_type(base64.b64encode(path.encode("utf8")), 'ascii') 57 58 def decode_path(self, path): 59 """Decode paths arriving over the wire.""" 60 return six.text_type(base64.b64decode(path.encode('ascii')), 'utf8') 61 62 def getmeta(self, meta_name): 63 meta = self.fs.getmeta(meta_name) 64 if isinstance(meta, basestring): 65 meta = self.decode_path(meta) 66 return meta 67 68 def getmeta_default(self, meta_name, default): 69 meta = self.fs.getmeta(meta_name, default) 70 if isinstance(meta, basestring): 71 meta = self.decode_path(meta) 72 return meta 73 74 def hasmeta(self, meta_name): 75 return self.fs.hasmeta(meta_name) 76 77 def get_contents(self, path, mode="rb"): 78 path = self.decode_path(path) 79 data = self.fs.getcontents(path, mode) 80 return xmlrpclib.Binary(data) 81 82 def set_contents(self, path, data): 83 path = self.decode_path(path) 84 self.fs.setcontents(path, data.data) 85 86 def exists(self, path): 87 path = self.decode_path(path) 88 return self.fs.exists(path) 89 90 def isdir(self, path): 91 path = self.decode_path(path) 92 return self.fs.isdir(path) 93 94 def isfile(self, path): 95 path = self.decode_path(path) 96 return self.fs.isfile(path) 97 98 def listdir(self, path="./", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): 99 path = self.decode_path(path) 100 entries = self.fs.listdir(path, wildcard, full, absolute, dirs_only, files_only) 101 return [self.encode_path(e) for e in entries] 102 103 def makedir(self, path, recursive=False, allow_recreate=False): 104 path = self.decode_path(path) 105 return self.fs.makedir(path, recursive, allow_recreate) 106 107 def remove(self, path): 108 path = self.decode_path(path) 109 return self.fs.remove(path) 110 111 def removedir(self, path, recursive=False, force=False): 112 path = self.decode_path(path) 113 return self.fs.removedir(path, recursive, force) 114 115 def rename(self, src, dst): 116 src = self.decode_path(src) 117 dst = self.decode_path(dst) 118 return self.fs.rename(src, dst) 119 120 def settimes(self, path, accessed_time, modified_time): 121 path = self.decode_path(path) 122 if isinstance(accessed_time, xmlrpclib.DateTime): 123 accessed_time = datetime.strptime(accessed_time.value, "%Y%m%dT%H:%M:%S") 124 if isinstance(modified_time, xmlrpclib.DateTime): 125 modified_time = datetime.strptime(modified_time.value, "%Y%m%dT%H:%M:%S") 126 return self.fs.settimes(path, accessed_time, modified_time) 127 128 def getinfo(self, path): 129 path = self.decode_path(path) 130 info = self.fs.getinfo(path) 131 info = dict((k, v) for k, v in info.iteritems() 132 if k in self._allowed_info) 133 return info 134 135 def desc(self, path): 136 path = self.decode_path(path) 137 return self.fs.desc(path) 138 139 def getxattr(self, path, attr, default=None): 140 path = self.decode_path(path) 141 attr = self.decode_path(attr) 142 return self.fs.getxattr(path, attr, default) 143 144 def setxattr(self, path, attr, value): 145 path = self.decode_path(path) 146 attr = self.decode_path(attr) 147 return self.fs.setxattr(path, attr, value) 148 149 def delxattr(self, path, attr): 150 path = self.decode_path(path) 151 attr = self.decode_path(attr) 152 return self.fs.delxattr(path, attr) 153 154 def listxattrs(self, path): 155 path = self.decode_path(path) 156 return [self.encode_path(a) for a in self.fs.listxattrs(path)] 157 158 def copy(self, src, dst, overwrite=False, chunk_size=16384): 159 src = self.decode_path(src) 160 dst = self.decode_path(dst) 161 return self.fs.copy(src, dst, overwrite, chunk_size) 162 163 def move(self, src, dst, overwrite=False, chunk_size=16384): 164 src = self.decode_path(src) 165 dst = self.decode_path(dst) 166 return self.fs.move(src, dst, overwrite, chunk_size) 167 168 def movedir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): 169 src = self.decode_path(src) 170 dst = self.decode_path(dst) 171 return self.fs.movedir(src, dst, overwrite, ignore_errors, chunk_size) 172 173 def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): 174 src = self.decode_path(src) 175 dst = self.decode_path(dst) 176 return self.fs.copydir(src, dst, overwrite, ignore_errors, chunk_size) 177 178 179class RPCFSServer(SimpleXMLRPCServer): 180 """Server to expose an FS object via XML-RPC. 181 182 This class takes as its first argument an FS instance, and as its second 183 argument a (hostname,port) tuple on which to listen for XML-RPC requests. 184 Example:: 185 186 fs = OSFS('/var/srv/myfiles') 187 s = RPCFSServer(fs,("",8080)) 188 s.serve_forever() 189 190 To cleanly shut down the server after calling serve_forever, set the 191 attribute "serve_more_requests" to False. 192 """ 193 194 def __init__(self, fs, addr, requestHandler=None, logRequests=None): 195 kwds = dict(allow_none=True) 196 if requestHandler is not None: 197 kwds['requestHandler'] = requestHandler 198 if logRequests is not None: 199 kwds['logRequests'] = logRequests 200 self.serve_more_requests = True 201 SimpleXMLRPCServer.__init__(self, addr, **kwds) 202 self.register_instance(RPCFSInterface(fs)) 203 204 def serve_forever(self): 205 """Override serve_forever to allow graceful shutdown.""" 206 while self.serve_more_requests: 207 self.handle_request() 208