This repository has been archived on 2019-09-18. You can view files and clone it, but cannot push or open issues or pull requests.
apt-transport-ipfs/client/ipfs

185 lines
5.4 KiB
Python
Executable File

#!/usr/bin/python2.7 -u
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2014-2015, 2017 Bashton Ltd
# Copyright (C) 2017 JaquerEspeis
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# IPFS transport for apt.
# This is based on apt-transport-s3:
# https://github.com/BashtonLtd/apt-transport-s3
import hashlib
import os
import sys
import ipfsapi
class IPFS_method():
__eof = False
def __init__(self):
self.send_capabilities()
def send_capabilities(self):
self._send(100, {
'Version': '1.1',
'Single-Instance': 'true'})
def send_status(self, headers):
self._send(102, headers)
def send_uri_start(self, headers):
self._send(200, headers)
def send_uri_done(self, headers):
self._send(201, headers)
def send_uri_failure(self, headers):
self._send(400, headers)
def _send(self, code, headers):
message = APTMessage(code, headers)
sys.stdout.write(message.encode())
def run(self):
"""Loop through requests on stdin"""
while True:
message = self._read_message()
if message is None:
return 0
if message['number'] == 600:
uri = message['URI'][0]
filename = message['Filename'][0]
try:
self.fetch(uri, filename)
except Exception as e:
self.send_uri_failure({
'URI': uri,
'Message': e.__class__.__name__ + ": " + str(e)})
else:
return 100
def _read_message(self):
"""Read an apt message.
Apt uses for communication with its methods the text protocol similar
to http. This function parses the protocol messages from stdin.
"""
if self.__eof:
return None
result = {}
line = sys.stdin.readline()
while line == '\n':
line = sys.stdin.readline()
if not line:
self.__eof = True
return None
s = line.split(" ", 1)
result['number'] = int(s[0])
result['text'] = s[1].strip()
while not self.__eof:
line = sys.stdin.readline()
if not line:
self.__eof = True
return result
if line == '\n':
return result
(item, value) = line.split(":", 1)
if not result.get(item):
result[item] = []
result[item].append(value.strip())
return result
def fetch(self, uri, filename):
ipfs_file_path = uri.replace('ipfs:', '', 1)
self.send_status({'URI': uri, 'Message': 'Waiting for stats'})
ipfs = ipfsapi.connect('127.0.0.1', 5001)
stat = ipfs.object_stat(ipfs_file_path)
self.send_uri_start({
'URI': uri,
# FIXME We can't get the real size without downloading the file.
# https://github.com/ipfs/go-ipfs/issues/2071
# --elopio - 20171203
'Size': stat['CumulativeSize']})
# XXX IPFS downloads the file to the current directory.
# --elopio - 20171203
cwd = os.getcwd()
os.chdir(os.path.dirname(filename))
try:
ipfs.get(ipfs_file_path)
os.rename(
os.path.basename(ipfs_file_path),
filename)
finally:
os.chdir(cwd)
hash_md5 = hashlib.md5()
hash_sha256 = hashlib.sha256()
hash_sha512 = hashlib.sha512()
with open(filename, 'rb') as fetched_file:
for chunk in iter(lambda: fetched_file.read(4096), b''):
hash_md5.update(chunk)
hash_sha256.update(chunk)
hash_sha512.update(chunk)
self.send_uri_done({
'URI': uri,
'Filename': filename,
'Size': os.stat(filename).st_size,
'MD5-Hash': hash_md5.hexdigest(),
'MD5Sum-Hash': hash_md5.hexdigest(),
'SHA256-Hash': hash_sha256.hexdigest(),
'SHA512-Hash': hash_sha512.hexdigest()})
class APTMessage():
_MESSAGE_CODES = {
100: 'Capabilities',
102: 'Status',
200: 'URI Start',
201: 'URI Done',
400: 'URI Failure',
600: 'URI Acquire',
601: 'Configuration'
}
def __init__(self, code, headers):
self._code = code
self._headers = headers
def encode(self):
result = '{0} {1}\n'.format(
self._code, self._MESSAGE_CODES[self._code])
for header_key in self._headers:
if self._headers[header_key] is not None:
result += '{0}: {1}\n'.format(
header_key, self._headers[header_key])
return result + '\n'
if __name__ == '__main__':
try:
method = IPFS_method()
exitcode = method.run()
sys.exit(exitcode)
except KeyboardInterrupt:
pass