remotestore.py
153 lines
| 5.1 KiB
| text/x-python
|
PythonLexer
various
|
r15168 | # Copyright 2010-2011 Fog Creek Software | ||
# Copyright 2010-2011 Unity Technologies | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
Mads Kiilerich
|
r17425 | '''remote largefile store; the base class for wirestore''' | ||
liscju
|
r29313 | from __future__ import absolute_import | ||
various
|
r15168 | |||
from mercurial.i18n import _ | ||||
liscju
|
r29313 | from mercurial import ( | ||
error, | ||||
Gregory Szorc
|
r43375 | pycompat, | ||
liscju
|
r29313 | util, | ||
) | ||||
Augie Fackler
|
r43346 | from mercurial.utils import stringutil | ||
Yuya Nishihara
|
r37102 | |||
liscju
|
r29313 | from . import ( | ||
basestore, | ||||
lfutil, | ||||
localstore, | ||||
) | ||||
timeless
|
r28883 | urlerr = util.urlerr | ||
urlreq = util.urlreq | ||||
Augie Fackler
|
r43346 | |||
various
|
r15168 | class remotestore(basestore.basestore): | ||
Greg Ward
|
r15252 | '''a largefile store accessed over a network''' | ||
Augie Fackler
|
r43346 | |||
various
|
r15168 | def __init__(self, ui, repo, url): | ||
super(remotestore, self).__init__(ui, repo, url) | ||||
Boris Feld
|
r35580 | self._lstore = None | ||
if repo is not None: | ||||
self._lstore = localstore.localstore(self.ui, self.repo, self.repo) | ||||
various
|
r15168 | |||
def put(self, source, hash): | ||||
if self.sendfile(source, hash): | ||||
Pierre-Yves David
|
r26587 | raise error.Abort( | ||
Augie Fackler
|
r43347 | _(b'remotestore: could not put %s to remote store %s') | ||
Augie Fackler
|
r43346 | % (source, util.hidepassword(self.url)) | ||
) | ||||
various
|
r15168 | self.ui.debug( | ||
Augie Fackler
|
r43347 | _(b'remotestore: put %s to remote store %s\n') | ||
Augie Fackler
|
r43346 | % (source, util.hidepassword(self.url)) | ||
) | ||||
various
|
r15168 | |||
Na'Tosha Bard
|
r17127 | def exists(self, hashes): | ||
Augie Fackler
|
r43346 | return dict( | ||
(h, s == 0) | ||||
Gregory Szorc
|
r43375 | for (h, s) in pycompat.iteritems( | ||
self._stat(hashes) | ||||
) # dict-from-generator | ||||
Augie Fackler
|
r43346 | ) | ||
various
|
r15168 | |||
def sendfile(self, filename, hash): | ||||
Augie Fackler
|
r43347 | self.ui.debug(b'remotestore: sendfile(%s, %s)\n' % (filename, hash)) | ||
various
|
r15168 | try: | ||
Mads Kiilerich
|
r30142 | with lfutil.httpsendfile(self.ui, filename) as fd: | ||
return self._put(hash, fd) | ||||
Gregory Szorc
|
r25660 | except IOError as e: | ||
Pierre-Yves David
|
r26587 | raise error.Abort( | ||
Augie Fackler
|
r43347 | _(b'remotestore: could not open file %s: %s') | ||
Augie Fackler
|
r43346 | % (filename, stringutil.forcebytestr(e)) | ||
) | ||||
various
|
r15168 | |||
def _getfile(self, tmpfile, filename, hash): | ||||
try: | ||||
Mads Kiilerich
|
r19004 | chunks = self._get(hash) | ||
timeless
|
r28883 | except urlerr.httperror as e: | ||
Pierre-Yves David
|
r26587 | # 401s get converted to error.Aborts; everything else is fine being | ||
various
|
r15168 | # turned into a StoreError | ||
Augie Fackler
|
r43346 | raise basestore.StoreError( | ||
filename, hash, self.url, stringutil.forcebytestr(e) | ||||
) | ||||
timeless
|
r28883 | except urlerr.urlerror as e: | ||
various
|
r15168 | # This usually indicates a connection problem, so don't | ||
# keep trying with the other files... they will probably | ||||
# all fail too. | ||||
Augie Fackler
|
r43346 | raise error.Abort( | ||
Augie Fackler
|
r43347 | b'%s: %s' % (util.hidepassword(self.url), e.reason) | ||
Augie Fackler
|
r43346 | ) | ||
Gregory Szorc
|
r25660 | except IOError as e: | ||
Augie Fackler
|
r43346 | raise basestore.StoreError( | ||
filename, hash, self.url, stringutil.forcebytestr(e) | ||||
) | ||||
various
|
r15168 | |||
Mads Kiilerich
|
r19004 | return lfutil.copyandhash(chunks, tmpfile) | ||
various
|
r15168 | |||
liscju
|
r29218 | def _hashesavailablelocally(self, hashes): | ||
existslocallymap = self._lstore.exists(hashes) | ||||
localhashes = [hash for hash in hashes if existslocallymap[hash]] | ||||
return localhashes | ||||
liscju
|
r29067 | def _verifyfiles(self, contents, filestocheck): | ||
failed = False | ||||
Augie Fackler
|
r43346 | expectedhashes = [ | ||
expectedhash for cset, filename, expectedhash in filestocheck | ||||
] | ||||
liscju
|
r29218 | localhashes = self._hashesavailablelocally(expectedhashes) | ||
Augie Fackler
|
r43346 | stats = self._stat( | ||
[ | ||||
expectedhash | ||||
for expectedhash in expectedhashes | ||||
if expectedhash not in localhashes | ||||
] | ||||
) | ||||
liscju
|
r29218 | |||
liscju
|
r29067 | for cset, filename, expectedhash in filestocheck: | ||
liscju
|
r29218 | if expectedhash in localhashes: | ||
filetocheck = (cset, filename, expectedhash) | ||||
Augie Fackler
|
r43346 | verifyresult = self._lstore._verifyfiles( | ||
contents, [filetocheck] | ||||
) | ||||
liscju
|
r29218 | if verifyresult: | ||
liscju
|
r29067 | failed = True | ||
liscju
|
r29218 | else: | ||
stat = stats[expectedhash] | ||||
if stat: | ||||
if stat == 1: | ||||
self.ui.warn( | ||||
Augie Fackler
|
r43347 | _(b'changeset %s: %s: contents differ\n') | ||
Augie Fackler
|
r43346 | % (cset, filename) | ||
) | ||||
liscju
|
r29218 | failed = True | ||
elif stat == 2: | ||||
self.ui.warn( | ||||
Augie Fackler
|
r43347 | _(b'changeset %s: %s missing\n') % (cset, filename) | ||
Augie Fackler
|
r43346 | ) | ||
liscju
|
r29218 | failed = True | ||
else: | ||||
Augie Fackler
|
r43346 | raise RuntimeError( | ||
Augie Fackler
|
r43347 | b'verify failed: unexpected response ' | ||
b'from statlfile (%r)' % stat | ||||
Augie Fackler
|
r43346 | ) | ||
liscju
|
r29067 | return failed | ||
Na'Tosha Bard
|
r17127 | |||
liscju
|
r28442 | def _put(self, hash, fd): | ||
'''Put file with the given hash in the remote store.''' | ||||
Augie Fackler
|
r43347 | raise NotImplementedError(b'abstract method') | ||
liscju
|
r28442 | |||
def _get(self, hash): | ||||
Mads Kiilerich
|
r30180 | '''Get a iterator for content with the given hash.''' | ||
Augie Fackler
|
r43347 | raise NotImplementedError(b'abstract method') | ||
liscju
|
r28442 | |||
def _stat(self, hashes): | ||||
'''Get information about availability of files specified by | ||||
hashes in the remote store. Return dictionary mapping hashes | ||||
to return code where 0 means that file is available, other | ||||
values if not.''' | ||||
Augie Fackler
|
r43347 | raise NotImplementedError(b'abstract method') | ||