Files
stacks-puppet-node/demos/python-filesharing/blockstack_files/bsk.py
2017-03-01 23:07:20 -05:00

395 lines
11 KiB
Python

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
This code is public domain.
Used as a demo for Bockstack.
"""
import requests
import urllib
import os
import sys
# need to pip install these
import blockstack_client as bsk
import jsontokens
import keylib
# application domain name (identifies the specific data store)
APPLICATION_NAME = 'files.blockstack.org'
# list of API families
APPLICATION_METHODS = [
'store_read',
'store_write',
'store_admin',
]
# ECDSA private key (different each time)
APPLICATION_KEY = keylib.ECPrivateKey().to_hex()
def bsk_get_url( path, port=6270 ):
"""
Make a URL to the local blockstack node
"""
return 'http://localhost:{}{}'.format(port, path)
def bsk_auth_headers( session ):
"""
Make authorization headers from a session token
"""
return {'Authorization': 'bearer {}'.format(session)}
def bsk_get_session( api_password, port=6270 ):
"""
Connect to the local blockstack node.
Get back a session.
Return the session (a JWT string) on success
Raise on error
"""
# will call `GET http://localhost:{port}/v1/auth?authRequest={auth JWT}`
# will get back a session JWT
# request permission to access the API
auth_request = {
'app_domain': APPLICATION_NAME,
'methods': APPLICATION_METHODS,
}
# authentication: basic {password}
headers = {
'Authorization': 'basic {}'.format(api_password)
}
# make the authentication token
signer = jsontokens.TokenSigner()
auth_token = signer.sign(auth_request, APPLICATION_KEY)
# ask for a session token
url = bsk_get_url('/v1/auth?authRequest={}'.format(auth_token), port=port)
req = requests.get(url, headers=headers )
if req.status_code == 200:
# good to go!
# expect {'token': ses token} JSON response
payload = req.json()
session = payload['token']
return session
else:
# whoops!
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_make_datastore( session, drivers=None, port=6270 ):
"""
Make our data store. The data store will be specific
to this application's name (APPLICATION_NAME).
This method is idempotent. If the data store already exists,
then no action will be taken (except to query the ID)
Return the datastore ID on success (even if it already exists).
Raise on error
"""
# create a datastore with `POST http://localhost:{port}/v1/stores?drivers={drivers}`
# get back {'status': True, 'app_user_id': datastore ID}
# does this data store already exist?
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}'.format(APPLICATION_NAME), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# the data store already exists. Give back it's ID
datastore_info = req.json()
datastore_id = datastore_info['datastore_id']
return datastore_id
elif req.status_code != 404:
# some other error
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
# doesn't exist yet. Go create it.
url = bsk_get_url('/v1/stores', port=port)
if drivers is not None:
# include requested drivers
url += '?drivers={}'.format( urllib.quote(','.join(drivers)) )
req = requests.post(url, headers=auth_headers)
if req.status_code == 200:
# succeeded!
# get back the ID
datastore_res = req.json()
datastore_id = datastore_res['datastore_id']
return datastore_id
else:
# something broke!
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_delete_datastore( session, port=6270 ):
"""
Delete our data store.
Return True on success
Raise on error
"""
# delete datastore with `DELETE http://localhost:{port}/v1/stores`
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores', port=port)
req = requests.delete(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_put_file( session, datastore_id, local_path, bsk_path, port=6270 ):
"""
Put the file data pointed to by {local_path} into
{bsk_path}
Return True on success
Raise on error
"""
# put the file with `POST http://localhost:{port}/v1/stores/{store ID}/files?path={bsk_path}`
# get back {'status': True}
# get data from disk
with open(local_path, 'r') as f:
data = f.read()
# issue the request to store
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/files?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.post(url, headers=auth_headers, data=data)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_get_file( session, datastore_id, bsk_path, port=6270, file=sys.stdout ):
"""
Get the file data pointed to by {bsk_path} from the data store.
Write it to stdout by default (override with file=)
Return True on success
Raise on error
"""
# get the file with `GET http://localhost:{port}/v1/stores/{store ID}/files?path={bsk_path}`
# get back raw data (application/octet-stream)
# issue the request to fetch
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/files?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# success!
print >> file, req.content
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_delete_file( session, datastore_id, bsk_path, port=6270 ):
"""
Delete the file data pointed to by {bsk_path} from the data store.
Return True on success
Raise on error
"""
# delete with `DELETE http://localhost:{port}/v1/stores/{store ID}/files?path={bsk_path)`
# get back {'status': True}
# issue the request
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/files?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.delete(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_mkdir( session, datastore_id, bsk_path, port=6270 ):
"""
Make a directory at {bsk_path} in the given data store
Return True on success
Raise on error
"""
# make the directory with `POST http://localhost:{port}/v1/stores/{store ID}/directories?path={bsk_path}`
# get back {'status': True} (http 200)
# issue the request to fetch
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/directories?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.post(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_listdir( session, datastore_id, bsk_path, port=6270, file=sys.stdout ):
"""
List a directory at {bsk_path} in the given data store.
Write the listing to stdout.
Return True on success
Raise on error
"""
# list the directory with `GET http://localhost:{port}/v1/stores/{store ID}/directories?path={bsk_path}`
# get back the structured inode of the directory.
# iterate through the inode's children and print their names and types (i.e. append '/' to directories)
# issue the request to fetch
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/directories?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# success!
dir_listing = req.json()
# extract names and types
names = []
for name in dir_listing.keys():
dirent = dir_listing[name]
if dirent['type'] == 2: # this is a file
name += '/'
names.append(name)
names.sort()
print >> file, '\n'.join(names)
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_rmdir( session, datastore_id, bsk_path, port=6270 ):
"""
Remove a directory at {bsk_path} in the given data store.
Return True on success
Raise on error
"""
# delete the directory with `DELETE http://localhost:{port}/v1/stores/{store ID}/directories?path={bsk_path}`
# issue the request
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/directories?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.delete(url, headers=auth_headers)
if req.status_code == 200:
# success!
return True
else:
raise Exception("HTTP status {} from Blockstack on {}".format(req.status_code, url))
def bsk_stat( session, datastore_id, bsk_path, port=6270 ):
"""
Stat a file or directory
Return the inode structure on success
Raise on error
"""
# send `GET http://localhost:{port}/v1/stores/{store ID}/inodes?path={bsk_path`
# issue the request
auth_headers = bsk_auth_headers(session)
url = bsk_get_url('/v1/stores/{}/inodes?path={}'.format(datastore_id, urllib.quote(bsk_path)), port=port)
req = requests.get(url, headers=auth_headers)
if req.status_code == 200:
# success!
return req.json()
else:
raise Exception('HTTP status {} from Blockstack on {}'.format(req.status_code, url))
if __name__ == '__main__':
# unit tests!
import StringIO
api_password = sys.argv[1]
ses = bsk_get_session(api_password)
assert ses, "Failed to authentcate"
datastore_id = bsk_make_datastore(ses, ['disk'])
assert datastore_id, "Failed to make datastore"
# load something
with open("/tmp/.footest", 'w') as f:
f.write("hello world\x00\x01\x02\x03\x04")
res = bsk_put_file(ses, datastore_id, "/tmp/.footest", "/foo")
assert res, "Failed to put file"
# get it back
sb = StringIO.StringIO()
res = bsk_get_file(ses, datastore_id, "/foo", file=sb)
assert res, "Failed to get file"
assert sb.getvalue() == "hello world\x00\x01\x02\x03\x04\n", "Got wrong data ({})".format(sb.getvalue())
# make a directory
res = bsk_mkdir(ses, datastore_id, "/bar")
assert res, "Failed to mkdir"
# list directory
sb = StringIO.StringIO()
res = bsk_listdir(ses, datastore_id, "/", file=sb)
assert res, "Failed to listdir"
assert sb.getvalue() == "bar/\nfoo\n", "Got wrong dir ({})".format(sb.getvalue())
# delete file
res = bsk_delete_file(ses, datastore_id, '/foo')
assert res
# delete directory
res = bsk_rmdir(ses, datastore_id, '/bar')
assert res
# delete datastore
res = bsk_delete_datastore(ses)
assert res