185 lines
5.4 KiB
Python
Executable File
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
|