def remoteAddFile(self, name, size, file, contentType, expires=None): """See `IFileUploadClient`.""" if file is None: raise TypeError('No data') if size <= 0: raise UploadFailed('No data') if isinstance(name, unicode): name = name.encode('utf-8') self._connect() try: database_name = ConnectionString(dbconfig.main_master).dbname self._sendLine('STORE %d %s' % (size, name)) self._sendHeader('Database-Name', database_name) self._sendHeader('Content-Type', str(contentType)) if expires is not None: epoch = time.mktime(expires.utctimetuple()) self._sendHeader('File-Expires', str(int(epoch))) # Send blank line self._sendLine('') # Prepare to the upload the file bytesWritten = 0 # Read in and upload the file 64kb at a time, by using the two-arg # form of iter (see # /usr/share/doc/python/html/library/functions.html#iter). for chunk in iter(lambda: file.read(1024 * 64), ''): self.state.f.write(chunk) bytesWritten += len(chunk) assert bytesWritten == size, ( 'size is %d, but %d were read from the file' % (size, bytesWritten)) self.state.f.flush() # Read response response = self.state.f.readline().strip() if not response.startswith('200'): raise UploadFailed( 'Could not upload %s. Server said: %s' % (name, response)) status, ids = response.split() contentID, aliasID = ids.split('/', 1) path = get_libraryfilealias_download_path(aliasID, name) return urljoin(self.download_url, path) finally: self._close()
def _connect(self): """Connect this client. The host and port default to what is specified in the configuration """ try: self.state.s = socket.socket(AF_INET, SOCK_STREAM) self.state.s.connect((self.upload_host, self.upload_port)) self.state.f = self.state.s.makefile('w+', 0) except socket.error as x: raise UploadFailed('[%s:%s]: %s' % (self.upload_host, self.upload_port, x))
def addFile(self, name, size, file, contentType, expires=None, debugID=None, allow_zero_length=False): """Add a file to the librarian. :param name: Name to store the file as :param size: Size of the file :param file: File-like object with the content in it :param contentType: mime-type, e.g. text/plain :param expires: Expiry time of file. See LibrarianGarbageCollection. Set to None to only expire when it is no longer referenced. :param debugID: Optional. If set, causes extra logging for this request on the server, which will be marked with the value given. :param allow_zero_length: If True permit zero length files. :returns: aliasID as an integer :raises UploadFailed: If the server rejects the upload for some reason. """ if file is None: raise TypeError('Bad File Descriptor: %s' % repr(file)) if allow_zero_length: min_size = -1 else: min_size = 0 if size <= min_size: raise UploadFailed('Invalid length: %d' % size) name = six.ensure_binary(name) # Import in this method to avoid a circular import from lp.services.librarian.model import LibraryFileContent from lp.services.librarian.model import LibraryFileAlias self._connect() try: # Get the name of the database the client is using, so that # the server can check that the client is using the same # database as the server. store = IMasterStore(LibraryFileAlias) databaseName = self._getDatabaseName(store) # Generate new content and alias IDs. # (we'll create rows with these IDs later, but not yet) contentID = store.execute( "SELECT nextval('libraryfilecontent_id_seq')").get_one()[0] aliasID = store.execute( "SELECT nextval('libraryfilealias_id_seq')").get_one()[0] # Send command self._sendLine('STORE %d %s' % (size, name)) # Send headers self._sendHeader('Database-Name', databaseName) self._sendHeader('File-Content-ID', contentID) self._sendHeader('File-Alias-ID', aliasID) if debugID is not None: self._sendHeader('Debug-ID', debugID) # Send blank line. Do not check for a response from the # server when no data will be sent. Otherwise # _checkError() might consume the "200" response which # is supposed to be read below in this method. self._sendLine('', check_for_error_responses=(size > 0)) # Prepare to the upload the file md5_digester = hashlib.md5() sha1_digester = hashlib.sha1() sha256_digester = hashlib.sha256() bytesWritten = 0 # Read in and upload the file 64kb at a time, by using the two-arg # form of iter (see # /usr/share/doc/python/html/library/functions.html#iter). for chunk in iter(lambda: file.read(1024 * 64), ''): self.state.f.write(chunk) bytesWritten += len(chunk) md5_digester.update(chunk) sha1_digester.update(chunk) sha256_digester.update(chunk) assert bytesWritten == size, ( 'size is %d, but %d were read from the file' % (size, bytesWritten)) self.state.f.flush() # Read response response = self.state.f.readline().strip() if response != '200': raise UploadFailed('Server said: ' + response) # Add rows to DB content = LibraryFileContent(id=contentID, filesize=size, sha256=sha256_digester.hexdigest(), sha1=sha1_digester.hexdigest(), md5=md5_digester.hexdigest()) LibraryFileAlias(id=aliasID, content=content, filename=name.decode('UTF-8'), mimetype=contentType, expires=expires, restricted=self.restricted) Store.of(content).flush() assert isinstance(aliasID, (int, long)), \ "aliasID %r not an integer" % (aliasID, ) return aliasID finally: self._close()
def _checkError(self): if select([self.state.s], [], [], 0)[0]: response = self.state.f.readline().strip() raise UploadFailed('Server said: ' + response)