77 lines
2.6 KiB
Python
77 lines
2.6 KiB
Python
import paramiko
|
|
from blobsend.client_base import BaseChunkClient
|
|
from blobsend import CHUNK_SIZE, hash_chunk
|
|
|
|
|
|
"""
|
|
ssh client
|
|
- assumes this utility (blobcopy) is installed on the remote end
|
|
"""
|
|
|
|
REMOTE_UTILITY = "/Users/dave/code/blobsend/testenv/bin/_blobsend_ssh_remote"#
|
|
|
|
|
|
class SshChunkClient(BaseChunkClient):
|
|
def __init__(self, server, username, password, fpath, is_src, chunk_size=CHUNK_SIZE):
|
|
super().__init__(chunk_size)
|
|
self.fpath = fpath
|
|
self.ssh = paramiko.SSHClient()
|
|
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
self.ssh.connect(hostname=server,
|
|
username=username,
|
|
password=password)
|
|
|
|
self.sftp = self.ssh.open_sftp()
|
|
|
|
# If the file doesnt exist and we are the destination, create it
|
|
if not is_src:
|
|
try:
|
|
with self.sftp.open(self.fpath, "r"):
|
|
pass
|
|
except FileNotFoundError:
|
|
with self.sftp.open(self.fpath, "wb") as f:
|
|
pass
|
|
|
|
# it seems like mode "ab+" doesn't work the same way under paramiko
|
|
# it refuses to seek before the open point (which is the end of the file)
|
|
self.file = self.sftp.open(self.fpath, "r+")
|
|
|
|
def get_hashes(self):
|
|
stdin, stdout, stderr = self.ssh.exec_command("{} chunks {}".format(REMOTE_UTILITY, self.fpath))#TODO safe arg escapes
|
|
stdin.close()
|
|
for line in iter(lambda: stdout.readline(1024), ""):
|
|
chunk_number, chunk_hash = line.strip().split(" ")
|
|
yield (int(chunk_number), chunk_hash, )
|
|
|
|
def get_chunk(self, chunk_number):
|
|
position = chunk_number * self.chunk_size
|
|
if position > self.get_length():
|
|
raise Exception("requested chunk {} is beyond EOF".format(chunk_number))
|
|
self.file.seek(position)#TODO not thread safe
|
|
return self.file.read(self.chunk_size)
|
|
|
|
def put_chunk(self, chunk_number, contents):
|
|
position = chunk_number * self.chunk_size
|
|
self.file.seek(position)
|
|
self.file.write(contents)
|
|
|
|
def get_length(self):
|
|
self.file.seek(0, 2) # seek to end
|
|
return self.file.tell()
|
|
|
|
def set_length(self, length):
|
|
if length < self.get_length():
|
|
self.file.truncate(length)
|
|
# do nothing for the case of extending the file
|
|
# put_chunk handles it
|
|
|
|
def close(self):
|
|
self.file.close()
|
|
|
|
@staticmethod
|
|
def from_uri(uri, is_src):
|
|
"""
|
|
instantiate a client from the given uri
|
|
"""
|
|
return SshChunkClient(uri.hostname, uri.username, uri.password, uri.path, is_src)
|