483 lines
18 KiB
Python
Executable File
483 lines
18 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import argparse,json,sys,os
|
|
from shutil import copyfile
|
|
from tabulate import tabulate
|
|
from datetime import datetime
|
|
from utils.utils import *
|
|
from utils.crypt import *
|
|
|
|
def get_root_path(opts):
|
|
root_folder = os.path.dirname(
|
|
os.path.dirname(
|
|
os.path.abspath(
|
|
opts.config
|
|
)
|
|
)
|
|
)
|
|
return root_folder
|
|
|
|
|
|
def list_shares(shares,opts):
|
|
table = []
|
|
table.append(('Name', 'Path','Public','Password','PassHash','Upload','Overwrite','Direct','Expire','Recipient','Description'))
|
|
for share in shares:
|
|
public = get_or_none('public',share, False)
|
|
passhash = '-'
|
|
password = 'pass_hash' in share
|
|
if opts.show_password:
|
|
if 'pass_plain' in share:
|
|
password = share['pass_plain']
|
|
else:
|
|
password = ""
|
|
if 'pass_hash' in share:
|
|
passhash = share['pass_hash']
|
|
else:
|
|
passhash = "-"
|
|
upload = get_or_none('upload',share, False)
|
|
overwrite = get_or_none('overwrite',share, True)
|
|
direct = get_or_none('direct_links',share, False) if password else False
|
|
expire = get_or_none('expire',share, "-")
|
|
description = get_or_none('description',share, "")[0:20]
|
|
table.append((
|
|
share['name'],
|
|
share['path']+"/",
|
|
public,
|
|
password,
|
|
passhash,
|
|
upload,
|
|
overwrite,
|
|
direct,
|
|
expire,
|
|
get_or_none('recipient', share, "")[0:20],
|
|
description
|
|
))
|
|
print(tabulate(table, headers = "firstrow"))
|
|
|
|
|
|
def list_folders(shares,config):
|
|
if 'data_folder' not in config:
|
|
print("data_folder not defined in config")
|
|
sys.exit(1)
|
|
data_folder = os.path.join(config['__root_path__'], config['data_folder'])
|
|
folders = sorted(os.listdir(data_folder))
|
|
table = []
|
|
table.append( ('Path','Share','Size','Unit') )
|
|
for folder in folders:
|
|
full_path = os.path.join(data_folder, folder)
|
|
if not os.path.isdir(full_path):
|
|
continue
|
|
share_name = "[unused by any share]"
|
|
for share in shares:
|
|
share_path = os.path.join(data_folder, share['path'])
|
|
if not os.path.exists(share_path):
|
|
break
|
|
if os.path.samefile(full_path, share_path):
|
|
share_name = share['name']
|
|
break
|
|
(size_num, size_unit) = file_size_human(get_folder_size(
|
|
full_path
|
|
)).split(" ")
|
|
table.append((
|
|
folder,
|
|
share_name,
|
|
size_num,
|
|
size_unit
|
|
))
|
|
print(tabulate(table, headers = "firstrow"))
|
|
|
|
def add_share(shares, config, opts):
|
|
|
|
share = {
|
|
'name': opts.name,
|
|
'path': opts.path,
|
|
'public': opts.public,
|
|
'upload': opts.upload,
|
|
'overwrite': opts.overwrite,
|
|
'direct_links': opts.direct,
|
|
'description': opts.description,
|
|
'recipient': opts.recipient
|
|
}
|
|
|
|
if opts.password:
|
|
if opts.plain:
|
|
share['pass_plain'] = opts.password
|
|
share['pass_hash'] = password_hash(opts.password, config['app_secret_key'])
|
|
if opts.expire:
|
|
try:
|
|
date_object = datetime.strptime(opts.expire,"%Y-%m-%d %H:%M")
|
|
except ValueError as e:
|
|
print(e)
|
|
print("Date format error")
|
|
sys.exit(1)
|
|
share.update({
|
|
'expire': opts.expire
|
|
})
|
|
|
|
if opts.write:
|
|
shares.append(share)
|
|
shares_file = os.path.join(config['__root_path__'], opts.shares_file)
|
|
if os.path.exists(shares_file):
|
|
print("creating backup %s"%(shares_file+".bkp",))
|
|
copyfile(
|
|
shares_file,
|
|
shares_file+".bkp"
|
|
)
|
|
with open(shares_file,'wt') as fp:
|
|
json.dump(shares, fp, indent = 2, sort_keys = True)
|
|
print("Wrote file %s"%(shares_file,))
|
|
print("Add share: %s"%( opts.name, ))
|
|
else:
|
|
print("Share not saved anywhere. Save with -w")
|
|
print(json.dumps(share, indent = 2, sort_keys = True))
|
|
|
|
|
|
def modify_share(shares, config, opts):
|
|
|
|
print("Modifying share: %s"%( opts.name, ))
|
|
found = False
|
|
for i,share in enumerate(shares):
|
|
if share['name'] != opts.name:
|
|
continue
|
|
orig_share = share.copy()
|
|
print(json.dumps(share, indent = 2, sort_keys = True))
|
|
found = True
|
|
break
|
|
if not found:
|
|
print('no such share')
|
|
sys.exit(1)
|
|
if opts.path != None:
|
|
share['path'] = opts.path
|
|
for attr in ('public','upload','direct_links','overwrite'):
|
|
if getattr(opts,attr) != None:
|
|
share[attr] = getattr(opts,attr) == 'true'
|
|
if opts.description != None:
|
|
share['description'] = opts.description
|
|
if opts.recipient != None:
|
|
share['recipient'] = opts.recipient
|
|
# REMOVE password
|
|
if opts.password == "":
|
|
if 'pass_plain' in share:
|
|
del share['pass_plain']
|
|
if 'pass_hash' in share:
|
|
del share['pass_hash']
|
|
|
|
if opts.password:
|
|
# ADD/Change a password
|
|
if opts.plain:
|
|
share['pass_plain'] = opts.password
|
|
share['pass_hash'] = password_hash(opts.password, config['app_secret_key'])
|
|
|
|
if opts.expire:
|
|
if opts.expire == "":
|
|
# REMOVE EXPIRATION
|
|
if 'expire' in share:
|
|
del share['expire']
|
|
else:
|
|
# ADD/CHANGE EXPIRATION
|
|
try:
|
|
date_object = datetime.strptime(opts.expire,"%Y-%m-%d %H:%M")
|
|
except ValueError as e:
|
|
print(e)
|
|
print("Date format error")
|
|
sys.exit(1)
|
|
share['expire'] = opts.expire
|
|
|
|
if opts.write:
|
|
shares[i] = share
|
|
shares_file = os.path.join(config['__root_path__'], opts.shares_file)
|
|
if os.path.exists(shares_file):
|
|
print("creating backup %s"%(shares_file+".bkp",))
|
|
copyfile(
|
|
shares_file,
|
|
shares_file+".bkp"
|
|
)
|
|
with open(shares_file,'wt') as fp:
|
|
json.dump(shares, fp, indent = 2, sort_keys = True)
|
|
print("Wrote file %s"%(shares_file,))
|
|
else:
|
|
print("Share not saved anywhere. Save with -w")
|
|
modified = []
|
|
for key in share:
|
|
if not key in orig_share:
|
|
modified.append(key)
|
|
continue
|
|
if orig_share[key] != share[key]:
|
|
modified.append(key)
|
|
continue
|
|
for key in orig_share:
|
|
if not key in share:
|
|
modified.append(key)
|
|
print("Modified values: %s"%(", ".join(modified)))
|
|
print(json.dumps(share, indent = 2, sort_keys = True))
|
|
|
|
|
|
def remove_share(shares,config,opts):
|
|
name = opts.name
|
|
share = [share for share in shares if share['name'] == name]
|
|
for share_ in share:
|
|
print("Removing share: %s"%( name, ))
|
|
print(json.dumps(share_, indent = 2, sort_keys = True))
|
|
|
|
if opts.write:
|
|
shares = [share for share in shares if share['name'] != name]
|
|
|
|
shares_file = os.path.join(config['__root_path__'], opts.shares_file)
|
|
print("creating backup %s"%(shares_file+".bkp",))
|
|
copyfile(shares_file, shares_file+".bkp")
|
|
with open(shares_file,'wt') as fp:
|
|
json.dump(shares, fp, indent = 2, sort_keys = True)
|
|
print("Removed %s from %s"%(name, shares_file))
|
|
else:
|
|
print("Share was not actually removed. Use -w to rewrite shares file.")
|
|
|
|
|
|
def print_rest_api(shares, config, opts):
|
|
if 'public_url' not in config:
|
|
print("Set public_url variable in your config.json")
|
|
sys.exit(1)
|
|
shares = [share for share in shares if share['name'] == opts.name]
|
|
if len(shares) == 0:
|
|
print("No such share %s"%( opts.name, ))
|
|
sys.exit(1)
|
|
share = shares[0]
|
|
|
|
if opts.type == "list":
|
|
print("Link to enter the share:")
|
|
print("%s/list/%s"%(
|
|
config['public_url'],
|
|
share['name']
|
|
))
|
|
return
|
|
if not 'pass_hash' in share:
|
|
print("REST API enabled only if pass_hash is set for share")
|
|
sys.exit(1)
|
|
crypter = Crypto(config['app_secret_key'])
|
|
crypted = crypter.encrypt(share['pass_hash'])
|
|
if opts.type == "login":
|
|
print("Link to automatically login in the share:")
|
|
print("%s/list/%s/%s"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
elif opts.type == "upload":
|
|
if 'upload' not in share or not share['upload']:
|
|
print("Uploading not allowed to this share")
|
|
sys.exit(0)
|
|
|
|
print("Link to upload file to the share:")
|
|
print("\n# curl -F file=@'the_file_name.ext' %s/upload/%s/%s"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
print("\nLink to upload multiple files to the share:")
|
|
print("\n# curl -s %s/script/upload/%s/%s | bash /dev/stdin file_to_upload.ext [second.file.ext]"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
print("\nLink to upload multiple files to the share, splitting large files (default 512Mb):")
|
|
print("\n# curl -s %s/script/upload_split/%s/%s | bash /dev/stdin [-s split_size_in_Mb] file_to_upload.ext [second.file.ext]"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
elif opts.type == "download":
|
|
print("Links to download files:")
|
|
share_path = os.path.join(
|
|
config['__root_path__'],
|
|
config['data_folder'],
|
|
share['path']
|
|
)
|
|
if not os.path.exists(share_path):
|
|
print("no files")
|
|
sys.exit(0)
|
|
for filename in sorted(os.listdir(share_path)):
|
|
if os.path.isdir(os.path.join(share_path,filename)):
|
|
continue
|
|
if filename.startswith("."):
|
|
continue
|
|
print("%s/download/%s/%s/%s"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted,
|
|
filename
|
|
))
|
|
print("or \n\n# curl -s %s/script/download/%s/%s | bash /dev/stdin [-f]"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
elif opts.type == "direct":
|
|
if 'direct_links' not in share or not share['direct_links']:
|
|
print("Direct downloading not allowed in this share")
|
|
sys.exit(0)
|
|
print("Links to direct download files:")
|
|
share_path = os.path.join(
|
|
config['__root_path__'],
|
|
config['data_folder'],
|
|
share['path']
|
|
)
|
|
if not os.path.exists(share_path):
|
|
print("no files")
|
|
sys.exit(0)
|
|
for filename in sorted(os.listdir(share_path)):
|
|
if os.path.isdir(os.path.join(share_path,filename)):
|
|
continue
|
|
if filename.startswith("."):
|
|
continue
|
|
print("%s/direct/%s/%s/%s"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
get_direct_token(share,filename),
|
|
filename
|
|
))
|
|
print("or \n\n# curl -s %s/script/direct/%s/%s | bash /dev/stdin [-f]"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
elif opts.type == "zip":
|
|
print("ZIP download:")
|
|
print("%s/zip/%s/%s"%(
|
|
config['public_url'],
|
|
share['name'],
|
|
crypted
|
|
))
|
|
|
|
|
|
def parse_options():
|
|
config_default = os.path.realpath(
|
|
os.path.join(
|
|
os.path.dirname(
|
|
os.path.realpath(__file__)
|
|
),
|
|
"..",
|
|
"data",
|
|
"config.json"
|
|
)
|
|
)
|
|
|
|
parser = argparse.ArgumentParser(description='Flees share manager')
|
|
parser.add_argument('-c','--config', action="store", dest="config", default = config_default,
|
|
help = "Your current config.json file [%(default)s]")
|
|
parser.add_argument('-s','--shares', action="store", dest="shares_file", default = None,
|
|
help = "shares.json you want to use. Defaults to what config.json defines")
|
|
|
|
subparsers = parser.add_subparsers(help='sub-command help', dest='subparser_name')
|
|
## list shares
|
|
parser_list = subparsers.add_parser('list', help = "List shares")
|
|
parser_list.add_argument('-P', action="store_true", dest="show_password", default = False,
|
|
help = "Display passwords")
|
|
## list folders
|
|
parser_folders = subparsers.add_parser('folders', help = "List folders and share names")
|
|
## Remove
|
|
parser_remove = subparsers.add_parser('remove', help = "Remove a share")
|
|
parser_remove.add_argument(dest="name")
|
|
parser_remove.add_argument('-w','--write', action="store_true", dest="write", default = False,
|
|
help = "Write changes to the shares.json file"
|
|
)
|
|
## Add
|
|
parser_add = subparsers.add_parser('add', help = "Add a share")
|
|
parser_add.add_argument('-n','--name', action="store", dest="name", required = True)
|
|
parser_add.add_argument('-p','--path', action="store", dest="path", required = True,
|
|
help= "path relative to data folder"
|
|
)
|
|
parser_add.add_argument('-D','--description', action="store", dest="description", default = "",
|
|
help= "Describe the contents"
|
|
)
|
|
parser_add.add_argument('-P','--public', action="store_true", dest="public", default = False)
|
|
parser_add.add_argument('-u','--upload', action="store_true", dest="upload", default = False)
|
|
parser_add.add_argument('-o','--overwrite', action="store_false", dest="overwrite", default = True,
|
|
help = "Disable file overwrites. If disabled, old files are versioned with modification date.")
|
|
parser_add.add_argument('-d','--direct', action="store_true", dest="direct", default = False,
|
|
help = "Allow direct file sharing (password hash included in URL)")
|
|
parser_add.add_argument('--pass-plain', action="store_true", dest="plain", default = False,
|
|
help = "Save the password as plain text")
|
|
parser_add.add_argument('--password', action="store", dest="password", default = False,
|
|
help = "Setting a password enables use of login links and direct downloads")
|
|
parser_add.add_argument('-e','--expire', action="store", dest="expire", default = False,
|
|
help = "expire date in format '%%Y-%%m-%%d %%H:%%M' ex. '2018-12-24 21:00'"
|
|
)
|
|
parser_add.add_argument('-r','--recipient', action="store", dest="recipient", default = "",
|
|
help= "Recipient for notifications (if enabled)"
|
|
)
|
|
parser_add.add_argument('-w','--write', action="store_true", dest="write", default = False,
|
|
help = "Write changes to the shares.json file"
|
|
)
|
|
## Modify
|
|
parser_modify = subparsers.add_parser('modify', help = "Modify share")
|
|
parser_modify.add_argument('-n','--name', action="store", dest="name", required = True)
|
|
parser_modify.add_argument('-p','--path', action="store", dest="path", default = None,
|
|
help= "path relative to data folder"
|
|
)
|
|
parser_modify.add_argument('-D','--description', action="store", dest="description", default = None,
|
|
help= "Describe the contents"
|
|
)
|
|
parser_modify.add_argument('-P','--public', action="store", dest="public", default = None, choices = ['true','false'])
|
|
parser_modify.add_argument('-u','--upload', action="store", dest="upload", default = None, choices = ['true','false'])
|
|
parser_modify.add_argument('-o','--overwrite', action="store", dest="overwrite", default = None, choices = ['true','false'],
|
|
help = "Disable file overwrites. If disabled, old files are versioned with modification date.")
|
|
parser_modify.add_argument('-d','--direct', action="store", dest="direct_links", default = None, choices = ['true','false'],
|
|
help = "Allow direct file sharing (password hash included in URL)")
|
|
parser_modify.add_argument('--pass-plain', action="store_true", dest="plain", default = False,
|
|
help = "Save the password as plain text")
|
|
parser_modify.add_argument('--password', action="store", dest="password", default = False,
|
|
help = "Setting a password enables use of login links and direct downloads. Set as empty string to remove password protection.")
|
|
parser_modify.add_argument('-e','--expire', action="store", dest="expire", default = False,
|
|
help = "expire date in format '%%Y-%%m-%%d %%H:%%M' ex. '2018-12-24 21:00'. Set as empty string to remove expiration."
|
|
)
|
|
parser_modify.add_argument('-r','--recipient', action="store", dest="recipient", default = None,
|
|
help= "Recipient for notifications (if enabled)"
|
|
)
|
|
parser_modify.add_argument('-w','--write', action="store_true", dest="write", default = False,
|
|
help = "Write changes to the shares.json file"
|
|
)
|
|
## REST
|
|
parser_rest = subparsers.add_parser('rest', help = "Display REST API links")
|
|
parser_rest.add_argument(dest="name", help = "Name of the share")
|
|
parser_rest.add_argument(dest="type", help = "Type of command",
|
|
choices = ['list','login','upload','download','direct','zip']
|
|
)
|
|
|
|
return parser.parse_args()
|
|
|
|
if __name__ == "__main__":
|
|
opts = parse_options()
|
|
config = {}
|
|
if os.path.exists(opts.config):
|
|
config = json.load(open(opts.config,'rt'))
|
|
config['__root_path__'] = get_root_path(opts)
|
|
else:
|
|
print("config file %s does not exist!"%(opts.config,))
|
|
sys.exit(1)
|
|
|
|
if opts.shares_file:
|
|
config['shares_file'] = opts.shares_file
|
|
if 'shares_file' in config:
|
|
# if not from command line, read from config
|
|
opts.shares_file = config['shares_file']
|
|
|
|
if os.path.exists(os.path.join(config['__root_path__'],config['shares_file'])):
|
|
shares = json.load(open(os.path.join(config['__root_path__'],config['shares_file']),'rt'))
|
|
else:
|
|
print("shares_file %s does not exist!"%(os.path.join(config['__root_path__'],config['shares_file'])))
|
|
shares = []
|
|
|
|
if opts.subparser_name == 'list':
|
|
list_shares(shares,opts)
|
|
elif opts.subparser_name == 'folders':
|
|
list_folders(shares,config)
|
|
elif opts.subparser_name == 'remove':
|
|
remove_share(shares,config,opts)
|
|
elif opts.subparser_name == 'add':
|
|
add_share(shares,config,opts)
|
|
elif opts.subparser_name == 'modify':
|
|
modify_share(shares,config,opts)
|
|
elif opts.subparser_name == 'rest':
|
|
print_rest_api(shares,config,opts)
|
|
|
|
|
|
|