From 3cfc24fe02325b88cd86415c9be75d2cf56c6809 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Fri, 15 Oct 2010 15:14:28 +0200 Subject: [PATCH] Improvements in the WebDAV client. Transmission of binary files seems to have bugs. --- shared/dav.py | 47 ++++++++++++++++++++++++----------------------- shared/utils.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/shared/dav.py b/shared/dav.py index 0eb4265..15f5e79 100644 --- a/shared/dav.py +++ b/shared/dav.py @@ -1,11 +1,13 @@ # ------------------------------------------------------------------------------ -import os, re, httplib, sys +import os, re, httplib, sys, stat from StringIO import StringIO from mimetypes import guess_type from base64 import encodestring +from appy.shared.utils import copyData # ------------------------------------------------------------------------------ urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I) +binaryRex = re.compile(r'[\000-\006\177-\277]') # ------------------------------------------------------------------------------ class Resource: @@ -26,6 +28,11 @@ class Resource: self.uri = uri or '/' else: raise 'Wrong URL: %s' % str(url) + def __repr__(self): + port = ':' + str(self.port) + if self.port == 80: port = '' + return '' % (self.url, port, self.uri) + def updateHeaders(self, headers): # Add credentials if present if not (self.username and self.password): return @@ -39,7 +46,7 @@ class Resource: headers['Accept'] = '*/*' return headers - def sendRequest(self, method, uri, body=None, headers={}): + def sendRequest(self, method, uri, body=None, headers={}, bodyType=None): '''Sends a HTTP request with p_method, for p_uri.''' conn = httplib.HTTP() conn.connect(self.host, self.port) @@ -48,7 +55,7 @@ class Resource: self.updateHeaders(headers) for n, v in headers.items(): conn.putheader(n, v) conn.endheaders() - if body: conn.send(body) + if body: copyData(body, conn, 'send', type=bodyType) ver, code, msg = conn.getreply() data = conn.getfile().read() conn.close() @@ -78,30 +85,24 @@ class Resource: ''' if type == 'fileName': # p_content is the name of a file on disk - f = file(content, 'rb') - body = f.read() - f.close() - fileName = os.path.basename(content) - fileType, encoding = guess_type(fileName) + size = os.stat(content)[stat.ST_SIZE] + body = file(content, 'rb') + name = os.path.basename(content) + fileType, encoding = guess_type(content) + bodyType = 'file' elif type == 'zope': # p_content is a "Zope" file, ie a OFS.Image.File instance - fileName = name + # p_name is given fileType = content.content_type encoding = None - if isinstance(content.data, basestring): - # The file content is here, in one piece - body = content.data - else: - # There are several parts to this file. - body = '' - data = content.data - while data is not None: - body += data.data - data = data.next - fileUri = self.uri + '/' + fileName - headers = {} + size = content.size + body = content + bodyType = 'zope' + fileUri = self.uri + '/' + name + headers = {'Content-Length': str(size)} if fileType: headers['Content-Type'] = fileType if encoding: headers['Content-Encoding'] = encoding - headers['Content-Length'] = str(len(body)) - return self.sendRequest('PUT', fileUri, body, headers) + res = self.sendRequest('PUT', fileUri, body, headers, bodyType=bodyType) + # Close the file when relevant + if type =='fileName': body.close() # ------------------------------------------------------------------------------ diff --git a/shared/utils.py b/shared/utils.py index fa0646e..36168c3 100644 --- a/shared/utils.py +++ b/shared/utils.py @@ -47,7 +47,7 @@ def cleanFolder(folder, exts=extsToClean, verbose=False): if verbose: print 'Removing %s...' % fileToRemove os.remove(fileToRemove) - +# ------------------------------------------------------------------------------ def copyFolder(source, dest, cleanDest=False): '''Copies the content of folder p_source to folder p_dest. p_dest is created, with intermediary subfolders if required. If p_cleanDest is @@ -70,6 +70,48 @@ def copyFolder(source, dest, cleanDest=False): # Copy a subfolder (recursively) copyFolder(sourceName, destName) +# ------------------------------------------------------------------------------ +def encodeData(data, encoding=None): + '''Applies some p_encoding to string p_data, but only if an p_encoding is + specified.''' + if not encoding: return data + return data.encode(encoding) + +# ------------------------------------------------------------------------------ +def copyData(data, target, targetMethod, type='string', encoding=None, + chunkSize=1024): + '''Copies p_data to a p_target, using p_targetMethod. For example, it copies + p_data which is a string containing the binary content of a file, to + p_target, which can be a HTTP connection or a file object. + + p_targetMethod can be "write" (files) or "send" (HTTP connections) or ... + p_type can be "string", "file" or "zope". In the latter case it is an + instance of OFS.Image.File. If p_type is "file", one may, in p_chunkSize, + specify the amount of bytes transmitted at a time. + + If an p_encoding is specified, it is applied on p_data before copying. + + Note that if the p_target is a Python file, it must be opened in a way + that is compatible with the content of p_data, ie file('myFile.doc','wb') + if content is binary.''' + dump = getattr(target, targetMethod) + if type == 'string': dump(encodeData(data, encoding)) + elif type == 'file': + while True: + chunk = data.read(chunkSize) + if not chunk: break + dump(encodeData(chunk, encoding)) + elif type == 'zope': + # A OFS.Image.File instance can be split into several chunks + if isinstance(data.data, basestring): # One chunk + dump(encodeData(data.data, encoding)) + else: + # Several chunks + data = data.data + while data is not None: + dump(encodeData(data.data, encoding)) + data = data.next + # ------------------------------------------------------------------------------ class Traceback: '''Dumps the last traceback into a string.''' @@ -229,6 +271,7 @@ class CodeAnalysis: print '%s: %d files, %d lines (%.0f%% comments, %.0f%% blank)' % \ (self.name, self.numberOfFiles, lines, commentRate, blankRate) +# ------------------------------------------------------------------------------ class LinesCounter: '''Counts and classifies the lines of code within a folder hierarchy.''' def __init__(self, folderOrModule):