ellethee has proposed merging lp:~luca-vs800/ladon/ladon into lp:ladon. Requested reviews: Ladon Developer (ladon-dev-team)
For more details, see: https://code.launchpad.net/~luca-vs800/ladon/ladon/+merge/215657 Replaces the standard file object with a file-like object that supports a callback function. So we can set up a function that can manage a progress bar or some other stuff. to set your callback, use: from ladon.tools.callback_file import set_callback set_callback(up_n_down_callback) the callback should manage these events: *init* *start* *read* *write* *end* *close* the callback structure should be like this: def up_n_down_callback(event, attachment, **kwargs): if event == 'init': """ Put here the progressbar initialization (file is open) """ elif event == 'start': """ Whatever you need when your file starts to read or write. """ elif event == 'read': """ Whatever you need when file reads. """ elif event == 'write': """ Whatever you need when file writes. """ elif event == 'end': """ When the file progress reach the file size (ends read/write process). """ elif event == 'close': """ When the file is closed. """ this could be and example: import progressbar pbar = progressbar.ProgressBar( widgets=[ "File Name placeholder", progressbar.Percentage(), ' ', progressbar.Bar('#'), ' ', progressbar.ETA(), ' ', progressbar.FileTransferSpeed()]) def up_n_down_callback(event, attachment, **kwargs): """ Progress callback. """ if event == 'init': pbar.maxval = attachment.size pbar.widgets[0] = attachment.save_name elif event == 'start': pbar.start() elif event in ['read', 'write']: pbar.update(attachment.progress) elif event == 'end': pbar.finish() elif event == 'close': pass -- https://code.launchpad.net/~luca-vs800/ladon/ladon/+merge/215657 Your team Ladon Developer is requested to review the proposed merge of lp:~luca-vs800/ladon/ladon into lp:ladon.
=== modified file 'frameworks/python/examples/services/transferservice.py' --- frameworks/python/examples/services/transferservice.py 2013-11-08 12:44:11 +0000 +++ frameworks/python/examples/services/transferservice.py 2014-04-14 13:05:34 +0000 @@ -75,6 +75,7 @@ f = File() f.name = name f.data = attachment(open(join(upload_dir,name),'rb')) + f.data.headers.update({'x-filename': name}) response += [f] return response === added file 'frameworks/python/examples/test_callback.py' --- frameworks/python/examples/test_callback.py 1970-01-01 00:00:00 +0000 +++ frameworks/python/examples/test_callback.py 2014-04-14 13:05:34 +0000 @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + test +""" +__file_name__ = "test_callback.py" +__author__ = "luca" +__version__ = "1.0.0" +__date__ = "2014-04-09" + + +import os +import pprint +from os.path import join, dirname, abspath +from ladon.clients.jsonwsp import JSONWSPClient + +base_url = 'http://localhost:8080' +files_dir = join(dirname(abspath(__file__)), 'files') +download_dir = join(dirname(abspath(__file__)), 'download') + + +# We need a progressbar to show +import progressbar +pbar = progressbar.ProgressBar( + widgets=[ + "Trasferimento", + progressbar.Percentage(), + ' ', progressbar.Bar('#'), + ' ', progressbar.ETA(), + ' ', progressbar.FileTransferSpeed()], +) + + +# And a silly callback that can manage our events. +def up_n_down_callback(event, attachment, **kwargs): + """ + Callback function + + :param event: one of the events 'init', 'start', 'read', 'write', 'end' + and 'close'. + :param attachment: a CallBackFile object which have also 'size', + 'save_name' and 'progress' as properties. + :param **kwargs: In case of... + """ + if event == 'init': + pass + elif event == 'start': + pbar.maxval = attachment.size + pbar.widgets[0] = attachment.save_name + pbar.start() + elif event in ['read', 'write']: + pbar.update(attachment.progress) + elif event == 'end': + pbar.finish() + pass + elif event == 'close': + pass + + +# Let's set up the callback +from ladon.tools.callback_file import set_callback +set_callback(up_n_down_callback) + + +def print_result(jsonwsp_resp): + if jsonwsp_resp.status == 200: + if 'result' in jsonwsp_resp.response_dict: + pprint.pprint(jsonwsp_resp.response_dict['result'], indent=2) + else: + pprint.pprint(jsonwsp_resp.response_dict) + else: + print("A problem occured while communicating with the service:\n") + print(jsonwsp_resp.response_body) + + +def test_callback(): + global files_dir, download_dir + + print("\n\nTesting TransferService:\n") + # Load the TransferService description + transfer_client = JSONWSPClient( + base_url + + '/TransferService/jsonwsp/description') + + # File list for the upload() call + file_list = [] + # list of file names fot the download() call + name_list = [] + + # Get a list of files in the "files" folder + files = os.listdir(files_dir) + for f in files: + fpath = join(files_dir, f) + # Check if the entry is a file + if os.path.isfile(fpath): + # Add the file as a TransferService.File object (for the upload() + # call) + file_list += [{ + # Attach the file using an open file-handle + 'data': open(fpath, 'rb'), + 'name': f # The file name + }] + + # Add the file name to the list of file names (for the download() + # call) + name_list += [f] + + # Upload multiple files (all files found in the "files" directory) in one + # request + jsonwsp_resp = transfer_client.upload(incomming=file_list) + print_result(jsonwsp_resp) + # Download all the files we just uploaded in one request + jsonwsp_resp = transfer_client.download(names=name_list) + print_result(jsonwsp_resp) + ## The attachments are referenced as open file-handles in the response object + ## read their content and save it as files in the "download" folder. + if jsonwsp_resp.status == 200: + jsonwsp_resp.save_all(download_dir) + +if __name__ == '__main__': + test_callback() === modified file 'frameworks/python/src/ladon/clients/jsonwsp.py' --- frameworks/python/src/ladon/clients/jsonwsp.py 2013-11-07 10:30:11 +0000 +++ frameworks/python/src/ladon/clients/jsonwsp.py 2014-04-14 13:05:34 +0000 @@ -15,6 +15,9 @@ from urlparse import urlparse from httplib import HTTPConnection, HTTPSConnection +# CallBackFile +from ladon.tools.callback_file import CallBackFile + rx_ctype_charset = re.compile('charset\s*=\s*([-_.a-zA-Z0-9]+)',re.I) rx_detect_multipart = re.compile('multipart/([^; ]+)',re.I) rx_detect_boundary = re.compile('boundary=([^; ]+)',re.I) @@ -130,6 +133,15 @@ if not attachment == None: result[key] = attachment + def save_all(self, path): + """ + Save all attachments. + """ + if not os.path.isdir(path): + raise IOError("Path must be a existing folder.") + for attach in self.attachments.values(): + attach.save(path) + class JSONWSPClient(object): @@ -245,7 +257,7 @@ boundary_match = rx_detect_boundary.findall(content_type) if len(boundary_match): boundary = boundary_match[0] - mpr = MultiPartReader(20000,boundary.encode(jsonwsp_charset),response) + mpr = MultiPartReader(20000,boundary.encode(jsonwsp_charset),response, size=int(jsonwsp_response.headers.get('CONTENT-LENGTH', '0'))) mpr.read_chunk() while not mpr.eos: mpr.read_chunk() @@ -297,8 +309,14 @@ else: conn = HTTPConnection(self.hostname,self.port) req_path = self.path + '/' + extra_path - buffer_fp = open(buffer_fname,'rb') - if extra_headers!=None: + # CallBackFile + if len(files) > 1: + save_name = "Multiple files" + else: + save_name = os.path.basename([f.name for f in files.values()][0]) + buffer_fp = CallBackFile(path=buffer_fname, save_name=save_name) + # /CallBackFile + if extra_headers is not None: headers.update(extra_headers) conn.request("POST", req_path, buffer_fp, headers) buffer_fp.close() === added file 'frameworks/python/src/ladon/tools/callback_file.py' --- frameworks/python/src/ladon/tools/callback_file.py 1970-01-01 00:00:00 +0000 +++ frameworks/python/src/ladon/tools/callback_file.py 2014-04-14 13:05:34 +0000 @@ -0,0 +1,189 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + New Attachment file to handle progressbars. +""" +__file_name__ = "callback_file.py" +__author__ = "luca" +__version__ = "1.0.0" +__date__ = "2014-04-08" + + +import tempfile +from os.path import join, isdir +import os +from shutil import copy2 + +try: + O_BINARY = os.O_BINARY +except AttributeError: + O_BINARY = 0 + +MODES = { + 'a': os.O_APPEND, + 'w': os.O_WRONLY, + 'r': os.O_RDONLY, + '+': os.O_RDWR, + 'b': O_BINARY, +} + + +def void_callback(event, attachment, **kwargs): + """ + A void callback. + """ + return (event, attachment, kwargs) + + +CALLBACK = void_callback + + +def set_callback(new_callback): + """ + Set global callback. + """ + global CALLBACK + CALLBACK = new_callback + + +class CallBackFile(object): + """ + File-Like Object that supports callbacks too. + + :param istemp: This will be a tmp file. + :param attach_id: A simple id for the file. + :param save_name: The real name that you'd like to use in tmp case. + :param size: The size of the file in tmp case. + :param path: Path of the file or tmp dir in tmp case. + :param mode: mode in NON tmp case. + :param callback: callback to use. + :param **kwargs: Add kwargs to pass to callback. + """ + fdesc = None + + def __init__( + self, istemp=False, attach_id=None, save_name=None, size=0L, + suffix='', prefix='tmp', text=False, removeit=True, path=None, + mode='rb', callback=None, **kwargs): + rmode = 0 + for char in mode: + rmode |= MODES.get(char, 0) + self.last_chunk = 0 + self.istemp = istemp + self.removeit = removeit + self.file_id = attach_id or id(self) + if self.istemp: + self.fdesc, self.name = tempfile.mkstemp( + suffix, prefix, path, text) + self.size = size + else: + self.name = path + if not os.path.exists(path): + rmode |= os.O_CREAT + self.fdesc = os.open(path, rmode) + self.seek(0, os.SEEK_END) + self.size = self.tell() or size + self.save_name = save_name or os.path.basename(self.name) + self.seek(0) + self._callback = callback or CALLBACK + self._kwargs = kwargs + self._callback('init', self, **self._kwargs) + + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_val, trace): + self.close() + + @staticmethod + def mkstemp( + suffix='', prefix='tmp', tmpdir=None, text=False, save_name=None): + """ + Simulate tempfile.mkstemp + """ + the_file = CallBackFile( + istemp=True, suffix=suffix, prefix=prefix, path=tmpdir, text=text, + save_name=save_name) + return the_file + + @staticmethod + def get_uploaditem(filename): + """ + Ritorna un *upload_item* da usare per l'invio di un file. + """ + return { + 'name': os.path.basename(filename), + 'data': open(filename, 'rb'), + 'size': os.path.getsize(filename) + } + + @property + def progress(self): + """ + Returns progress position. + """ + return self.tell() + + def __len__(self): + return self.size + + def seek(self, pos, how=os.SEEK_SET): + """ + Seek wrapper. + """ + return os.lseek(self.fdesc, pos, how) + + def tell(self): + """ + tell wrapper. + """ + return os.lseek(self.fdesc, 0, os.SEEK_CUR) + + def read(self, size): + """ + read wrapper. + """ + if self.progress == 0: + self._callback('start', self, **self._kwargs) + data = os.read(self.fdesc, size) + part = len(data) + self._callback('read', self, **self._kwargs) + if part == 0 or part == self.size: + self._callback('end', self, **self._kwargs) + return data + + def write(self, data): + """ + Write wrapper. + """ + if self.istemp and self.size and ( + self.progress + len(data)) > self.size: + data = data[:-((self.progress + len(data)) - self.size)] + self.last_chunk = len(data) + if self.progress == 0: + self._callback('start', self, **self._kwargs) + os.write(self.fdesc, data) + self._callback('write', self, **self._kwargs) + if self.progress + len(data) >= self.size: + self._callback('end', self, **self._kwargs) + + def save(self, path): + """ + Save attachment. + """ + if isdir(path): + path = join(path, self.save_name) + copy2(self.name, path) + + def __del__(self): + pass + + def close(self): + """ + Close wrapper. + """ + try: + os.close(self.fdesc) + self._callback('close', self, **self._kwargs) + except: + pass === modified file 'frameworks/python/src/ladon/tools/multiparthandler.py' --- frameworks/python/src/ladon/tools/multiparthandler.py 2011-11-01 11:50:14 +0000 +++ frameworks/python/src/ladon/tools/multiparthandler.py 2014-04-14 13:05:34 +0000 @@ -13,6 +13,10 @@ import re,tempfile,os,time,hashlib from ladon.compat import PORTABLE_BYTES, PORTABLE_STRING +# CallBackFile +from ladon.tools.callback_file import CallBackFile +import json + rx_2linefeeds = re.compile(b'\n\n|\r\n\r\n',re.M) rx_headers = re.compile(b'^([-_a-zA-Z0-9]+): (.*)$',re.M) @@ -20,9 +24,11 @@ return (pair[0].upper().replace(b'-',b'_'),pair[1]) class MultiPartReader(object): - def __init__(self,chunk_size,boundary,req,content_size=None): + + def __init__(self, chunk_size, boundary, req, content_size=None, size=0): self.chunk_size = chunk_size self.content_size=content_size + self.size = size self.boundary = boundary rx_boundary = re.escape(boundary) self.rx_boundary_split = re.compile(b'--<b>\n|\n--<b>\n|\n--<b>--|--<b>\r\n|\r\n--<b>\r\n|\r\n--<b>--'.replace(b'<b>',boundary) ,re.M) @@ -39,6 +45,7 @@ self.attachment_headers_parsed = False self.tmpdir = None self.fd = None + self.fobj = None self.request_bytes_read = 0 self.attachment_bytes_size = 0 self.raw_dump_rest = False @@ -65,11 +72,15 @@ self.interface_request += data self.interface_request_headers = dict(map(upperkey,rx_headers.findall(self.attachment_headers))) else: - os.write(self.fd,data) + # CallBackFile + self.fobj.write(data) + # /CallBackFile if eop==True: - if self.fd != None: - os.close(self.fd) + if self.fobj != None: + # CallBackFile + self.fobj.close() + # /CallBackFile headers_dict = dict(map(upperkey,rx_headers.findall(self.attachment_headers))) self.attachments[self.part_count-1]['size'] = self.attachment_bytes_size self.attachments[self.part_count-1]['headers'] = headers_dict @@ -78,7 +89,16 @@ if not self.eos: self.attachments[self.part_count] = {} - self.fd,self.attachments[self.part_count]['path'] = tempfile.mkstemp('','content_',self.tmpdir) + # CallBackFile + try: + save_name = json.loads(data)['result']['file']['name'] + except: + save_name = None + self.fobj = CallBackFile( + istemp=True, prefix='content_', path=self.tmpdir, + save_name=save_name, size=self.size) + self.attachments[self.part_count]['path'] = self.fobj.name + # /CallBackFile self.part_count += 1 self.attachment_bytes_size = 0 self.attachment_headers = b'' @@ -108,7 +128,9 @@ self.eos = True self.write(parts[-1:][0][:-self.len_rest_chunk]) if self.eos: - os.close(self.fd) + # CallBackFile + self.fobj.close() + # /CallBackFile os.unlink(self.attachments[self.part_count-1]['path']) self.rest_chunk = parts[-1:][0][-self.len_rest_chunk:] === modified file 'frameworks/python/src/ladon/types/attachment.py' --- frameworks/python/src/ladon/types/attachment.py 2011-12-11 22:51:56 +0000 +++ frameworks/python/src/ladon/types/attachment.py 2014-04-14 13:05:34 +0000 @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- import os,re +from os.path import isdir, join, basename +from shutil import copy2 from ladon.exceptions.dispatcher import NonExistingAttachment,InvalidAttachmentReference class attachment(object): @@ -36,6 +38,16 @@ self.closed = self.bufferobj.closed self.close = self.bufferobj.close + def save(self, path): + """ + Save the attachment. + """ + save_name = self.headers.get( + 'X_FILENAME', basename(self.bufferobj.name)) + if isdir(path): + path = join(path, save_name) + copy2(self.bufferobj.name, path) + def __del__(self): if self.bufferobj: self.bufferobj.close()
-- Mailing list: https://launchpad.net/~ladon-dev-team Post to : ladon-dev-team@lists.launchpad.net Unsubscribe : https://launchpad.net/~ladon-dev-team More help : https://help.launchpad.net/ListHelp