1# Copyright (C) 2006-2010 Canonical Ltd 2# 3# This program is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 17"""VFS operations for the smart server. 18 19This module defines the smart server methods that are low-level file operations 20-- i.e. methods that operate directly on files and directories, rather than 21higher-level concepts like branches and revisions. 22 23These methods, plus 'hello' and 'get_bundle', are version 1 of the smart server 24protocol, as implemented in bzr 0.11 and later. 25""" 26 27import os 28 29from ... import urlutils 30from . import request 31 32 33def _deserialise_optional_mode(mode): 34 # XXX: FIXME this should be on the protocol object. Later protocol versions 35 # might serialise modes differently. 36 if mode == b'': 37 return None 38 else: 39 return int(mode) 40 41 42def vfs_enabled(): 43 """Is the VFS enabled ? 44 45 the VFS is disabled when the BRZ_NO_SMART_VFS environment variable is set. 46 47 :return: ``True`` if it is enabled. 48 """ 49 return 'BRZ_NO_SMART_VFS' not in os.environ 50 51 52class VfsRequest(request.SmartServerRequest): 53 """Base class for VFS requests. 54 55 VFS requests are disabled if vfs_enabled() returns False. 56 """ 57 58 def _check_enabled(self): 59 if not vfs_enabled(): 60 raise request.DisabledMethod(self.__class__.__name__) 61 62 def translate_client_path(self, relpath): 63 # VFS requests are made with escaped paths so the escaping done in 64 # SmartServerRequest.translate_client_path leads to double escaping. 65 # Remove it here -- the fact that the result is still escaped means 66 # that the str() will not fail on valid input. 67 x = request.SmartServerRequest.translate_client_path(self, relpath) 68 return str(urlutils.unescape(x)) 69 70 71class HasRequest(VfsRequest): 72 73 def do(self, relpath): 74 relpath = self.translate_client_path(relpath) 75 r = self._backing_transport.has(relpath) and b'yes' or b'no' 76 return request.SuccessfulSmartServerResponse((r,)) 77 78 79class GetRequest(VfsRequest): 80 81 def do(self, relpath): 82 relpath = self.translate_client_path(relpath) 83 backing_bytes = self._backing_transport.get_bytes(relpath) 84 return request.SuccessfulSmartServerResponse((b'ok',), backing_bytes) 85 86 87class AppendRequest(VfsRequest): 88 89 def do(self, relpath, mode): 90 relpath = self.translate_client_path(relpath) 91 self._relpath = relpath 92 self._mode = _deserialise_optional_mode(mode) 93 94 def do_body(self, body_bytes): 95 old_length = self._backing_transport.append_bytes( 96 self._relpath, body_bytes, self._mode) 97 return request.SuccessfulSmartServerResponse((b'appended', str(old_length).encode('ascii'))) 98 99 100class DeleteRequest(VfsRequest): 101 102 def do(self, relpath): 103 relpath = self.translate_client_path(relpath) 104 self._backing_transport.delete(relpath) 105 return request.SuccessfulSmartServerResponse((b'ok', )) 106 107 108class IterFilesRecursiveRequest(VfsRequest): 109 110 def do(self, relpath): 111 if not relpath.endswith(b'/'): 112 relpath += b'/' 113 relpath = self.translate_client_path(relpath) 114 transport = self._backing_transport.clone(relpath) 115 filenames = transport.iter_files_recursive() 116 return request.SuccessfulSmartServerResponse((b'names',) + tuple(filenames)) 117 118 119class ListDirRequest(VfsRequest): 120 121 def do(self, relpath): 122 if not relpath.endswith(b'/'): 123 relpath += b'/' 124 relpath = self.translate_client_path(relpath) 125 filenames = self._backing_transport.list_dir(relpath) 126 return request.SuccessfulSmartServerResponse((b'names',) + tuple([filename.encode('utf-8') for filename in filenames])) 127 128 129class MkdirRequest(VfsRequest): 130 131 def do(self, relpath, mode): 132 relpath = self.translate_client_path(relpath) 133 self._backing_transport.mkdir(relpath, 134 _deserialise_optional_mode(mode)) 135 return request.SuccessfulSmartServerResponse((b'ok',)) 136 137 138class MoveRequest(VfsRequest): 139 140 def do(self, rel_from, rel_to): 141 rel_from = self.translate_client_path(rel_from) 142 rel_to = self.translate_client_path(rel_to) 143 self._backing_transport.move(rel_from, rel_to) 144 return request.SuccessfulSmartServerResponse((b'ok',)) 145 146 147class PutRequest(VfsRequest): 148 149 def do(self, relpath, mode): 150 relpath = self.translate_client_path(relpath) 151 self._relpath = relpath 152 self._mode = _deserialise_optional_mode(mode) 153 154 def do_body(self, body_bytes): 155 self._backing_transport.put_bytes( 156 self._relpath, body_bytes, self._mode) 157 return request.SuccessfulSmartServerResponse((b'ok',)) 158 159 160class PutNonAtomicRequest(VfsRequest): 161 162 def do(self, relpath, mode, create_parent, dir_mode): 163 relpath = self.translate_client_path(relpath) 164 self._relpath = relpath 165 self._dir_mode = _deserialise_optional_mode(dir_mode) 166 self._mode = _deserialise_optional_mode(mode) 167 # a boolean would be nicer XXX 168 self._create_parent = (create_parent == b'T') 169 170 def do_body(self, body_bytes): 171 self._backing_transport.put_bytes_non_atomic(self._relpath, 172 body_bytes, 173 mode=self._mode, 174 create_parent_dir=self._create_parent, 175 dir_mode=self._dir_mode) 176 return request.SuccessfulSmartServerResponse((b'ok',)) 177 178 179class ReadvRequest(VfsRequest): 180 181 def do(self, relpath): 182 relpath = self.translate_client_path(relpath) 183 self._relpath = relpath 184 185 def do_body(self, body_bytes): 186 """accept offsets for a readv request.""" 187 offsets = self._deserialise_offsets(body_bytes) 188 backing_bytes = b''.join(bytes for offset, bytes in 189 self._backing_transport.readv(self._relpath, offsets)) 190 return request.SuccessfulSmartServerResponse((b'readv',), backing_bytes) 191 192 def _deserialise_offsets(self, text): 193 # XXX: FIXME this should be on the protocol object. 194 offsets = [] 195 for line in text.split(b'\n'): 196 if not line: 197 continue 198 start, length = line.split(b',') 199 offsets.append((int(start), int(length))) 200 return offsets 201 202 203class RenameRequest(VfsRequest): 204 205 def do(self, rel_from, rel_to): 206 rel_from = self.translate_client_path(rel_from) 207 rel_to = self.translate_client_path(rel_to) 208 self._backing_transport.rename(rel_from, rel_to) 209 return request.SuccessfulSmartServerResponse((b'ok', )) 210 211 212class RmdirRequest(VfsRequest): 213 214 def do(self, relpath): 215 relpath = self.translate_client_path(relpath) 216 self._backing_transport.rmdir(relpath) 217 return request.SuccessfulSmartServerResponse((b'ok', )) 218 219 220class StatRequest(VfsRequest): 221 222 def do(self, relpath): 223 if not relpath.endswith(b'/'): 224 relpath += b'/' 225 relpath = self.translate_client_path(relpath) 226 stat = self._backing_transport.stat(relpath) 227 return request.SuccessfulSmartServerResponse( 228 (b'stat', str(stat.st_size).encode('ascii'), oct(stat.st_mode).encode('ascii'))) 229