Browse Source

Switched to ipfshttpclient (vendored)

main
Victor Roest 9 months ago
parent
commit
996d2f4ce1
Signed by: 0x76 GPG Key ID: A3923C699D1A3BDA
  1. 3
      .gitmodules
  2. 13
      Pipfile
  3. 67
      Pipfile.lock
  4. 3
      ipfs_share/__init__.py
  5. 0
      ipfs_share/api.py
  6. 28
      ipfs_share/cli.py
  7. 15
      ipfs_share/clipboard.py
  8. 133
      ipfs_share/ipfs_share.py
  9. 0
      ipfs_share/ipfshttpclient

3
.gitmodules

@ -1,3 +1,6 @@
[submodule "ipfshttpclient"]
path = ipfshttpclient
url = git@github.com:NULLx76/py-ipfs-http-client.git
[submodule "ipfs_share/ipfshttpclient"]
path = ipfs_share/ipfshttpclient
url = git@github.com:NULLx76/py-ipfs-http-client.git

13
Pipfile

@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "~=2.25.1"
future = "~=0.18.2"
[dev-packages]
[requires]
python_version = "3.9"

67
Pipfile.lock

@ -0,0 +1,67 @@
{
"_meta": {
"hash": {
"sha256": "7cc5e9f90baae308f19164ace8c8d927e3f145365bc78a157fd6681ee2a19eae"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
],
"version": "==2020.12.5"
},
"chardet": {
"hashes": [
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"future": {
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"index": "pypi",
"version": "==0.18.2"
},
"idna": {
"hashes": [
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.10"
},
"requests": {
"hashes": [
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
],
"index": "pypi",
"version": "==2.25.1"
},
"urllib3": {
"hashes": [
"sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
"sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.4"
}
},
"develop": {}
}

3
ipfs_share/__init__.py

@ -1,2 +1 @@
from .ipfs_share import ipfs_share, ShareOptions
from .pinner import RemotePinner, RemotePinnerType
from ipfs_share.ipfshttpclient import ipfshttpclient

0
ipfs_share/api.py

28
ipfs_share/cli.py

@ -1,10 +1,8 @@
import os
import sys
import argparse
from urllib.parse import urlparse
from .pinner import RemotePinner, RemotePinnerType
from .ipfs_share import ShareOptions, ipfs_share
from ipfs_share.ipfs_share import ipfs_share
GATEWAYS = ["https://cloudflare-ipfs.com", "https://ipfs.xirion.net"]
@ -30,30 +28,28 @@ def url_t(url: str) -> str:
def main():
gateways = [url_t(x) for x in env_gateways.split()] if (env_gateways := os.environ.get("IPFS_GATEWAYS")) is not None else None
remote_pinner_url = url_t(env_remote_pinner) if (env_remote_pinner := os.environ.get("IPFS_REMOTE_PINNER")) is not None else None
pinner_type = RemotePinnerType(env_pinner_type) if (env_pinner_type := os.environ.get("IPFS_REMOTE_PINNER_TYPE")) is not None else RemotePinnerType.Node
# remote_pinner_url = url_t(env_remote_pinner) if (env_remote_pinner := os.environ.get("IPFS_REMOTE_PINNER")) is not None else None
# pinner_type = RemotePinnerType(env_pinner_type) if (env_pinner_type := os.environ.get("IPFS_REMOTE_PINNER_TYPE")) is not None else RemotePinnerType.Node
parser = argparse.ArgumentParser(description="Share a file using IPFS")
parser.add_argument("path", type=path_t, help="the file or folder to share")
parser.add_argument("--no-clipboard", action="store_true", help="disable clipboard support")
parser.add_argument("--nocopy", action="store_true", help="Use the experimental ipfs 'no copy' feature")
parser.add_argument("-p", "--pin", action="store_true", help="Pin file/folder to a remote pinner")
parser.add_argument("-g", "--gateway", metavar="URL", action="append", type=url_t, default=gateways,
help="gateway(s) to use for url generation (repetition allowed). You can also use the 'IPFS_GATEWAYS' environment variable")
parser.add_argument("-r", "--remote-pinner-url", metavar="URL", type=url_t, default=remote_pinner_url,
required="-p" in sys.argv and remote_pinner_url is None,
help="Url of a remote pinner. You can also use the 'IPFS_REMOTE_PINNER' environment variable. Required when using '--pin'")
parser.add_argument("-t", "--pinner-type", type=RemotePinnerType, default=pinner_type, choices=list(RemotePinnerType),
help="Remote pinner type to use. You can also use the 'IPFS_REMOTE_PINNER_TYPE' environment variable. Defaults to node")
# parser.add_argument("-p", "--pin", action="store_true", help="Pin file/folder to a remote pinner")
# parser.add_argument("-r", "--remote-pinner-url", metavar="URL", type=url_t, default=remote_pinner_url,
# required="-p" in sys.argv and remote_pinner_url is None,
# help="Url of a remote pinner. You can also use the 'IPFS_REMOTE_PINNER' environment variable. Required when using '--pin'")
#
# parser.add_argument("-t", "--pinner-type", type=RemotePinnerType, default=pinner_type, choices=list(RemotePinnerType),
# help="Remote pinner type to use. You can also use the 'IPFS_REMOTE_PINNER_TYPE' environment variable. Defaults to node")
args = parser.parse_args()
pinner = RemotePinner.from_type(args.pinner_type, args.remote_pinner_url)
ipfs_share(ShareOptions(args.path, not args.no_clipboard, args.gateway or GATEWAYS, args.pin, pinner, args.nocopy))
# pinner = RemotePinner.from_type(args.pinner_type, args.remote_pinner_url)
ipfs_share(args.path, not args.no_clipboard, args.gateway or GATEWAYS)
if __name__ == "__main__":

15
ipfs_share/clipboard.py

@ -0,0 +1,15 @@
# Try importing Tk for clipboard support
try:
from tkinter import Tk
except ImportError:
Tk = None
def copy_to_clipboard(string: str):
if Tk is not None:
r = Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append(string)
r.update()
r.destroy()

133
ipfs_share/ipfs_share.py

@ -1,102 +1,79 @@
#!/usr/bin/env python3
import subprocess
import os
from .pinner import RemotePinner
from __future__ import annotations
from dataclasses import dataclass
from shutil import which
from typing import Tuple, List
import os
from urllib.parse import urljoin
# Try importing Tk for clipboard support
try:
from tkinter import Tk
except ImportError:
Tk = None
@dataclass
class ShareOptions:
target: str
enable_clipboard: bool
gateways: List[str]
pin: bool
pinner: RemotePinner
no_copy: bool
from ipfs_share import ipfshttpclient as ipfs
from ipfs_share.clipboard import copy_to_clipboard
BASE_FOLDER = "/ipfs-share"
# Upload file to ipfs
def upload_file(file: str, ipfs: str, no_copy: bool) -> Tuple[str, str]:
# ipfs subcommand
# -w: Wrap, wraps the file in a folder, allows linking to the filename
# -q: Quiet, gives better parsable output
cmd = [ipfs, "add", "-wq", file]
if no_copy:
cmd.insert(3, "--nocopy")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise Exception(f"ipfs command failed:\n\n{result.stderr}")
class IpfsShare:
def __init__(self, addr: str = ipfs.DEFAULT_ADDR, base_folder: str = BASE_FOLDER):
self._client = ipfs.connect(addr=addr)
self._base_folder = base_folder
folder_hash = result.stdout.splitlines()[-1]
file_name = os.path.basename(file)
def __enter__(self) -> IpfsShare:
return self
return str(folder_hash), f"{folder_hash}/{file_name}"
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
self._client.close()
# Upload folder to ipfs, returns (folder_cid, file_cid)
def upload_folder(path: str, ipfs: str, no_copy: bool) -> str:
# ipfs subcommand
# -r: Recursive, required for folders
# -q: Quiet, gives better parsable output
cmd = [ipfs, "add", "-rq", path]
if no_copy:
cmd.insert(3, "--nocopy")
def root_cid(self):
self._client.files.mkdir(self._base_folder, parents=True)
return self._client.files.stat(self._base_folder)['Hash']
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise Exception(f"ipfs command failed:\n\n{result.stderr}")
def add(self, path: str) -> str:
if os.path.isfile(path):
return self.add_file(path)
elif os.path.isdir(path):
return self.add_folder(path)
else:
raise ValueError("Path must be a file or folder")
folder_hash = result.stdout.splitlines()[-1]
return str(folder_hash)
def add_file(self, path: str) -> str:
if not os.path.isfile(path):
raise ValueError("Path must be a file")
filename = os.path.basename(path)
mfs_path = os.path.join(BASE_FOLDER, filename)
self._client.files.mkdir(os.path.split(mfs_path)[0], parents=True)
# Upload folder or file to ipfs, returns (folder_cid, file_cid)
def upload(path: str, ipfs: str = "ipfs", no_copy: bool = False) -> Tuple[str, str]:
if os.path.isdir(path):
cid = upload_folder(path, ipfs, no_copy)
return cid, cid
elif os.path.isfile(path):
return upload_file(path, ipfs, no_copy)
else:
raise ValueError(f"{path} is an invalid path to file or dir")
with open(os.path.abspath(path), "rb") as f:
self._client.files.write(mfs_path, f, create=True)
return f"{self.root_cid()}/{filename}"
def copy_to_clipboard(string: str):
if Tk is not None:
r = Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append(string)
r.update()
r.destroy()
def add_folder(self, path: str) -> str:
if not os.path.isdir(path):
raise ValueError("Path must be a directory")
abs_path = os.path.abspath(path)
prefix = os.sep.join(abs_path.split(os.sep)[:-1])
def ipfs_share(options: ShareOptions):
if (ipfs := which("ipfs")) is None:
raise Exception("ipfs binary could not be found")
for dirpath, _, filenames in os.walk(abs_path):
for file in filenames:
path = os.path.join(dirpath, file)
mfs_path = BASE_FOLDER + path.removeprefix(prefix)
self._client.files.mkdir(os.path.dirname(mfs_path), parents=True)
with open(path, "rb") as f:
self._client.files.write(mfs_path, f, create=True)
folder_cid, file_cid = upload(options.target, ipfs)
return f"{self.root_cid()}/{abs_path.split(os.sep)[-1]}"
if options.pin:
options.pinner.pin(folder_cid)
urls = [urljoin(g, f"ipfs/{file_cid}") for g in options.gateways]
def ipfs_share(path: str, clipboard: bool, gateways: [str]):
with IpfsShare() as share:
cid = share.add(path)
if options.enable_clipboard:
urls = [urljoin(g, f"ipfs/{cid}") for g in gateways]
if clipboard:
copy_to_clipboard(urls[-1])
print(f"CID: {folder_cid}")
for el in urls:
print(el)
print(f"CID: {cid}")
for url in urls:
print(url)

0
ipfshttpclient → ipfs_share/ipfshttpclient

Loading…
Cancel
Save