# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018-2019 OzzieIsaacs
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
').replace('\n', '
')]) parent_commit = parent_data['parents'][0] remaining_parents_cnt -= 1 except Exception: # it isn't crucial if we can't get information about the parent break else: # parent is our current version break return parents @staticmethod def _load_nightly_data(repository_url, commit, status): update_data = dict() try: headers = {'Accept': 'application/vnd.github.v3+json'} r = requests.get(repository_url + '/git/commits/' + commit['object']['sha'], headers=headers, timeout=10) r.raise_for_status() update_data = r.json() except requests.exceptions.HTTPError as e: status['message'] = _('HTTP Error') + ' ' + str(e) except requests.exceptions.ConnectionError: status['message'] = _('Connection error') except requests.exceptions.Timeout: status['message'] = _('Timeout while establishing connection') except (requests.exceptions.RequestException, ValueError): status['message'] = _('General error') return status, update_data @staticmethod def _add_excluded_files(log_function): excluded_files = [ os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db', os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep + 'client_secrets.json', os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv', os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2', os.sep + '.key', os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR', os.sep + 'gmail.json', os.sep + 'exclude.txt', os.sep + 'cps' + os.sep + 'cache' ] try: with open(os.path.join(constants.BASE_DIR, "exclude.txt"), "r") as f: lines = f.readlines() for line in lines: processed_line = line.strip("\n\r ").strip("\"'").lstrip("\\/ ").\ replace("\\", os.sep).replace("/", os.sep) if os.path.exists(os.path.join(constants.BASE_DIR, processed_line)): excluded_files.append(os.sep + processed_line) else: log_function("File list for updater: {} not found".format(line)) except (PermissionError, FileNotFoundError): log_function("Excluded file list for updater not found, or not accessible") return excluded_files def _nightly_available_updates(self, request_method): tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone) if request_method == "GET": repository_url = _REPOSITORY_API_URL status, commit = self._load_remote_data(repository_url + '/git/refs/heads/master') parents = [] if status['message'] != '': return json.dumps(status) if 'object' not in commit or 'url' not in commit['object']: status['message'] = _('Unexpected data while reading update information') return json.dumps(status) try: if commit['object']['sha'] == status['current_commit_hash']: status.update({ 'update': False, 'success': True, 'message': _('No update available. You already have the latest version installed') }) return json.dumps(status) except (TypeError, KeyError): status['message'] = _('Unexpected data while reading update information') return json.dumps(status) # a new update is available status['update'] = True status, update_data = self._load_nightly_data(repository_url, commit, status) if status['message'] != '': return json.dumps(status) # if 'committer' in update_data and 'message' in update_data: try: log.debug("A new update is available.") status['success'] = True status['message'] = _( 'A new update is available. Click on the button below to update to the latest version.') new_commit_date = datetime.datetime.strptime( update_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz parents.append( [ format_datetime(new_commit_date, format='short'), update_data['message'], update_data['sha'] ] ) # it only makes sense to analyze the parents if we know the current commit hash if status['current_commit_hash'] != '': parents = self._populate_parent_commits(update_data, status, tz, parents) status['history'] = parents[::-1] except (IndexError, KeyError): status['success'] = False status['message'] = _('Could not fetch update information') log.error("Could not fetch update information") return json.dumps(status) return '' def _stable_updater_set_status(self, i, newer, status, parents, commit): if i == -1 and newer is False: status.update({ 'update': True, 'success': True, 'message': _( 'Click on the button below to update to the latest stable version.'), 'history': parents }) self.updateFile = commit[0]['zipball_url'] elif i == -1 and newer is True: status.update({ 'update': True, 'success': True, 'message': _('A new update is available. Click on the button below to ' 'update to version: %(version)s', version=commit[0]['tag_name']), 'history': parents }) self.updateFile = commit[0]['zipball_url'] return status def _stable_updater_parse_major_version(self, commit, i, parents, current_version, status): if int(commit[i + 1]['tag_name'].split('.')[1]) == current_version[1]: parents.append([commit[i]['tag_name'], commit[i]['body'].replace('\r\n', '
').replace('\n', '
')]) status.update({ 'update': True, 'success': True, 'message': _(u'A new update is available. Click on the button below to ' u'update to version: %(version)s', version=commit[i]['tag_name']), 'history': parents }) self.updateFile = commit[i]['zipball_url'] else: parents.append([commit[i + 1]['tag_name'], commit[i + 1]['body'].replace('\r\n', '
').replace('\n', '
')]) status.update({ 'update': True, 'success': True, 'message': _(u'A new update is available. Click on the button below to ' u'update to version: %(version)s', version=commit[i + 1]['tag_name']), 'history': parents }) self.updateFile = commit[i + 1]['zipball_url'] return status, parents def _stable_available_updates(self, request_method): status = None if request_method == "GET": parents = [] # repository_url = 'https://api.github.com/repos/flatpak/flatpak/releases' # test URL repository_url = _REPOSITORY_API_URL + '/releases?per_page=100' status, commit = self._load_remote_data(repository_url) if status['message'] != '': return json.dumps(status) if not commit: status['success'] = True status['message'] = _(u'No release information available') return json.dumps(status) version = status['current_commit_hash'] current_version = status['current_commit_hash'].split('.') # we are already on newest version, no update available if 'tag_name' not in commit[0]: status['message'] = _(u'Unexpected data while reading update information') log.error("Unexpected data while reading update information") return json.dumps(status) if commit[0]['tag_name'] == version: status.update({ 'update': False, 'success': True, 'message': _(u'No update available. You already have the latest version installed') }) return json.dumps(status) i = len(commit) - 1 newer = False while i >= 0: if 'tag_name' not in commit[i] or 'body' not in commit[i] or 'zipball_url' not in commit[i]: status['message'] = _(u'Unexpected data while reading update information') return json.dumps(status) major_version_update = int(commit[i]['tag_name'].split('.')[0]) minor_version_update = int(commit[i]['tag_name'].split('.')[1]) patch_version_update = int(commit[i]['tag_name'].split('.')[2]) current_version[0] = int(current_version[0]) current_version[1] = int(current_version[1]) try: current_version[2] = int(current_version[2]) except ValueError: current_version[2] = int(current_version[2].replace("b", "").split(' ')[0])-1 # Check if major versions are identical search for newest non-equal commit and update to this one if major_version_update == current_version[0]: if (minor_version_update == current_version[1] and patch_version_update > current_version[2]) or \ minor_version_update > current_version[1]: parents.append([commit[i]['tag_name'], commit[i]['body'].replace('\r\n', '
')]) newer = True i -= 1 continue if major_version_update < current_version[0]: i -= 1 continue if major_version_update > current_version[0]: # found update to last version before major update, unless current version is on last version # before major update if i == (len(commit) - 1): i -= 1 status, parents = self._stable_updater_parse_major_version(commit, i, parents, current_version, status) break status = self._stable_updater_set_status(i, newer, status, parents, commit) return json.dumps(status) def _get_request_path(self): if self.config.config_updatechannel == constants.UPDATE_STABLE: return self.updateFile return _REPOSITORY_API_URL + '/zipball/master' def _load_remote_data(self, repository_url): status = { 'update': False, 'success': False, 'message': '', 'current_commit_hash': '' } commit = None version = self.get_current_version_info() if version is False: status['current_commit_hash'] = _(u'Unknown') else: status['current_commit_hash'] = version try: headers = {'Accept': 'application/vnd.github.v3+json'} r = requests.get(repository_url, headers=headers, timeout=10) commit = r.json() r.raise_for_status() except requests.exceptions.HTTPError as e: if commit: if 'message' in commit: status['message'] = _(u'HTTP Error') + ': ' + commit['message'] else: status['message'] = _(u'HTTP Error') + ': ' + str(e) except requests.exceptions.ConnectionError as e: status['message'] = _(u'Connection error') except requests.exceptions.Timeout: status['message'] = _(u'Timeout while establishing connection') except (requests.exceptions.RequestException, ValueError): status['message'] = _(u'General error') log.debug('Updater status: {}'.format(status['message'] or "OK")) return status, commit