Skip to content

Commit

Permalink
add binary protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
tomgross committed Feb 28, 2024
1 parent 4854018 commit 821e1bf
Show file tree
Hide file tree
Showing 8 changed files with 530 additions and 90 deletions.
124 changes: 43 additions & 81 deletions src/pcloud/api.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import os
import requests
import zipfile

from hashlib import sha1
from io import BytesIO

from pcloud.jsonprotocol import PCloudJSONConnection
from pcloud.oauth2 import TokenHandler
from pcloud.utils import log
from pcloud.validate import MODE_AND
from pcloud.validate import RequiredParameterCheck
from requests_toolbelt.multipart.encoder import MultipartEncoder

from urllib.parse import urlparse
from urllib.parse import urlunsplit

import datetime
import logging
import os.path
import requests
import sys
import zipfile


log = logging.getLogger("pcloud")
log.setLevel(logging.INFO)

handler = logging.StreamHandler(sys.stderr)
handler.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
log.addHandler(handler)

# File open flags https://docs.pcloud.com/methods/fileops/file_open.html
O_WRITE = int("0x0002", 16)
Expand All @@ -47,40 +38,42 @@ class InvalidFileModeError(Exception):
"""File mode not supported"""


# Helpers
def to_api_datetime(dt):
"""Converter to a datetime structure the pCloud API understands
See https://docs.pcloud.com/structures/datetime.html
"""
if isinstance(dt, datetime.datetime):
return dt.isoformat()
return dt


class PyCloud(object):
endpoints = {
"api": "https://api.pcloud.com/",
"eapi": "https://eapi.pcloud.com/",
"test": "http://localhost:5023/",
"test": "localhost:5023",
"binapi": "https://binapi.pcloud.com",
"bineapi": "https://bineapi.pcloud.com",
"nearest": "",
}

def __init__(
self, username, password, endpoint="api", token_expire=31536000, oauth2=False
self, username, password, endpoint="api", token_expire=31536000, oauth2=False,
connection=PCloudJSONConnection
):
self.session = requests.Session()
conn = connection(self)
self.connection = conn.connect()
if endpoint not in self.endpoints:
log.error(
"Endpoint (%s) not found. Use one of: %s",
endpoint,
",".join(self.endpoints.keys()),
", ".join(self.endpoints.keys()),
)
return
elif endpoint == "nearest":
self.endpoint = self.getnearestendpoint()
elif endpoint not in connection.allowed_endpoints:
log.error(
"Endpoint (%s) not in allowed list of '%s'. Use one of: %s",
endpoint,
connection.__name__,
", ".join(connection.allowed_endpoints),
)
return
else:
self.endpoint = self.endpoints.get(endpoint)

log.info(f"Using pCloud API endpoint: {self.endpoint}")
self.username = username.lower().encode("utf-8")
self.password = password.encode("utf-8")
Expand Down Expand Up @@ -122,25 +115,8 @@ def oauth2_authorize(
return cls("", access_token, endpoint, token_expire, oauth2=True)

def _do_request(self, method, authenticate=True, json=True, endpoint=None, **kw):
if authenticate and self.auth_token: # Password authentication
params = {"auth": self.auth_token}
elif authenticate and self.access_token: # OAuth2 authentication
params = {"access_token": self.access_token}
else:
params = {}
if endpoint is None:
endpoint = self.endpoint
params.update(kw)
log.debug("Doing request to %s%s", endpoint, method)
log.debug("Params: %s", params)
resp = self.session.get(endpoint + method, params=params)
if json:
result = resp.json()
else:
result = resp.content
log.debug("Response: %s", result)
return result

return self.connection.do_get_request(method, authenticate, json, endpoint, **kw)

# Authentication
def getdigest(self):
resp = self._do_request("getdigest", authenticate=False)
Expand Down Expand Up @@ -174,8 +150,8 @@ def supportedlanguages(self, **kwargs):
def getnearestendpoint(self):
default_api = self.endpoints.get("api")
resp = self._do_request(
"getapiserver", authenticate=False, endpoint=default_api
)
"getapiserver", authenticate=False, endpoint=default_api)

api = resp.get("api")
if len(api):
return urlunsplit(["https", api[0], "/", "", ""])
Expand Down Expand Up @@ -234,24 +210,11 @@ def copyfolder(self, **kwargs):
raise NotImplementedError

# File
def _upload(self, method, files, **kwargs):
if self.auth_token: # Password authentication
kwargs["auth"] = self.auth_token
elif self.access_token: # OAuth2 authentication
kwargs["access_token"] = self.access_token
fields = list(kwargs.items())
fields.extend(files)
m = MultipartEncoder(fields=fields)
resp = requests.post(
self.endpoint + method, data=m, headers={"Content-Type": m.content_type}
)
return resp.json()

@RequiredParameterCheck(("files", "data"))
def uploadfile(self, **kwargs):
"""upload a file to pCloud
1) You can specify a list of filenames to read
1) You can specify a list of filenames to upload
files=['/home/pcloud/foo.txt', '/home/pcloud/bar.txt']
2) you can specify binary data via the data parameter and
Expand All @@ -276,7 +239,7 @@ def uploadfile(self, **kwargs):
if "folderid" in kwargs:
# cast folderid to string, since API allows this but requests not
kwargs["folderid"] = str(kwargs["folderid"])
return self._upload("uploadfile", files, **kwargs)
return self.connection.upload("uploadfile", files, **kwargs)

@RequiredParameterCheck(("progresshash",))
def uploadprogress(self, **kwargs):
Expand Down Expand Up @@ -365,54 +328,53 @@ def gettextfile(self, **kwargs):
# File API methods
@RequiredParameterCheck(("flags",))
def file_open(self, **kwargs):
return self._do_request("file_open", **kwargs)
return self._do_request("file_open", use_session=True, **kwargs)

@RequiredParameterCheck(("fd", "count"))
def file_read(self, **kwargs):
return self._do_request("file_read", json=False, **kwargs)
return self._do_request("file_read", json=False, use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_pread(self, **kwargs):
return self._do_request("file_pread", json=False, **kwargs)
return self._do_request("file_pread", json=False, use_session=True, **kwargs)

@RequiredParameterCheck(("fd", "data"))
def file_pread_ifmod(self, **kwargs):
return self._do_request("file_pread_ifmod", json=False, **kwargs)
return self._do_request("file_pread_ifmod", json=False, use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_size(self, **kwargs):
return self._do_request("file_size", **kwargs)
return self._do_request("file_size", use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_truncate(self, **kwargs):
return self._do_request("file_truncate", **kwargs)
return self._do_request("file_truncate", use_session=True, **kwargs)

@RequiredParameterCheck(("fd", "data"))
def file_write(self, **kwargs):
files = [("file", ("upload-file.io", BytesIO(kwargs.pop("data"))))]
kwargs["fd"] = str(kwargs["fd"])
return self._upload("file_write", files, **kwargs)
# return self._do_request("file_write", **kwargs)
return self.connection.upload("file_write", files, **kwargs)

@RequiredParameterCheck(("fd",))
def file_pwrite(self, **kwargs):
return self._do_request("file_pwrite", **kwargs)
return self._do_request("file_pwrite", use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_checksum(self, **kwargs):
return self._do_request("file_checksum", **kwargs)
return self._do_request("file_checksum", use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_seek(self, **kwargs):
return self._do_request("file_seek", **kwargs)
return self._do_request("file_seek", use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_close(self, **kwargs):
return self._do_request("file_close", **kwargs)
return self._do_request("file_close", use_session=True, **kwargs)

@RequiredParameterCheck(("fd",))
def file_lock(self, **kwargs):
return self._do_request("file_lock", **kwargs)
return self._do_request("file_lock", use_session=True, **kwargs)

# Archiving
@RequiredParameterCheck(("path", "fileid"))
Expand Down
Loading

0 comments on commit 821e1bf

Please sign in to comment.