diff --git a/.gitignore b/.gitignore index 732c5cb..57e057a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -urbackup_server_web_api_wrapper.egg-info/* -dist/urbackup-server-web-api-wrapper-*.zip -urbackup_api/__pycache__/* +urbackup_server_web_api_wrapper.egg-info/* +dist/urbackup-server-web-api-wrapper-*.zip +urbackup_api/__pycache__/* +test.py +*.pyc +__pycache__ diff --git a/MANIFEST.in b/MANIFEST.in index c5a5da2..7ea2843 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include README.md +include README.md include LICENSE.txt \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..804abb1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests +urllib3 diff --git a/setup.cfg b/setup.cfg index a2f3748..224a779 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ -[metadata] +[metadata] description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py index d986c24..8d73d16 100644 --- a/setup.py +++ b/setup.py @@ -1,79 +1,79 @@ -# Always prefer setuptools over distutils -from setuptools import setup, find_packages -# To use a consistent encoding -from codecs import open -from os import path - -here = path.abspath(path.dirname(__file__)) - -# Get the long description from the README file -with open(path.join(here, 'README.md'), encoding='utf-8') as f: - long_description = f.read() - -setup( - name='urbackup-server-web-api-wrapper', - - # Versions should comply with PEP440. For a discussion on single-sourcing - # the version across setup.py and the project code, see - # https://packaging.python.org/en/latest/single_source_version.html - version='0.6', - - description='Python wrapper to access and control an UrBackup server', - long_description=long_description, - - # The project's main homepage. - url='https://github.com/uroni/urbackup-server-python-web-api-wrapper', - - # Author details - author='Martin Raiber', - author_email='martin@urbackup.org', - - # Choose your license - license='Apache License 2.0', - - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 3 - Alpha', - - # Indicate who your project is intended for - 'Intended Audience :: Developers', - - # Pick your license as you wish (should match "license" above) - 'License :: OSI Approved :: Apache Software License', - - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ], - - # What does your project relate to? - keywords='urbackup web api client', - - # You can just specify the packages manually here if your project is - # simple. Or you can use find_packages(). - packages=find_packages(exclude=['contrib', 'docs', 'tests']), - - # Alternatively, if you want to distribute just a my_module.py, uncomment - # this: - # py_modules=["my_module"], - - # List run-time dependencies here. These will be installed by pip when - # your project is installed. For an analysis of "install_requires" vs pip's - # requirements files see: - # https://packaging.python.org/en/latest/requirements.html - install_requires=[], - - # List additional groups of dependencies here (e.g. development - # dependencies). You can install these using the following syntax, - # for example: - # $ pip install -e .[dev,test] - extras_require={ - 'dev': [], - 'test': [], - } -) \ No newline at end of file +# Always prefer setuptools over distutils +from setuptools import setup, find_packages +# To use a consistent encoding +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='urbackup-server-web-api-wrapper', + + # Versions should comply with PEP440. For a discussion on single-sourcing + # the version across setup.py and the project code, see + # https://packaging.python.org/en/latest/single_source_version.html + version='0.7', + + description='Python wrapper to access and control an UrBackup server', + long_description=long_description, + + # The project's main homepage. + url='https://github.com/uroni/urbackup-server-python-web-api-wrapper', + + # Author details + author='Martin Raiber', + author_email='martin@urbackup.org', + + # Choose your license + license='Apache License 2.0', + + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # Indicate who your project is intended for + 'Intended Audience :: Developers', + + # Pick your license as you wish (should match "license" above) + 'License :: OSI Approved :: Apache Software License', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], + + # What does your project relate to? + keywords='urbackup web api client', + + # You can just specify the packages manually here if your project is + # simple. Or you can use find_packages(). + packages=find_packages(exclude=['contrib', 'docs', 'tests']), + + # Alternatively, if you want to distribute just a my_module.py, uncomment + # this: + # py_modules=["my_module"], + + # List run-time dependencies here. These will be installed by pip when + # your project is installed. For an analysis of "install_requires" vs pip's + # requirements files see: + # https://packaging.python.org/en/latest/requirements.html + install_requires=[], + + # List additional groups of dependencies here (e.g. development + # dependencies). You can install these using the following syntax, + # for example: + # $ pip install -e .[dev,test] + extras_require={ + 'dev': [], + 'test': [], + } +) diff --git a/test/stop_all.py b/test/stop_all.py index aa5557a..46ae425 100644 --- a/test/stop_all.py +++ b/test/stop_all.py @@ -1,12 +1,12 @@ -import urbackup_api - - -server = urbackup_api.urbackup_server("http://127.0.0.1:55414/x", "admin", "foo") - -for action in server.get_actions(): - a = action["action"] - if a ==server.action_full_file or a==server.action_resumed_full_file: - print("Running full file backup: "+action["name"]) - - print("Stopping...") +import urbackup_api + + +server = urbackup_api.urbackup_server("http://127.0.0.1:55414/x", "admin", "foo") + +for action in server.get_actions(): + a = action["action"] + if a ==server.action_full_file or a==server.action_resumed_full_file: + print("Running full file backup: "+action["name"]) + + print("Stopping...") server.stop_action(action) \ No newline at end of file diff --git a/test/urbackup_api_test.py b/test/urbackup_api_test.py index adbdbb5..4355acb 100644 --- a/test/urbackup_api_test.py +++ b/test/urbackup_api_test.py @@ -1,72 +1,72 @@ -import urbackup_api -import datetime -import time - - -server = urbackup_api.urbackup_server("http://127.0.0.1:55414/x", "admin", "foo") - -for extra_client in server.get_extra_clients(): - server.remove_extra_client(extra_client["id"]) - -computernames = """2.2.2.2 -3.3.3.3""" - -for line in computernames: - server.add_extra_client(line) - - -clients = server.get_status() -usage = server.get_usage() - -if len(clients) != len(usage): - print("Failed to retreive usage or status information. Length of both lists is different.") - -# Uncomment to format time differently -# locale.setlocale(locale.LC_TIME, "german") - -diff_time = 3*24*60*60 # 3 days -for client in clients: - - if client["lastbackup"]=="-" or client["lastbackup"] < time.time() - diff_time: - - if client["lastbackup"]=="-" or client["lastbackup"]==0: - lastbackup = "Never" - else: - lastbackup = datetime.datetime.fromtimestamp(client["lastbackup"]).strftime("%x %X") - - print("Last file backup at {lastbackup} of client {clientname} is older than three days".format( - lastbackup=lastbackup, clientname=client["name"] ) ) - - -#if server.start_incr_file_backup("Johnwin7test-PC2"): -# print("Started file backup successfully") -#else: -# print("Failed to start file backup") - - -if not server.get_livelog(): - print("Failed to get livelog contents" - -settings = server.get_client_settings("Johnwin7test-PC2") - -for key in settings: - print("{key}={value}".format(key=key, value=settings[key])) - -print("Authkey: "+server.get_client_authkey("Johnwin7test-PC2")) - -if server.change_client_setting("Johnwin7test-PC2", "max_image_incr", "40"): - print("Changed setting successfully") -else: - print("Failed to change setting") - - -settings = server.get_global_settings() - -for key in settings: - print("Global: {key}={value}".format(key=key, value=settings[key])) - - -if server.set_global_setting("max_image_incr", "40"): - print("Changed global setting successfully") -else: - print("Failed to change global setting") +import urbackup_api +import datetime +import time + + +server = urbackup_api.urbackup_server("http://127.0.0.1:55414/x", "admin", "foo") + +for extra_client in server.get_extra_clients(): + server.remove_extra_client(extra_client["id"]) + +computernames = """2.2.2.2 +3.3.3.3""" + +for line in computernames: + server.add_extra_client(line) + + +clients = server.get_status() +usage = server.get_usage() + +if len(clients) != len(usage): + print("Failed to retreive usage or status information. Length of both lists is different.") + +# Uncomment to format time differently +# locale.setlocale(locale.LC_TIME, "german") + +diff_time = 3*24*60*60 # 3 days +for client in clients: + + if client["lastbackup"]=="-" or client["lastbackup"] < time.time() - diff_time: + + if client["lastbackup"]=="-" or client["lastbackup"]==0: + lastbackup = "Never" + else: + lastbackup = datetime.datetime.fromtimestamp(client["lastbackup"]).strftime("%x %X") + + print("Last file backup at {lastbackup} of client {clientname} is older than three days".format( + lastbackup=lastbackup, clientname=client["name"] ) ) + + +#if server.start_incr_file_backup("Johnwin7test-PC2"): +# print("Started file backup successfully") +#else: +# print("Failed to start file backup") + + +if not server.get_livelog(): + print("Failed to get livelog contents" + +settings = server.get_client_settings("Johnwin7test-PC2") + +for key in settings: + print("{key}={value}".format(key=key, value=settings[key])) + +print("Authkey: "+server.get_client_authkey("Johnwin7test-PC2")) + +if server.change_client_setting("Johnwin7test-PC2", "max_image_incr", "40"): + print("Changed setting successfully") +else: + print("Failed to change setting") + + +settings = server.get_global_settings() + +for key in settings: + print("Global: {key}={value}".format(key=key, value=settings[key])) + + +if server.set_global_setting("max_image_incr", "40"): + print("Changed global setting successfully") +else: + print("Failed to change global setting") diff --git a/urbackup_api/__init__.py b/urbackup_api/__init__.py index 042c6b3..f50e1a8 100644 --- a/urbackup_api/__init__.py +++ b/urbackup_api/__init__.py @@ -1,482 +1,470 @@ -import http.client as http -import json -from urllib.parse import urlparse -from urllib.parse import urlencode -from base64 import b64encode -import hashlib -import socket -import shutil -import os -import binascii -import logging -logger = logging.getLogger('urbackup-server-python-api-wrapper') - - -class urbackup_server: - - def __init__(self, server_url, server_username, server_password): - self._server_url=server_url - self._server_username=server_username - self._server_password=server_password - - - #If you have basic authentication via .htpasswd - server_basic_username = '' - server_basic_password = '' - - - _session="" - - _logged_in = False - - _lastlogid = 0 - - def _get_response(self, action, params, method="POST"): - - headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json; charset=UTF-8' - } - - if('server_basic_username' in globals() and len(self.server_basic_username)>0): - userAndPass = b64encode(str.encode(self.server_basic_username+":"+self.server_basic_password)).decode("ascii") - headers['Authorization'] = 'Basic %s' % userAndPass - - curr_server_url=self._server_url+"?"+urlencode({"a": action}); - - if(len(self._session)>0): - params["ses"]=self._session - - if method==None: - method = 'POST' - - if method=="GET": - curr_server_url+="&"+urlencode(params); - - target = urlparse(curr_server_url) - - if method=='POST': - body = urlencode(params) - else: - body = '' - - http_timeout = 10*60; - - if(target.scheme=='http'): - h = http.HTTPConnection(target.hostname, target.port, timeout=http_timeout) - elif(target.scheme=='https'): - h = http.HTTPSConnection(target.hostname, target.port, timeout=http_timeout) - else: - logger.error('Unkown scheme: '+target.scheme) - raise Exception("Unkown scheme: "+target.scheme) - - h.request( - method, - target.path+"?"+target.query, - body, - headers) - - return h.getresponse(); - - def _get_json(self, action, params = {}): - tries = 50 - - while tries>0: - response = self._get_response(action, params) - - if(response.status == 200): - break - - tries=tries-1 - if(tries==0): - return None - else: - logger.error("API call failed. Retrying...") - - data = response.read(); - - response.close() - - return json.loads(data.decode("utf-8","ignore")) - - def _download_file(self, action, outputfn, params): - - response = self.get_response(action, params, "GET"); - - if(response.status!=200): - return False - - with open(outputfn, 'wb') as outputf: - shutil.copyfileobj(response, outputf) - - - return True - - def _md5(self, s): - return hashlib.md5(s.encode()).hexdigest() - - def login(self): - - if( not self._logged_in): - - logger.debug("Trying anonymous login...") - - login = self._get_json("login", {}); - - if(not login or 'success' not in login or not login['success']): - - logger.debug("Logging in...") - - salt = self._get_json("salt", {"username": self._server_username}) - - if( not salt or not ('ses' in salt) ): - logger.warning('Username does not exist') - return False - - self._session = salt["ses"]; - - if( 'salt' in salt ): - password_md5_bin = hashlib.md5((salt["salt"]+self._server_password).encode()).digest() - password_md5 = binascii.hexlify(password_md5_bin).decode() - - if "pbkdf2_rounds" in salt: - pbkdf2_rounds = int(salt["pbkdf2_rounds"]) - if pbkdf2_rounds>0: - password_md5 = binascii.hexlify(hashlib.pbkdf2_hmac('sha256', password_md5_bin, - salt["salt"].encode(), pbkdf2_rounds)).decode() - - password_md5 = self._md5(salt["rnd"]+password_md5) - - login = self._get_json("login", { "username": self._server_username, - "password": password_md5 }) - - if(not login or 'success' not in login or not login['success']): - logger.warning('Error during login. Password wrong?') - return False - - else: - self._logged_in=True - return True - else: - return False - else: - self._logged_in=True - self._session = login["session"]; - return True - else: - - return True - - - def get_client_status(self, clientname): - - if not self.login(): - return None - - status = self._get_json("status") - - if not status: - return None - - if not "status" in status: - return None - - for client in status["status"]: - - if (client["name"] == clientname): - - return client; - - logger.warning("Could not find client status. No permission?") - return None - - def download_installer(self, installer_fn, new_clientname): - - if not self.login(): - return False - - new_client = self._get_json("add_client", { "clientname": new_clientname}) - - if "already_exists" in new_client: - - status = self.get_client_status(new_clientname) - - if status == None: - return False - - return self._download_file("download_client", installer_fn, - {"clientid": status["id"] }) - - - if not "new_authkey" in new_client: - return False - - return self._download_file("download_client", installer_fn, - {"clientid": new_client["new_clientid"], - "authkey": new_client["new_authkey"] - }) - - def add_client(self, clientname): - - if not self.login(): - return None - - ret = self._get_json("add_client", { "clientname": clientname}) - if ret==None or "already_exists" in ret: - return None - - return ret - - def get_global_settings(self): - if not self.login(): - return None - - settings = self._get_json("settings", {"sa": "general"} ) - - if not settings or not "settings" in settings: - return None - - return settings["settings"] - - def set_global_setting(self, key, new_value): - if not self.login(): - return False - - settings = self._get_json("settings", {"sa": "general"} ) - - if not settings or not "settings" in settings: - return False - - settings["settings"][key] = new_value - settings["settings"]["sa"] = "general_save" - - ret = self._get_json("settings", settings["settings"]) - - return ret!=None and "saved_ok" in ret - - def get_client_settings(self, clientname): - - if not self.login(): - return None - - client = self.get_client_status(clientname) - - if client == None: - return None - - clientid = client["id"]; - - settings = self._get_json("settings", {"sa": "clientsettings", - "t_clientid": clientid}) - - if not settings or not "settings" in settings: - return None - - return settings["settings"] - - def change_client_setting(self, clientname, key, new_value): - if not self.login(): - return False - - client = self.get_client_status(clientname) - - if client == None: - return False - - clientid = client["id"]; - - settings = self._get_json("settings", {"sa": "clientsettings", - "t_clientid": clientid}) - - if not settings or not "settings" in settings: - return False - - settings["settings"][key] = new_value - settings["settings"]["overwrite"] = "true" - settings["settings"]["sa"] = "clientsettings_save" - settings["settings"]["t_clientid"] = clientid - - ret = self._get_json("settings", settings["settings"]) - - return ret!=None and "saved_ok" in ret - - def get_client_authkey(self, clientname): - - if not self.login(): - return None - - settings = self.get_client_settings(clientname) - - if settings: - return settings["internet_authkey"] - - return None - - def get_server_identity(self): - - if not self.login(): - return None - - status = self._get_json("status") - - if not status: - return None - - if not "server_identity" in status: - return None - - return status["server_identity"] - - def get_status(self): - if not self.login(): - return None - - status = self._get_json("status") - - if not status: - return None - - if not "status" in status: - return None - - return status["status"] - - def get_livelog(self, clientid = 0): - if not self.login(): - return None - - log = self._get_json("livelog", {"clientid": clientid, "lastid": self._lastlogid}) - - if not log: - return None - - if not "logdata" in log: - return None - - self._lastlogid = log["logdata"][-1]['id'] - - return log["logdata"] - - def get_usage(self): - if not self.login(): - return None - - usage = self._get_json("usage") - - if not usage: - return None - - if not "usage" in usage: - return None - - return usage["usage"] - - def get_extra_clients(self): - if not self.login(): - return None - - status = self._get_json("status") - - if not status: - return None - - if not "extra_clients" in status: - return None - - return status["extra_clients"] - - def _start_backup(self, clientname, backup_type): - - client_info = self.get_client_status(clientname) - - if not client_info: - return False - - ret = self._get_json("start_backup", {"start_client": client_info["id"], - "start_type": backup_type } ); - - if ( ret==None - or "result" not in ret - or len(ret["result"])!=1 - or "start_ok" not in ret["result"][0] - or not ret["result"][0]["start_ok"] ): - return False - - return True - - def start_incr_file_backup(self, clientname): - return self._start_backup(clientname, 'incr_file'); - - def start_full_file_backup(self, clientname): - return self._start_backup(clientname, 'full_file'); - - def start_incr_image_backup(self, clientname): - return self._start_backup(clientname, 'incr_image'); - - def start_full_image_backup(self, clientname): - return self._start_backup(clientname, 'full_image'); - - def add_extra_client(self, addr): - if not self.login(): - return None - - ret = self._get_json("status", {"hostname": addr } ); - - if not ret: - return False - - return True - - def remove_extra_client(self, ecid): - if not self.login(): - return None - - ret = self._get_json("status", {"hostname": ecid, - "remove": "true" } ); - - if not ret: - return False - - return True - - action_incr_file = 1 - action_full_file = 2 - action_incr_image = 3 - action_full_image = 4 - action_resumed_incr_file = 5 - action_resumed_full_file = 6 - action_file_restore = 8 - action_image_restore = 9 - action_client_update = 10 - action_check_db_integrity = 11 - action_backup_db = 12 - action_recalc_stats = 13 - - def get_actions(self): - if not self.login(): - return None - - ret = self._get_json("progress") - - if not ret or not "progress" in ret: - return None - - return ret["progress"] - - def stop_action(self, action): - if (not "clientid" in action - or not "id" in action): - return False - - if not self.login(): - return None - - ret = self._get_json("progress", - {"stop_clientid": action["clientid"], - "stop_id": action["id"]}) - - if not ret or not "progress" in ret: - return False - - return True - +import json +import requests +from base64 import b64encode +import hashlib +import socket +import shutil +import os +import binascii +import logging + +logger = logging.getLogger('urbackup-server-python-api-wrapper') + + +"""Backwards compatible imports""" +try: + import urllib +except ImportError: + import urllib.parse + + +class urbackup_server: + + #If you have basic authentication via .htpasswd + server_basic_username = '' + server_basic_password = '' + + action_incr_file = 1 + action_full_file = 2 + action_incr_image = 3 + action_full_image = 4 + action_resumed_incr_file = 5 + action_resumed_full_file = 6 + action_file_restore = 8 + action_image_restore = 9 + action_client_update = 10 + action_check_db_integrity = 11 + action_backup_db = 12 + action_recalc_stats = 13 + + _session = '' + _logged_in = False + _lastlogid = 0 + + def __init__(self, url, username, password): + self._server_url = url + self._server_username = username + self._server_password = password + + def _get_response(self, action, params, method='POST'): + + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=UTF-8' + } + + if('server_basic_username' in globals() and len(self.server_basic_username)>0): + userAndPass = b64encode( + str.encode('%s:%s' % (self.server_basic_username, self.server_basic_password)) + ).decode('ascii') + headers['Authorization'] = 'Basic %s' % userAndPass + + try: + action_url = '%s?%s' % (self._server_url, urllib.urlencode({'a': action})) + except AttributeError: + action_url = '%s?%s' % (self._server_url, urllib.parse.urlencode({'a': action})) + + if(len(self._session)>0): + params['ses'] = self._session + + if method == 'POST': + r = requests.post( + action_url, + data=params + ) + elif method == 'GET': + r = requests.get( + action_url, + params=params + ) + else: + raise Exception('Request with method \'%s\' has not been implemented yet.' % method) + + return r + + def _get_json(self, action, params = {}): + tries = 50 + + while tries>0: + response = self._get_response(action, params) + + if(response.status_code == 200): + break + + tries -= 1 + if tries <= 0: + return None + else: + logger.error('API call failed. Retrying...') + + return response.json() + + def _download_file(self, action, outputfn, params): + + req = self._get_response(action, params, 'GET'); + + if req.status_code != 200: + return False + + with open(outputfn, 'wb') as stream: + for chunk in req.iter_content(chunk_size=128): + stream.write(chunk) + + return True + + def _md5(self, s): + return hashlib.md5(s.encode()).hexdigest() + + def login(self): + + if not self._logged_in: + + logger.debug('Trying anonymous login...') + + login = self._get_json('login', {}); + + if not login or 'success' not in login or not login['success'] : + logger.debug('Logging in...') + + salt = self._get_json('salt', {'username': self._server_username}) + if not salt or not ('ses' in salt): + logger.warning('Username does not exist') + return False + + self._session = salt['ses']; + + if 'salt' in salt: + password_md5_bin = hashlib.md5((salt['salt']+self._server_password).encode()).digest() + password_md5 = binascii.hexlify(password_md5_bin).decode() + + if 'pbkdf2_rounds' in salt: + pbkdf2_rounds = int(salt['pbkdf2_rounds']) + if pbkdf2_rounds>0: + password_md5 = binascii.hexlify(hashlib.pbkdf2_hmac('sha256', password_md5_bin, + salt['salt'].encode(), pbkdf2_rounds)).decode() + + password_md5 = self._md5(salt['rnd']+password_md5) + + login = self._get_json('login', { + 'username': self._server_username, + 'password': password_md5 }) + + if not login or 'success' not in login or not login['success']: + logger.warning('Error during login. Password wrong?') + return False + + else: + self._logged_in = True + return True + else: + return False + else: + self._logged_in = True + self._session = login['session']; + return True + else: + return True + + def get_client_status(self, clientname): + + if not self.login(): + return None + + status = self._get_json('status') + + if not status: + return None + + if not 'status' in status: + return None + + for client in status['status']: + + if client['name'] == clientname: + + return client; + + logger.warning('Could not find client status. No permission?') + return None + + def download_installer(self, installer_fn, new_clientname, os='linux'): + """Download installer for os, defaults to linux""" + + if not os.lower() in ['linux', 'osx', 'mac', 'windows']: + raise Exception('OS not supported') + + if not self.login(): + return False + + new_client = self._get_json('add_client', {'clientname': new_clientname}) + + if 'already_exists' in new_client: + + status = self.get_client_status(new_clientname) + + if status == None: + return False + + return self._download_file('download_client', installer_fn, {'clientid': status['id'] }) + + + if not 'new_authkey' in new_client: + return False + + return self._download_file('download_client', installer_fn, + { + 'clientid': new_client['new_clientid'], + 'authkey': new_client['new_authkey'], + 'os': os + }) + + def add_client(self, clientname): + + if not self.login(): + return None + + ret = self._get_json('add_client', { 'clientname': clientname}) + if ret==None or 'already_exists' in ret: + return None + + return ret + + def get_global_settings(self): + if not self.login(): + return None + + settings = self._get_json('settings', {'sa': 'general'} ) + + if not settings or not 'settings' in settings: + return None + + return settings['settings'] + + def set_global_setting(self, key, new_value): + if not self.login(): + return False + + settings = self._get_json('settings', {'sa': 'general'} ) + + if not settings or not 'settings' in settings: + return False + + settings['settings'][key] = new_value + settings['settings']['sa'] = 'general_save' + + ret = self._get_json('settings', settings['settings']) + + return ret != None and 'saved_ok' in ret + + def get_client_settings(self, clientname): + + if not self.login(): + return None + + client = self.get_client_status(clientname) + + if client == None: + return None + + clientid = client['id']; + + settings = self._get_json('settings', {'sa': 'clientsettings', + 't_clientid': clientid}) + + if not settings or not 'settings' in settings: + return None + + return settings['settings'] + + def change_client_setting(self, clientname, key, new_value): + if not self.login(): + return False + + client = self.get_client_status(clientname) + + if client == None: + return False + + clientid = client['id']; + + settings = self._get_json('settings', {'sa': 'clientsettings', + 't_clientid': clientid}) + + if not settings or not 'settings' in settings: + return False + + settings['settings'][key] = new_value + settings['settings']['overwrite'] = 'true' + settings['settings']['sa'] = 'clientsettings_save' + settings['settings']['t_clientid'] = clientid + + ret = self._get_json('settings', settings['settings']) + + return ret != None and 'saved_ok' in ret + + def get_client_authkey(self, clientname): + + if not self.login(): + return None + + settings = self.get_client_settings(clientname) + + if settings: + return settings['internet_authkey'] + + return None + + def get_server_identity(self): + + if not self.login(): + return None + + status = self._get_json('status') + + if not status: + return None + + if not 'server_identity' in status: + return None + + return status['server_identity'] + + def get_status(self): + if not self.login(): + return None + + status = self._get_json('status') + + if not status: + return None + + if not 'status' in status: + return None + + return status['status'] + + def get_livelog(self, clientid = 0): + if not self.login(): + return None + + log = self._get_json('livelog', {'clientid': clientid, 'lastid': self._lastlogid}) + + if not log: + return None + + if not 'logdata' in log: + return None + + self._lastlogid = log['logdata'][-1]['id'] + + return log['logdata'] + + def get_usage(self): + if not self.login(): + return None + + usage = self._get_json('usage') + + if not usage: + return None + + if not 'usage' in usage: + return None + + return usage['usage'] + + def get_extra_clients(self): + if not self.login(): + return None + + status = self._get_json('status') + + if not status: + return None + + if not 'extra_clients' in status: + return None + + return status['extra_clients'] + + def _start_backup(self, clientname, backup_type): + + client_info = self.get_client_status(clientname) + + if not client_info: + return False + + ret = self._get_json('start_backup', {'start_client': client_info['id'], + 'start_type': backup_type } ); + + if ( ret == None + or 'result' not in ret + or len(ret['result'])!=1 + or 'start_ok' not in ret['result'][0] + or not ret['result'][0]['start_ok']): + return False + + return True + + def start_incr_file_backup(self, clientname): + return self._start_backup(clientname, 'incr_file'); + + def start_full_file_backup(self, clientname): + return self._start_backup(clientname, 'full_file'); + + def start_incr_image_backup(self, clientname): + return self._start_backup(clientname, 'incr_image'); + + def start_full_image_backup(self, clientname): + return self._start_backup(clientname, 'full_image'); + + def add_extra_client(self, addr): + if not self.login(): + return None + + ret = self._get_json('status', {'hostname': addr } ) + + if not ret: + return False + + return True + + def remove_extra_client(self, ecid): + if not self.login(): + return None + + ret = self._get_json('status', {'hostname': ecid, + 'remove': 'true' }) + + if not ret: + return False + + return True + + def get_actions(self): + if not self.login(): + return None + + ret = self._get_json('progress') + + if not ret or not 'progress' in ret: + return None + + return ret['progress'] + + def stop_action(self, action): + if (not 'clientid' in action + or not 'id' in action): + return False + + if not self.login(): + return None + + ret = self._get_json('progress', + {'stop_clientid': action['clientid'], + 'stop_id': action['id']}) + + if not ret or not 'progress' in ret: + return False + + return True