625 lines
22 KiB
Python
Executable File
625 lines
22 KiB
Python
Executable File
#!/usr/bin/python
|
|
# PYTHON_ARGCOMPLETE_OK
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import getpass
|
|
import os
|
|
import platform
|
|
import shlex
|
|
import struct
|
|
import subprocess
|
|
import sys
|
|
from base64 import b64decode, b64encode
|
|
from distutils.util import strtobool
|
|
|
|
import argcomplete
|
|
|
|
from webdav3.client import Client, WebDavException, NotConnection, Urn
|
|
|
|
|
|
def get_terminal_size():
|
|
current_os = platform.system()
|
|
tuple_xy = None
|
|
if current_os == 'Windows':
|
|
tuple_xy = _get_terminal_size_windows()
|
|
if tuple_xy is None:
|
|
tuple_xy = _get_terminal_size_input()
|
|
if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
|
|
tuple_xy = _get_terminal_size_linux()
|
|
if tuple_xy is None:
|
|
tuple_xy = 80, 25
|
|
return tuple_xy
|
|
|
|
|
|
def _get_terminal_size_windows():
|
|
try:
|
|
from ctypes import windll, create_string_buffer
|
|
h = windll.kernel32.GetStdHandle(-12)
|
|
csbi = create_string_buffer(22)
|
|
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
|
|
if res:
|
|
(bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
|
|
sizex = right - left + 1
|
|
sizey = bottom - top + 1
|
|
return sizex, sizey
|
|
except:
|
|
pass
|
|
|
|
|
|
def _get_terminal_size_input():
|
|
try:
|
|
cols = int(subprocess.check_call(shlex.split('tput cols')))
|
|
rows = int(subprocess.check_call(shlex.split('tput lines')))
|
|
return (cols, rows)
|
|
except:
|
|
pass
|
|
|
|
|
|
def _get_terminal_size_linux():
|
|
|
|
def ioctl_GWINSZ(fd):
|
|
try:
|
|
import fcntl
|
|
import termios
|
|
return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
|
except:
|
|
pass
|
|
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
|
if not cr:
|
|
try:
|
|
fd = os.open(os.ctermid(), os.O_RDONLY)
|
|
cr = ioctl_GWINSZ(fd)
|
|
os.close(fd)
|
|
except:
|
|
pass
|
|
if not cr:
|
|
try:
|
|
cr = (os.environ['LINES'], os.environ['COLUMNS'])
|
|
except:
|
|
return None
|
|
return int(cr[1]), int(cr[0])
|
|
|
|
|
|
class ProgressBar:
|
|
|
|
def __init__(self):
|
|
self.precise = 0
|
|
self.total = 0
|
|
|
|
def show(self):
|
|
progress_line = self._progress_line()
|
|
import sys
|
|
if self.precise == 100:
|
|
print(progress_line, end="")
|
|
else:
|
|
print(progress_line, end="")
|
|
sys.stdout.write("\r")
|
|
|
|
def _progress_line(self):
|
|
sizex, sizey = get_terminal_size()
|
|
available_width = sizex
|
|
precise = self._get_field_precise()
|
|
ratio = self._get_field_ratio()
|
|
available_width -= len(precise) + len(ratio)
|
|
progress = self._get_field_progress(available_width-2)
|
|
return "{precise} {progress} {ratio}".format(precise=precise, progress=progress, ratio=ratio)
|
|
|
|
def _get_field_precise(self):
|
|
line = "{precise}%".format(precise=self.precise)
|
|
return "{:<4}".format(line)
|
|
|
|
def _get_field_ratio(self):
|
|
current = float(self.precise * self.total / 100)
|
|
total = float(self.total)
|
|
current_line = "{:.2f}".format(current)
|
|
total_line = "{:.2f}".format(total)
|
|
line = "{current}/{total}".format(current=current_line, total=total_line)
|
|
available = len(total_line) * 2 + 1
|
|
format_line = "{prefix}{value}{sufix}".format(prefix="{:>", value=available, sufix="}")
|
|
line = format_line.format(line)
|
|
return line
|
|
|
|
def _get_field_progress(self, width):
|
|
available_width = width - 2
|
|
current = int(self.precise * available_width / 100)
|
|
available_width -= current + 1
|
|
tip = ">" if not self.precise == 100 else ""
|
|
progress = "{arrow}{tip}{space}".format(arrow="=" * current, tip=tip, space=" " * available_width)
|
|
return "[{progress}]".format(progress=progress)
|
|
|
|
def callback(self, current, total):
|
|
if total and not self.total:
|
|
self.total = total
|
|
if not total:
|
|
return
|
|
precise = int(float(current) * 100 / total)
|
|
if self.precise == precise:
|
|
return
|
|
else:
|
|
self.precise = precise
|
|
self.show()
|
|
|
|
setting_keys = ['webdav_hostname', 'webdav_root', 'webdav_login', 'webdav_password', 'webdav_token',
|
|
'proxy_hostname', 'proxy_login', 'proxy_password',
|
|
'cert_path', 'key_path']
|
|
|
|
crypto_keys = ['webdav_password', 'webdav_token', 'proxy_password']
|
|
|
|
|
|
def encoding(source):
|
|
|
|
if not source:
|
|
return ""
|
|
return b64encode(source.encode('utf-8'))
|
|
|
|
|
|
def decoding(source):
|
|
|
|
if not source:
|
|
return ""
|
|
return b64decode(source).decode('utf-8')
|
|
|
|
|
|
def import_options():
|
|
|
|
options = dict()
|
|
for setting_key in setting_keys:
|
|
options[setting_key] = os.environ.get(setting_key.upper())
|
|
|
|
for crypto_key in crypto_keys:
|
|
if not options[crypto_key]:
|
|
continue
|
|
options[crypto_key] = decoding(options[crypto_key])
|
|
|
|
return options
|
|
|
|
|
|
def valid(options):
|
|
|
|
if 'webdav_hostname' not in options:
|
|
return False
|
|
|
|
if not options['webdav_token'] and not options['webdav_login']:
|
|
return False
|
|
|
|
if options['webdav_login'] and not options['webdav_password']:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
class Formatter(argparse.RawTextHelpFormatter):
|
|
|
|
def _get_default_metavar_for_optional(self, action):
|
|
if not action.option_strings:
|
|
return action.dest
|
|
else:
|
|
return ""
|
|
|
|
def _format_action_invocation(self, action):
|
|
if not action.option_strings:
|
|
default = self._get_default_metavar_for_optional(action)
|
|
metavar, = self._metavar_formatter(action, default)(1)
|
|
return metavar
|
|
|
|
else:
|
|
parts = []
|
|
|
|
if action.nargs == 0:
|
|
parts.extend(action.option_strings)
|
|
else:
|
|
default = self._get_default_metavar_for_optional(action)
|
|
args_string = self._format_args(action, default)
|
|
for option_string in action.option_strings:
|
|
parts.append(option_string)
|
|
|
|
return '%s %s' % (', '.join(parts), args_string)
|
|
|
|
return ', '.join(parts)
|
|
|
|
def _metavar_formatter(self, action, default_metavar):
|
|
if action.metavar is not None:
|
|
result = action.metavar
|
|
else:
|
|
result = default_metavar
|
|
|
|
def format(tuple_size):
|
|
if isinstance(result, tuple):
|
|
return result
|
|
else:
|
|
return (result, ) * tuple_size
|
|
return format
|
|
|
|
|
|
def logging_exception(exception):
|
|
print(exception)
|
|
|
|
|
|
def urn_completer(prefix, **kwargs):
|
|
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
|
|
prefix_urn = Urn(prefix)
|
|
if prefix_urn.is_dir():
|
|
return (prefix+filename for filename in client.list(prefix_urn.path()))
|
|
else:
|
|
parent = prefix_urn.parent()
|
|
prefix_filename = prefix_urn.filename()
|
|
prefix_filename_length = len(prefix_filename)
|
|
return (prefix + filename[prefix_filename_length:] for filename in client.list(parent) if filename.startswith(prefix_filename))
|
|
except WebDavException:
|
|
pass
|
|
|
|
return tuple()
|
|
|
|
if __name__ == "__main__":
|
|
|
|
epilog = """
|
|
Examples:
|
|
--------
|
|
$ wdc login https://webdav.server.ru
|
|
webdav_login: login
|
|
webdav_password: password
|
|
success
|
|
$ wdc login https://webdav.server.ru --token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
not success
|
|
$ wdc check
|
|
success
|
|
$ wdc check file1
|
|
not success
|
|
$ wdc free
|
|
245234120344
|
|
$ wdc ls dir1
|
|
file1
|
|
...
|
|
fileN
|
|
$ wdc mkdir dir2
|
|
$ wdc copy dir1/file1 -t dir2/file1
|
|
$ wdc move dir2/file1 -t dir2/file2
|
|
$ wdc download dir1/file1 -t ~/Downloads/file1
|
|
$ wdc download dir1/ -t ~/Downloads/dir1/
|
|
$ wdc upload dir2/file2 -f ~/Documents/file1
|
|
$ wdc upload dir2/ -f ~/Documents/
|
|
$ wdc publish di2/file2
|
|
https://yadi.sk/i/vWtTUcBucAc6k
|
|
$ wdc unpublish dir2/file2
|
|
$ wdc pull dir1/ -t ~/Documents/dir1/
|
|
$ wdc push dir1/ -f ~/Documents/dir1/
|
|
$ wdc info dir1/file1
|
|
{'name': 'file1', 'modified': 'Thu, 23 Oct 2014 16:16:37 GMT',
|
|
'size': '3460064', 'created': '2014-10-23T16:16:37Z'}
|
|
"""
|
|
|
|
usage = """
|
|
wdc [-h] [-v]
|
|
wdc login https://webdav.server.ru [--token] [-r] [-p] [-c] [-k]
|
|
wdc [action] [path] [-t] [-f]
|
|
"""
|
|
|
|
actions = "login logout check info free ls clean mkdir copy move download upload publish unpublish push pull".split()
|
|
actions_help = "check, info, free, ls, clean, mkdir, copy, move,\ndownload, upload, publish, unpublish, push, pull"
|
|
|
|
parser = argparse.ArgumentParser(prog='wdc', formatter_class=Formatter, epilog=epilog, usage=usage)
|
|
parser.add_argument("action", help=actions_help, choices=actions)
|
|
|
|
from webdav3.client import __version__ as version
|
|
version_text = "{name} {version}".format(name="%(prog)s", version=version)
|
|
parser.add_argument("-v", '--version', action='version', version=version_text)
|
|
parser.add_argument("-r", "--root", help="example: dir1/dir2")
|
|
parser.add_argument("--token", help="example: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
|
|
parser.add_argument("-c", "--cert-path", help="example: /etc/ssl/certs/certificate.crt")
|
|
parser.add_argument("-k", "--key-path", help="example: /etc/ssl/private/certificate.key")
|
|
parser.add_argument("-p", "--proxy", help="example: http://127.0.0.1:8080")
|
|
parser.add_argument("path", help="example: dir1/dir2/file1", nargs='?').completer = urn_completer
|
|
parser.add_argument("-f", '--from-path', help="example: ~/Documents/file1")
|
|
parser.add_argument("-t", "--to-path", help="example for download and pull: ~/Download/file1\nexample for copy and move: dir1/dir2").completer = urn_completer
|
|
|
|
argcomplete.autocomplete(parser, exclude=("-h", "--help", "--proxy", "-p", "-r", "--root", "-c", "--cert-path", "-t", "--to-path", "-v", "--version", "-f", "--from-path", "-k", "--key-path"))
|
|
args = parser.parse_args()
|
|
action = args.action
|
|
|
|
if action == 'login':
|
|
env = dict()
|
|
if not args.path:
|
|
try:
|
|
env['webdav_hostname'] = raw_input("webdav_hostname: ")
|
|
except NameError:
|
|
env['webdav_hostname'] = input("webdav_hostname: ")
|
|
else:
|
|
env['webdav_hostname'] = args.path
|
|
|
|
if not args.token:
|
|
try:
|
|
env['webdav_login'] = raw_input("webdav_login: ")
|
|
except NameError:
|
|
env['webdav_login'] = input("webdav_login: ")
|
|
env['webdav_password'] = getpass.getpass("webdav_password: ")
|
|
else:
|
|
env['webdav_token'] = args.token
|
|
|
|
if args.proxy:
|
|
env['proxy_hostname'] = args.proxy
|
|
try:
|
|
env['proxy_login'] = raw_input("proxy_login: ")
|
|
except NameError:
|
|
env['proxy_login'] = input("proxy_login: ")
|
|
env['proxy_password'] = getpass.getpass("proxy_password: ")
|
|
|
|
if args.root:
|
|
env['webdav_root'] = args.root
|
|
|
|
if args.cert_path:
|
|
env['cert_path'] = args.cert_path
|
|
|
|
if args.key_path:
|
|
env['key_path'] = args.key_path
|
|
|
|
try:
|
|
client = Client(env)
|
|
check = client.check()
|
|
text = "success" if check else "not success"
|
|
print(text)
|
|
|
|
if check:
|
|
for crypto_key in crypto_keys:
|
|
if crypto_key not in env:
|
|
continue
|
|
if not env[crypto_key]:
|
|
continue
|
|
env[crypto_key] = encoding(env[crypto_key])
|
|
|
|
for (key, value) in env.items():
|
|
os.putenv(key.upper(), value)
|
|
os.system('bash')
|
|
|
|
except WebDavException as e:
|
|
print("not success")
|
|
sys.exit()
|
|
else:
|
|
options = import_options()
|
|
if not valid(options):
|
|
print("First log on webdav server using the following command: wdc login.")
|
|
sys.exit()
|
|
|
|
elif action == "logout":
|
|
os.system("exit")
|
|
|
|
elif action == 'check':
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
check = client.check(args.path) if args.path else client.check()
|
|
text = "success" if check else "not success"
|
|
print(text)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'free':
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
free_size = client.free()
|
|
print(free_size)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'ls':
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
paths = client.list(args.path) if args.path else client.list()
|
|
for path in paths:
|
|
print(path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'clean':
|
|
if not args.path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.clean(args.path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'mkdir':
|
|
if not args.path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.mkdir(args.path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'copy':
|
|
if not args.path or not args.to_path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.copy(remote_path_from=args.path, remote_path_to=args.to_path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'move':
|
|
if not args.path or not args.to_path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.move(remote_path_from=args.path, remote_path_to=args.to_path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'download':
|
|
if not args.path or not args.to_path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
progress_bar = ProgressBar()
|
|
|
|
def download_progress(download_t, download_d, upload_t, upload_d):
|
|
progress_bar.callback(current=download_d, total=download_t)
|
|
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
if not os.path.exists(path=args.to_path):
|
|
client.download(remote_path=args.path, local_path=args.to_path, progress=download_progress)
|
|
print("\n")
|
|
else:
|
|
try:
|
|
choice = raw_input("Local path exists, do you want to overwrite it? [Y/n] ")
|
|
except NameError:
|
|
choice = input("Local path exists, do you want to overwrite it? [Y/n] ")
|
|
try:
|
|
yes = strtobool(choice.lower())
|
|
if yes:
|
|
client.download(remote_path=args.path, local_path=args.to_path, progress=download_progress)
|
|
print("\n")
|
|
except ValueError:
|
|
print("Incorrect answer")
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'upload':
|
|
if not args.path or not args.from_path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
progress_bar = ProgressBar()
|
|
|
|
def upload_progress(download_t, download_d, upload_t, upload_d):
|
|
progress_bar.callback(current=upload_d, total=upload_t)
|
|
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
if not client.check(remote_path=args.path):
|
|
client.upload(remote_path=args.path, local_path=args.from_path, progress=upload_progress)
|
|
print("\n")
|
|
else:
|
|
try:
|
|
choice = raw_input("Remote resource exists, do you want to overwrite it? [Y/n] ")
|
|
except NameError:
|
|
choice = input("Remote resource exists, do you want to overwrite it? [Y/n] ")
|
|
try:
|
|
yes = strtobool(choice.lower())
|
|
if yes:
|
|
client.upload(remote_path=args.path, local_path=args.from_path, progress=upload_progress)
|
|
print("\n")
|
|
except ValueError:
|
|
print("Incorrect answer")
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'publish':
|
|
if not args.path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
link = client.publish(args.path)
|
|
print(link)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'unpublish':
|
|
if not args.path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.unpublish(args.path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'push':
|
|
if not args.path or not args.from_path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.push(remote_directory=args.path, local_directory=args.from_path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'pull':
|
|
if not args.path or not args.to_path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
client.pull(remote_directory=args.path, local_directory=args.to_path)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
elif action == 'info':
|
|
if not args.path:
|
|
parser.print_help()
|
|
else:
|
|
options = import_options()
|
|
try:
|
|
client = Client(options)
|
|
connection = client.check()
|
|
if not connection:
|
|
raise NotConnection(options["webdav_hostname"])
|
|
info = client.info(args.path)
|
|
print(info)
|
|
except WebDavException as e:
|
|
logging_exception(e)
|
|
|
|
else:
|
|
parser.print_help()
|