Merge PR #255 from meejah/2774.status-api-only.0-part1

(rebased to current master, added a few fixups)
This commit is contained in:
Brian Warner 2016-04-12 01:00:59 -07:00
commit 47b921855a
15 changed files with 170 additions and 42 deletions

View File

@ -130,7 +130,7 @@ class ProhibitedNode:
def get_best_readable_version(self): def get_best_readable_version(self):
raise FileProhibited(self.reason) raise FileProhibited(self.reason)
def download_best_version(self): def download_best_version(self, progress=None):
raise FileProhibited(self.reason) raise FileProhibited(self.reason)
def get_best_mutable_version(self): def get_best_mutable_version(self):

View File

@ -588,7 +588,7 @@ class DirectoryNode:
return d return d
def add_file(self, namex, uploadable, metadata=None, overwrite=True): def add_file(self, namex, uploadable, metadata=None, overwrite=True, progress=None):
"""I upload a file (using the given IUploadable), then attach the """I upload a file (using the given IUploadable), then attach the
resulting FileNode to the directory at the given name. I return a resulting FileNode to the directory at the given name. I return a
Deferred that fires (with the IFileNode of the uploaded file) when Deferred that fires (with the IFileNode of the uploaded file) when
@ -596,7 +596,7 @@ class DirectoryNode:
name = normalize(namex) name = normalize(namex)
if self.is_readonly(): if self.is_readonly():
return defer.fail(NotWriteableError()) return defer.fail(NotWriteableError())
d = self._uploader.upload(uploadable) d = self._uploader.upload(uploadable, progress=progress)
d.addCallback(lambda results: d.addCallback(lambda results:
self._create_and_validate_node(results.get_uri(), None, self._create_and_validate_node(results.get_uri(), None,
name)) name))

View File

@ -74,7 +74,7 @@ PiB=1024*TiB
class Encoder(object): class Encoder(object):
implements(IEncoder) implements(IEncoder)
def __init__(self, log_parent=None, upload_status=None): def __init__(self, log_parent=None, upload_status=None, progress=None):
object.__init__(self) object.__init__(self)
self.uri_extension_data = {} self.uri_extension_data = {}
self._codec = None self._codec = None
@ -86,6 +86,7 @@ class Encoder(object):
self._log_number = log.msg("creating Encoder %s" % self, self._log_number = log.msg("creating Encoder %s" % self,
facility="tahoe.encoder", parent=log_parent) facility="tahoe.encoder", parent=log_parent)
self._aborted = False self._aborted = False
self._progress = progress
def __repr__(self): def __repr__(self):
if hasattr(self, "_storage_index"): if hasattr(self, "_storage_index"):
@ -105,6 +106,8 @@ class Encoder(object):
def _got_size(size): def _got_size(size):
self.log(format="file size: %(size)d", size=size) self.log(format="file size: %(size)d", size=size)
self.file_size = size self.file_size = size
if self._progress:
self._progress.set_progress_total(self.file_size)
d.addCallback(_got_size) d.addCallback(_got_size)
d.addCallback(lambda res: eu.get_all_encoding_parameters()) d.addCallback(lambda res: eu.get_all_encoding_parameters())
d.addCallback(self._got_all_encoding_parameters) d.addCallback(self._got_all_encoding_parameters)
@ -436,6 +439,7 @@ class Encoder(object):
shareid = shareids[i] shareid = shareids[i]
d = self.send_block(shareid, segnum, block, lognum) d = self.send_block(shareid, segnum, block, lognum)
dl.append(d) dl.append(d)
block_hash = hashutil.block_hash(block) block_hash = hashutil.block_hash(block)
#from allmydata.util import base32 #from allmydata.util import base32
#log.msg("creating block (shareid=%d, blocknum=%d) " #log.msg("creating block (shareid=%d, blocknum=%d) "
@ -445,6 +449,14 @@ class Encoder(object):
self.block_hashes[shareid].append(block_hash) self.block_hashes[shareid].append(block_hash)
dl = self._gather_responses(dl) dl = self._gather_responses(dl)
def do_progress(ign):
done = self.segment_size * (segnum + 1)
if self._progress:
self._progress.set_progress(done)
return ign
dl.addCallback(do_progress)
def _logit(res): def _logit(res):
self.log("%s uploaded %s / %s bytes (%d%%) of your file." % self.log("%s uploaded %s / %s bytes (%d%%) of your file." %
(self, (self,

View File

@ -245,11 +245,13 @@ class ImmutableFileNode:
# we keep it here, we should also put this on CiphertextFileNode # we keep it here, we should also put this on CiphertextFileNode
def __hash__(self): def __hash__(self):
return self.u.__hash__() return self.u.__hash__()
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, ImmutableFileNode): if isinstance(other, ImmutableFileNode):
return self.u.__eq__(other.u) return self.u.__eq__(other.u)
else: else:
return False return False
def __ne__(self, other): def __ne__(self, other):
if isinstance(other, ImmutableFileNode): if isinstance(other, ImmutableFileNode):
return self.u.__eq__(other.u) return self.u.__eq__(other.u)
@ -273,12 +275,16 @@ class ImmutableFileNode:
def get_uri(self): def get_uri(self):
return self.u.to_string() return self.u.to_string()
def get_cap(self): def get_cap(self):
return self.u return self.u
def get_readcap(self): def get_readcap(self):
return self.u.get_readonly() return self.u.get_readonly()
def get_verify_cap(self): def get_verify_cap(self):
return self.u.get_verify_cap() return self.u.get_verify_cap()
def get_repair_cap(self): def get_repair_cap(self):
# CHK files can be repaired with just the verifycap # CHK files can be repaired with just the verifycap
return self.u.get_verify_cap() return self.u.get_verify_cap()
@ -288,6 +294,7 @@ class ImmutableFileNode:
def get_size(self): def get_size(self):
return self.u.get_size() return self.u.get_size()
def get_current_size(self): def get_current_size(self):
return defer.succeed(self.get_size()) return defer.succeed(self.get_size())
@ -305,6 +312,7 @@ class ImmutableFileNode:
def check_and_repair(self, monitor, verify=False, add_lease=False): def check_and_repair(self, monitor, verify=False, add_lease=False):
return self._cnode.check_and_repair(monitor, verify, add_lease) return self._cnode.check_and_repair(monitor, verify, add_lease)
def check(self, monitor, verify=False, add_lease=False): def check(self, monitor, verify=False, add_lease=False):
return self._cnode.check(monitor, verify, add_lease) return self._cnode.check(monitor, verify, add_lease)
@ -316,14 +324,13 @@ class ImmutableFileNode:
""" """
return defer.succeed(self) return defer.succeed(self)
def download_best_version(self, progress=None):
def download_best_version(self):
""" """
Download the best version of this file, returning its contents Download the best version of this file, returning its contents
as a bytestring. Since there is only one version of an immutable as a bytestring. Since there is only one version of an immutable
file, we download and return the contents of this file. file, we download and return the contents of this file.
""" """
d = consumer.download_to_data(self) d = consumer.download_to_data(self, progress=progress)
return d return d
# for an immutable file, download_to_data (specified in IReadable) # for an immutable file, download_to_data (specified in IReadable)

View File

@ -113,7 +113,10 @@ class LiteralFileNode(_ImmutableFileNodeBase):
return defer.succeed(self) return defer.succeed(self)
def download_best_version(self): def download_best_version(self, progress=None):
if progress is not None:
progress.set_progress_total(len(self.u.data))
progress.set_progress(len(self.u.data))
return defer.succeed(self.u.data) return defer.succeed(self.u.data)

View File

@ -137,7 +137,8 @@ class CHKUploadHelper(Referenceable, upload.CHKUploader):
def __init__(self, storage_index, def __init__(self, storage_index,
helper, storage_broker, secret_holder, helper, storage_broker, secret_holder,
incoming_file, encoding_file, incoming_file, encoding_file,
log_number): log_number, progress=None):
upload.CHKUploader.__init__(self, storage_broker, secret_holder, progress=progress)
self._storage_index = storage_index self._storage_index = storage_index
self._helper = helper self._helper = helper
self._incoming_file = incoming_file self._incoming_file = incoming_file

View File

@ -21,7 +21,7 @@ from allmydata.util.rrefutil import add_version_to_remote_reference
from allmydata.interfaces import IUploadable, IUploader, IUploadResults, \ from allmydata.interfaces import IUploadable, IUploader, IUploadResults, \
IEncryptedUploadable, RIEncryptedUploadable, IUploadStatus, \ IEncryptedUploadable, RIEncryptedUploadable, IUploadStatus, \
NoServersError, InsufficientVersionError, UploadUnhappinessError, \ NoServersError, InsufficientVersionError, UploadUnhappinessError, \
DEFAULT_MAX_SEGMENT_SIZE DEFAULT_MAX_SEGMENT_SIZE, IProgress
from allmydata.immutable import layout from allmydata.immutable import layout
from pycryptopp.cipher.aes import AES from pycryptopp.cipher.aes import AES
@ -623,7 +623,7 @@ class EncryptAnUploadable:
implements(IEncryptedUploadable) implements(IEncryptedUploadable)
CHUNKSIZE = 50*1024 CHUNKSIZE = 50*1024
def __init__(self, original, log_parent=None): def __init__(self, original, log_parent=None, progress=None):
precondition(original.default_params_set, precondition(original.default_params_set,
"set_default_encoding_parameters not called on %r before wrapping with EncryptAnUploadable" % (original,)) "set_default_encoding_parameters not called on %r before wrapping with EncryptAnUploadable" % (original,))
self.original = IUploadable(original) self.original = IUploadable(original)
@ -636,6 +636,7 @@ class EncryptAnUploadable:
self._file_size = None self._file_size = None
self._ciphertext_bytes_read = 0 self._ciphertext_bytes_read = 0
self._status = None self._status = None
self._progress = progress
def set_upload_status(self, upload_status): def set_upload_status(self, upload_status):
self._status = IUploadStatus(upload_status) self._status = IUploadStatus(upload_status)
@ -656,6 +657,8 @@ class EncryptAnUploadable:
self._file_size = size self._file_size = size
if self._status: if self._status:
self._status.set_size(size) self._status.set_size(size)
if self._progress:
self._progress.set_progress_total(size)
return size return size
d.addCallback(_got_size) d.addCallback(_got_size)
return d return d
@ -894,7 +897,7 @@ class UploadStatus:
class CHKUploader: class CHKUploader:
server_selector_class = Tahoe2ServerSelector server_selector_class = Tahoe2ServerSelector
def __init__(self, storage_broker, secret_holder): def __init__(self, storage_broker, secret_holder, progress=None):
# server_selector needs storage_broker and secret_holder # server_selector needs storage_broker and secret_holder
self._storage_broker = storage_broker self._storage_broker = storage_broker
self._secret_holder = secret_holder self._secret_holder = secret_holder
@ -904,6 +907,7 @@ class CHKUploader:
self._upload_status = UploadStatus() self._upload_status = UploadStatus()
self._upload_status.set_helper(False) self._upload_status.set_helper(False)
self._upload_status.set_active(True) self._upload_status.set_active(True)
self._progress = progress
# locate_all_shareholders() will create the following attribute: # locate_all_shareholders() will create the following attribute:
# self._server_trackers = {} # k: shnum, v: instance of ServerTracker # self._server_trackers = {} # k: shnum, v: instance of ServerTracker
@ -947,8 +951,11 @@ class CHKUploader:
eu = IEncryptedUploadable(encrypted) eu = IEncryptedUploadable(encrypted)
started = time.time() started = time.time()
self._encoder = e = encode.Encoder(self._log_number, self._encoder = e = encode.Encoder(
self._upload_status) self._log_number,
self._upload_status,
progress=self._progress,
)
d = e.set_encrypted_uploadable(eu) d = e.set_encrypted_uploadable(eu)
d.addCallback(self.locate_all_shareholders, started) d.addCallback(self.locate_all_shareholders, started)
d.addCallback(self.set_shareholders, e) d.addCallback(self.set_shareholders, e)
@ -1073,12 +1080,13 @@ def read_this_many_bytes(uploadable, size, prepend_data=[]):
class LiteralUploader: class LiteralUploader:
def __init__(self): def __init__(self, progress=None):
self._status = s = UploadStatus() self._status = s = UploadStatus()
s.set_storage_index(None) s.set_storage_index(None)
s.set_helper(False) s.set_helper(False)
s.set_progress(0, 1.0) s.set_progress(0, 1.0)
s.set_active(False) s.set_active(False)
self._progress = progress
def start(self, uploadable): def start(self, uploadable):
uploadable = IUploadable(uploadable) uploadable = IUploadable(uploadable)
@ -1086,6 +1094,8 @@ class LiteralUploader:
def _got_size(size): def _got_size(size):
self._size = size self._size = size
self._status.set_size(size) self._status.set_size(size)
if self._progress:
self._progress.set_progress_total(size)
return read_this_many_bytes(uploadable, size) return read_this_many_bytes(uploadable, size)
d.addCallback(_got_size) d.addCallback(_got_size)
d.addCallback(lambda data: uri.LiteralFileURI("".join(data))) d.addCallback(lambda data: uri.LiteralFileURI("".join(data)))
@ -1109,6 +1119,8 @@ class LiteralUploader:
self._status.set_progress(1, 1.0) self._status.set_progress(1, 1.0)
self._status.set_progress(2, 1.0) self._status.set_progress(2, 1.0)
self._status.set_results(ur) self._status.set_results(ur)
if self._progress:
self._progress.set_progress(self._size)
return ur return ur
def close(self): def close(self):
@ -1503,12 +1515,13 @@ class Uploader(service.MultiService, log.PrefixingLogMixin):
name = "uploader" name = "uploader"
URI_LIT_SIZE_THRESHOLD = 55 URI_LIT_SIZE_THRESHOLD = 55
def __init__(self, helper_furl=None, stats_provider=None, history=None): def __init__(self, helper_furl=None, stats_provider=None, history=None, progress=None):
self._helper_furl = helper_furl self._helper_furl = helper_furl
self.stats_provider = stats_provider self.stats_provider = stats_provider
self._history = history self._history = history
self._helper = None self._helper = None
self._all_uploads = weakref.WeakKeyDictionary() # for debugging self._all_uploads = weakref.WeakKeyDictionary() # for debugging
self._progress = progress
log.PrefixingLogMixin.__init__(self, facility="tahoe.immutable.upload") log.PrefixingLogMixin.__init__(self, facility="tahoe.immutable.upload")
service.MultiService.__init__(self) service.MultiService.__init__(self)
@ -1542,12 +1555,13 @@ class Uploader(service.MultiService, log.PrefixingLogMixin):
return (self._helper_furl, bool(self._helper)) return (self._helper_furl, bool(self._helper))
def upload(self, uploadable): def upload(self, uploadable, progress=None):
""" """
Returns a Deferred that will fire with the UploadResults instance. Returns a Deferred that will fire with the UploadResults instance.
""" """
assert self.parent assert self.parent
assert self.running assert self.running
assert progress is None or IProgress.providedBy(progress)
uploadable = IUploadable(uploadable) uploadable = IUploadable(uploadable)
d = uploadable.get_size() d = uploadable.get_size()
@ -1556,13 +1570,15 @@ class Uploader(service.MultiService, log.PrefixingLogMixin):
precondition(isinstance(default_params, dict), default_params) precondition(isinstance(default_params, dict), default_params)
precondition("max_segment_size" in default_params, default_params) precondition("max_segment_size" in default_params, default_params)
uploadable.set_default_encoding_parameters(default_params) uploadable.set_default_encoding_parameters(default_params)
if progress:
progress.set_progress_total(size)
if self.stats_provider: if self.stats_provider:
self.stats_provider.count('uploader.files_uploaded', 1) self.stats_provider.count('uploader.files_uploaded', 1)
self.stats_provider.count('uploader.bytes_uploaded', size) self.stats_provider.count('uploader.bytes_uploaded', size)
if size <= self.URI_LIT_SIZE_THRESHOLD: if size <= self.URI_LIT_SIZE_THRESHOLD:
uploader = LiteralUploader() uploader = LiteralUploader(progress=progress)
return uploader.start(uploadable) return uploader.start(uploadable)
else: else:
eu = EncryptAnUploadable(uploadable, self._parentmsgid) eu = EncryptAnUploadable(uploadable, self._parentmsgid)
@ -1575,7 +1591,7 @@ class Uploader(service.MultiService, log.PrefixingLogMixin):
else: else:
storage_broker = self.parent.get_storage_broker() storage_broker = self.parent.get_storage_broker()
secret_holder = self.parent._secret_holder secret_holder = self.parent._secret_holder
uploader = CHKUploader(storage_broker, secret_holder) uploader = CHKUploader(storage_broker, secret_holder, progress=progress)
d2.addCallback(lambda x: uploader.start(eu)) d2.addCallback(lambda x: uploader.start(eu))
self._all_uploads[uploader] = None self._all_uploads[uploader] = None

View File

@ -1,5 +1,5 @@
from zope.interface import Interface from zope.interface import Interface, Attribute
from foolscap.api import StringConstraint, ListOf, TupleOf, SetOf, DictOf, \ from foolscap.api import StringConstraint, ListOf, TupleOf, SetOf, DictOf, \
ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable ChoiceOf, IntegerConstraint, Any, RemoteInterface, Referenceable
@ -624,6 +624,38 @@ class MustNotBeUnknownRWError(CapConstraintError):
"""Cannot add an unknown child cap specified in a rw_uri field.""" """Cannot add an unknown child cap specified in a rw_uri field."""
class IProgress(Interface):
"""
Remembers progress measured in arbitrary units. Users of these
instances must call ``set_progress_total`` at least once before
progress can be valid, and must use the same units for both
``set_progress_total`` and ``set_progress calls``.
See also:
:class:`allmydata.util.progress.PercentProgress`
"""
progress = Attribute(
"Current amount of progress (in percentage)"
)
def set_progress(self, value):
"""
Sets the current amount of progress.
Arbitrary units, but must match units used for
set_progress_total.
"""
def set_progress_total(self, value):
"""
Sets the total amount of expected progress
Arbitrary units, but must be same units as used when calling
set_progress() on this instance)..
"""
class IReadable(Interface): class IReadable(Interface):
"""I represent a readable object -- either an immutable file, or a """I represent a readable object -- either an immutable file, or a
specific version of a mutable file. specific version of a mutable file.
@ -653,9 +685,12 @@ class IReadable(Interface):
def get_size(): def get_size():
"""Return the length (in bytes) of this readable object.""" """Return the length (in bytes) of this readable object."""
def download_to_data(): def download_to_data(progress=None):
"""Download all of the file contents. I return a Deferred that fires """Download all of the file contents. I return a Deferred that fires
with the contents as a byte string.""" with the contents as a byte string.
:param progress: None or IProgress implementer
"""
def read(consumer, offset=0, size=None): def read(consumer, offset=0, size=None):
"""Download a portion (possibly all) of the file's contents, making """Download a portion (possibly all) of the file's contents, making
@ -915,11 +950,13 @@ class IFileNode(IFilesystemNode):
the Deferred will errback with an UnrecoverableFileError. the Deferred will errback with an UnrecoverableFileError.
""" """
def download_best_version(): def download_best_version(progress=None):
"""Download the contents of the version that would be returned """Download the contents of the version that would be returned
by get_best_readable_version(). This is equivalent to calling by get_best_readable_version(). This is equivalent to calling
download_to_data() on the IReadable given by that method. download_to_data() on the IReadable given by that method.
progress is anything that implements IProgress
I return a Deferred that fires with a byte string when the file I return a Deferred that fires with a byte string when the file
has been fully downloaded. To support streaming download, use has been fully downloaded. To support streaming download, use
the 'read' method of IReadable. If no version is recoverable, the 'read' method of IReadable. If no version is recoverable,
@ -1065,7 +1102,7 @@ class IMutableFileNode(IFileNode):
everything) to get increased visibility. everything) to get increased visibility.
""" """
def upload(new_contents, servermap): def upload(new_contents, servermap, progress=None):
"""Replace the contents of the file with new ones. This requires a """Replace the contents of the file with new ones. This requires a
servermap that was previously updated with MODE_WRITE. servermap that was previously updated with MODE_WRITE.
@ -1086,6 +1123,8 @@ class IMutableFileNode(IFileNode):
operation. If I do not signal UncoordinatedWriteError, then I was operation. If I do not signal UncoordinatedWriteError, then I was
able to write the new version without incident. able to write the new version without incident.
``progress`` is either None or an IProgress provider
I return a Deferred that fires (with a PublishStatus object) when the I return a Deferred that fires (with a PublishStatus object) when the
publish has completed. I will update the servermap in-place with the publish has completed. I will update the servermap in-place with the
location of all new shares. location of all new shares.
@ -1276,12 +1315,14 @@ class IDirectoryNode(IFilesystemNode):
equivalent to calling set_node() multiple times, but is much more equivalent to calling set_node() multiple times, but is much more
efficient.""" efficient."""
def add_file(name, uploadable, metadata=None, overwrite=True): def add_file(name, uploadable, metadata=None, overwrite=True, progress=None):
"""I upload a file (using the given IUploadable), then attach the """I upload a file (using the given IUploadable), then attach the
resulting ImmutableFileNode to the directory at the given name. I set resulting ImmutableFileNode to the directory at the given name. I set
metadata the same way as set_uri and set_node. The child name must be metadata the same way as set_uri and set_node. The child name must be
a unicode string. a unicode string.
``progress`` either provides IProgress or is None
I return a Deferred that fires (with the IFileNode of the uploaded I return a Deferred that fires (with the IFileNode of the uploaded
file) when the operation completes.""" file) when the operation completes."""

View File

@ -403,21 +403,21 @@ class MutableFileNode:
return d.addCallback(_get_version, version) return d.addCallback(_get_version, version)
def download_best_version(self): def download_best_version(self, progress=None):
""" """
I return a Deferred that fires with the contents of the best I return a Deferred that fires with the contents of the best
version of this mutable file. version of this mutable file.
""" """
return self._do_serialized(self._download_best_version) return self._do_serialized(self._download_best_version, progress=progress)
def _download_best_version(self): def _download_best_version(self, progress=None):
""" """
I am the serialized sibling of download_best_version. I am the serialized sibling of download_best_version.
""" """
d = self.get_best_readable_version() d = self.get_best_readable_version()
d.addCallback(self._record_size) d.addCallback(self._record_size)
d.addCallback(lambda version: version.download_to_data()) d.addCallback(lambda version: version.download_to_data(progress=progress))
# It is possible that the download will fail because there # It is possible that the download will fail because there
# aren't enough shares to be had. If so, we will try again after # aren't enough shares to be had. If so, we will try again after
@ -432,7 +432,7 @@ class MutableFileNode:
d = self.get_best_mutable_version() d = self.get_best_mutable_version()
d.addCallback(self._record_size) d.addCallback(self._record_size)
d.addCallback(lambda version: version.download_to_data()) d.addCallback(lambda version: version.download_to_data(progress=progress))
return d return d
d.addErrback(_maybe_retry) d.addErrback(_maybe_retry)
@ -935,13 +935,13 @@ class MutableFileVersion:
return self._servermap.size_of_version(self._version) return self._servermap.size_of_version(self._version)
def download_to_data(self, fetch_privkey=False): def download_to_data(self, fetch_privkey=False, progress=None):
""" """
I return a Deferred that fires with the contents of this I return a Deferred that fires with the contents of this
readable object as a byte string. readable object as a byte string.
""" """
c = consumer.MemoryConsumer() c = consumer.MemoryConsumer(progress=progress)
d = self.read(c, fetch_privkey=fetch_privkey) d = self.read(c, fetch_privkey=fetch_privkey)
d.addCallback(lambda mc: "".join(mc.chunks)) d.addCallback(lambda mc: "".join(mc.chunks))
return d return d

View File

@ -31,6 +31,7 @@ class BadResponse(object):
def __init__(self, url, err): def __init__(self, url, err):
self.status = -1 self.status = -1
self.reason = "Error trying to connect to %s: %s" % (url, err) self.reason = "Error trying to connect to %s: %s" % (url, err)
self.error = err
def read(self): def read(self):
return "" return ""

View File

@ -151,8 +151,8 @@ class FakeCHKFileNode:
return defer.succeed(self) return defer.succeed(self)
def download_to_data(self): def download_to_data(self, progress=None):
return download_to_data(self) return download_to_data(self, progress=progress)
download_best_version = download_to_data download_best_version = download_to_data
@ -329,11 +329,11 @@ class FakeMutableFileNode:
d.addCallback(_done) d.addCallback(_done)
return d return d
def download_best_version(self): def download_best_version(self, progress=None):
return defer.succeed(self._download_best_version()) return defer.succeed(self._download_best_version(progress=progress))
def _download_best_version(self, ignored=None): def _download_best_version(self, ignored=None, progress=None):
if isinstance(self.my_uri, uri.LiteralFileURI): if isinstance(self.my_uri, uri.LiteralFileURI):
return self.my_uri.data return self.my_uri.data
if self.storage_index not in self.all_contents: if self.storage_index not in self.all_contents:

View File

@ -1519,7 +1519,7 @@ class FakeMutableFile:
def get_write_uri(self): def get_write_uri(self):
return self.uri.to_string() return self.uri.to_string()
def download_best_version(self): def download_best_version(self, progress=None):
return defer.succeed(self.data) return defer.succeed(self.data)
def get_writekey(self): def get_writekey(self):

View File

@ -83,7 +83,7 @@ class FakeUploader(service.Service):
helper_furl = None helper_furl = None
helper_connected = False helper_connected = False
def upload(self, uploadable): def upload(self, uploadable, **kw):
d = uploadable.get_size() d = uploadable.get_size()
d.addCallback(lambda size: uploadable.read(size)) d.addCallback(lambda size: uploadable.read(size))
def _got_data(datav): def _got_data(datav):

View File

@ -8,9 +8,12 @@ from twisted.internet.interfaces import IConsumer
class MemoryConsumer: class MemoryConsumer:
implements(IConsumer) implements(IConsumer)
def __init__(self):
def __init__(self, progress=None):
self.chunks = [] self.chunks = []
self.done = False self.done = False
self._progress = progress
def registerProducer(self, p, streaming): def registerProducer(self, p, streaming):
self.producer = p self.producer = p
if streaming: if streaming:
@ -19,12 +22,19 @@ class MemoryConsumer:
else: else:
while not self.done: while not self.done:
p.resumeProducing() p.resumeProducing()
def write(self, data): def write(self, data):
self.chunks.append(data) self.chunks.append(data)
if self._progress is not None:
self._progress.set_progress(sum([len(c) for c in self.chunks]))
def unregisterProducer(self): def unregisterProducer(self):
self.done = True self.done = True
def download_to_data(n, offset=0, size=None): def download_to_data(n, offset=0, size=None, progress=None):
d = n.read(MemoryConsumer(), offset, size) """
:param progress: None or an IProgress implementer
"""
d = n.read(MemoryConsumer(progress=progress), offset, size)
d.addCallback(lambda mc: "".join(mc.chunks)) d.addCallback(lambda mc: "".join(mc.chunks))
return d return d

View File

@ -0,0 +1,37 @@
"""
Utilities relating to computing progress information.
Ties in with the "consumer" module also
"""
from allmydata.interfaces import IProgress
from zope.interface import implementer
@implementer(IProgress)
class PercentProgress(object):
"""
Represents progress as a percentage, from 0.0 to 100.0
"""
def __init__(self, total_size=None):
self._value = 0.0
self.set_progress_total(total_size)
def set_progress(self, value):
"IProgress API"
self._value = value
def set_progress_total(self, size):
"IProgress API"
if size is not None:
size = float(size)
self._total_size = size
@property
def progress(self):
if self._total_size is None:
return 0 # or 1.0?
if self._total_size <= 0.0:
return 0
return (self._value / self._total_size) * 100.0