mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-09 21:14:15 +02:00
[appimage] improvements -> database files only updated when expired
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -10,6 +10,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- **git**: for AUR support.
|
- **git**: for AUR support.
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
- AppImage
|
||||||
|
- database updater daemon dropped. Now the database is only downloaded/updated during the initialization process (if it is considered expired). Its expiration are controlled through the new settings property **database.expiration** (in minutes. default: 60 minutes. Use 0 so it is always updated).
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/vinifmor/bauh-files/master/pictures/releases/0.9.11/appim_db_exp.png">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
- old settings properties were dropped (**db_updater.interval**, **db_updated.enabled**)
|
||||||
|
- database files (**apps.db** and **releases.db**) are now stored at **~/.cache/bauh/appimage**
|
||||||
|
- displaying a warning when the cached database files could not be found
|
||||||
|
|
||||||
- Arch
|
- Arch
|
||||||
- AUR
|
- AUR
|
||||||
- upgrade checking now considers modification dates as well (needed because not all AUR packages follow versioning standards)
|
- upgrade checking now considers modification dates as well (needed because not all AUR packages follow versioning standards)
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -142,18 +142,16 @@ install_channel: false # it allows to select an available channel during the ap
|
|||||||
- **Upgrade file**: allows to upgrade a manually installed AppImage file
|
- **Upgrade file**: allows to upgrade a manually installed AppImage file
|
||||||
- Supported sources: [AppImageHub](https://appimage.github.io) (**applications with no releases published to GitHub are currently not available**)
|
- Supported sources: [AppImageHub](https://appimage.github.io) (**applications with no releases published to GitHub are currently not available**)
|
||||||
- Installed applications are store at **~/.local/share/bauh/appimage/installed**
|
- Installed applications are store at **~/.local/share/bauh/appimage/installed**
|
||||||
- Desktop entries ( menu shortcuts ) of the installed applications are stored at **~/.local/share/applications**
|
- Desktop entries (menu shortcuts) of the installed applications are stored at **~/.local/share/applications**
|
||||||
- Symlinks are created at **~/.local/bin**. They have the same name of the application (if the name already exists, it will be created as 'app_name-appimage'. e.g: 'rpcs3-appimage')
|
- Symlinks are created at **~/.local/bin**. They have the same name of the application (if the name already exists, it will be created as 'app_name-appimage'. e.g: 'rpcs3-appimage')
|
||||||
- Downloaded database files are stored at **~/.local/share/bauh/appimage** as **apps.db** and **releases.db**
|
- Downloaded database files are stored at **~/.cache/bauh/appimage** as **apps.db** and **releases.db**
|
||||||
- Databases are always updated when bauh starts
|
- Databases are updated during the initialization process if they are considered outdated (after 60 minutes by default, but this behavior can be changed via the AppImage settings)
|
||||||
- Databases updater daemon running every 20 minutes (it can be customized via the configuration file described below)
|
|
||||||
- All supported application names can be found at [apps.txt](https://github.com/vinifmor/bauh-files/blob/master/appimage/apps.txt)
|
- All supported application names can be found at [apps.txt](https://github.com/vinifmor/bauh-files/blob/master/appimage/apps.txt)
|
||||||
- Applications with ignored updates are defined at **~/.config/bauh/appimage/updates_ignored.txt**
|
- Applications with ignored updates are defined at **~/.config/bauh/appimage/updates_ignored.txt**
|
||||||
- The configuration file is located at **~/.config/bauh/appimage.yml** and it allows the following customizations:
|
- The configuration file is located at **~/.config/bauh/appimage.yml** and it allows the following customizations:
|
||||||
```
|
```
|
||||||
db_updater:
|
database:
|
||||||
enabled: true # if 'false': disables the daemon database updater (bauh will not be able to see if there are updates for your already installed AppImages)
|
expiration: 60 # defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it.
|
||||||
interval: 1200 # the databases update interval in SECONDS (1200 == 20 minutes)
|
|
||||||
```
|
```
|
||||||
- Required dependencies
|
- Required dependencies
|
||||||
- Arch-based systems: **sqlite**, **wget** (or **aria2**/**axel** for faster multi-threaded downloads)
|
- Arch-based systems: **sqlite**, **wget** (or **aria2**/**axel** for faster multi-threaded downloads)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Set
|
from typing import Set, Optional
|
||||||
|
|
||||||
|
|
||||||
class MemoryCache(ABC):
|
class MemoryCache(ABC):
|
||||||
@@ -42,7 +42,7 @@ class MemoryCacheFactory(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def new(self, expiration: int) -> MemoryCache:
|
def new(self, expiration: Optional[int]) -> MemoryCache:
|
||||||
"""
|
"""
|
||||||
:param expiration: expiration time for the cache keys in seconds. Use -1 to disable this feature.
|
:param expiration: expiration time for the cache keys in seconds. Use -1 to disable this feature.
|
||||||
:return:
|
:return:
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ class SearchResult:
|
|||||||
self.new = new
|
self.new = new
|
||||||
self.total = total
|
self.total = total
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def empty(cls):
|
||||||
|
return cls(installed=[], new=[], total=0)
|
||||||
|
|
||||||
|
|
||||||
class UpgradeRequirement:
|
class UpgradeRequirement:
|
||||||
|
|
||||||
@@ -116,7 +120,7 @@ class SoftwareManager(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int, only_apps: bool, pkg_types: Set[Type[SoftwarePackage]], internet_available: bool) -> SearchResult:
|
def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int, only_apps: bool, pkg_types: Optional[Set[Type[SoftwarePackage]]], internet_available: bool) -> SearchResult:
|
||||||
"""
|
"""
|
||||||
:param disk_loader: a running disk loader thread that loads application data from the disk asynchronously
|
:param disk_loader: a running disk loader thread that loads application data from the disk asynchronously
|
||||||
:param limit: the max number of packages to be retrieved. <= 1 should retrieve everything
|
:param limit: the max number of packages to be retrieved. <= 1 should retrieve everything
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from bauh.api.constants import CONFIG_PATH
|
from bauh.api.constants import CONFIG_PATH, CACHE_PATH
|
||||||
from bauh.commons import resource
|
from bauh.commons import resource
|
||||||
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -13,6 +13,12 @@ CONFIG_FILE = '{}/appimage.yml'.format(CONFIG_PATH)
|
|||||||
CONFIG_DIR = '{}/appimage'.format(CONFIG_PATH)
|
CONFIG_DIR = '{}/appimage'.format(CONFIG_PATH)
|
||||||
UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR)
|
UPDATES_IGNORED_FILE = '{}/updates_ignored.txt'.format(CONFIG_DIR)
|
||||||
SYMLINKS_DIR = '{}/.local/bin'.format(str(Path.home()))
|
SYMLINKS_DIR = '{}/.local/bin'.format(str(Path.home()))
|
||||||
|
URL_COMPRESSED_DATABASES = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/appimage/dbs.tar.gz'
|
||||||
|
DATABASES_DIR = '{}/appimage'.format(CACHE_PATH)
|
||||||
|
DATABASE_APPS_FILE = '{}/apps.db'.format(DATABASES_DIR)
|
||||||
|
DATABASE_RELEASES_FILE = '{}/releases.db'.format(DATABASES_DIR)
|
||||||
|
DATABASES_TS_FILE = '{}/dbs.ts'.format(DATABASES_DIR)
|
||||||
|
DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(str(Path.home()))
|
||||||
|
|
||||||
|
|
||||||
def get_icon_path() -> str:
|
def get_icon_path() -> str:
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ from bauh.gems.appimage import CONFIG_FILE
|
|||||||
|
|
||||||
def read_config(update_file: bool = False) -> dict:
|
def read_config(update_file: bool = False) -> dict:
|
||||||
default = {
|
default = {
|
||||||
'db_updater': {
|
'database': {
|
||||||
'interval': 60 * 20,
|
'expiration': 60
|
||||||
'enabled': True
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return read(CONFIG_FILE, default, update_file=update_file)
|
return read(CONFIG_FILE, default, update_file=update_file)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import traceback
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from math import floor
|
from math import floor
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Lock
|
|
||||||
from typing import Set, Type, List, Tuple, Optional
|
from typing import Set, Type, List, Tuple, Optional
|
||||||
|
|
||||||
from colorama import Fore
|
from colorama import Fore
|
||||||
@@ -29,17 +28,13 @@ from bauh.commons.config import save_config
|
|||||||
from bauh.commons.html import bold
|
from bauh.commons.html import bold
|
||||||
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess
|
from bauh.commons.system import SystemProcess, new_subprocess, ProcessHandler, run_cmd, SimpleProcess
|
||||||
from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE, CONFIG_FILE, ROOT_DIR, \
|
from bauh.gems.appimage import query, INSTALLATION_PATH, LOCAL_PATH, SUGGESTIONS_FILE, CONFIG_FILE, ROOT_DIR, \
|
||||||
CONFIG_DIR, UPDATES_IGNORED_FILE, util, get_default_manual_installation_file_dir
|
CONFIG_DIR, UPDATES_IGNORED_FILE, util, get_default_manual_installation_file_dir, DATABASE_APPS_FILE, \
|
||||||
|
DATABASE_RELEASES_FILE, DESKTOP_ENTRIES_PATH, DATABASES_DIR
|
||||||
from bauh.gems.appimage.config import read_config
|
from bauh.gems.appimage.config import read_config
|
||||||
from bauh.gems.appimage.model import AppImage
|
from bauh.gems.appimage.model import AppImage
|
||||||
from bauh.gems.appimage.util import replace_desktop_entry_exec_command
|
from bauh.gems.appimage.util import replace_desktop_entry_exec_command
|
||||||
from bauh.gems.appimage.worker import DatabaseUpdater, SymlinksVerifier
|
from bauh.gems.appimage.worker import DatabaseUpdater, SymlinksVerifier
|
||||||
|
|
||||||
DB_APPS_PATH = '{}/{}'.format(str(Path.home()), '.local/share/bauh/appimage/apps.db')
|
|
||||||
DB_RELEASES_PATH = '{}/{}'.format(str(Path.home()), '.local/share/bauh/appimage/releases.db')
|
|
||||||
|
|
||||||
DESKTOP_ENTRIES_PATH = '{}/.local/share/applications'.format(str(Path.home()))
|
|
||||||
|
|
||||||
RE_DESKTOP_ICON = re.compile(r'Icon\s*=\s*.+\n')
|
RE_DESKTOP_ICON = re.compile(r'Icon\s*=\s*.+\n')
|
||||||
RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$')
|
RE_ICON_ENDS_WITH = re.compile(r'.+\.(png|svg)$')
|
||||||
RE_APPIMAGE_NAME = re.compile(r'(.+)\.appimage', flags=re.IGNORECASE)
|
RE_APPIMAGE_NAME = re.compile(r'(.+)\.appimage', flags=re.IGNORECASE)
|
||||||
@@ -81,7 +76,6 @@ class AppImageManager(SoftwareManager):
|
|||||||
self.http_client = context.http_client
|
self.http_client = context.http_client
|
||||||
self.logger = context.logger
|
self.logger = context.logger
|
||||||
self.file_downloader = context.file_downloader
|
self.file_downloader = context.file_downloader
|
||||||
self.db_locks = {DB_APPS_PATH: Lock(), DB_RELEASES_PATH: Lock()}
|
|
||||||
self.custom_actions = [CustomSoftwareAction(i18n_label_key='appimage.custom_action.install_file',
|
self.custom_actions = [CustomSoftwareAction(i18n_label_key='appimage.custom_action.install_file',
|
||||||
i18n_status_key='appimage.custom_action.install_file.status',
|
i18n_status_key='appimage.custom_action.install_file.status',
|
||||||
manager=self,
|
manager=self,
|
||||||
@@ -177,60 +171,74 @@ class AppImageManager(SoftwareManager):
|
|||||||
|
|
||||||
def _get_db_connection(self, db_path: str) -> sqlite3.Connection:
|
def _get_db_connection(self, db_path: str) -> sqlite3.Connection:
|
||||||
if os.path.exists(db_path):
|
if os.path.exists(db_path):
|
||||||
self.db_locks[db_path].acquire()
|
try:
|
||||||
return sqlite3.connect(db_path)
|
return sqlite3.connect(db_path)
|
||||||
|
except:
|
||||||
|
self.logger.error("Could not connect to database file '{}'".format(db_path))
|
||||||
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
self.logger.warning("Could not get a database connection. File '{}' not found".format(db_path))
|
self.logger.warning("Could not get a connection for database '{}'".format(db_path))
|
||||||
|
|
||||||
def _close_connection(self, db_path: str, con: sqlite3.Connection):
|
|
||||||
con.close()
|
|
||||||
self.db_locks[db_path].release()
|
|
||||||
|
|
||||||
def _gen_app_key(self, app: AppImage):
|
def _gen_app_key(self, app: AppImage):
|
||||||
return '{}{}'.format(app.name.lower(), app.github.lower() if app.github else '')
|
return '{}{}'.format(app.name.lower(), app.github.lower() if app.github else '')
|
||||||
|
|
||||||
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
|
def search(self, words: str, disk_loader: DiskCacheLoader, limit: int = -1, is_url: bool = False) -> SearchResult:
|
||||||
if is_url:
|
if is_url:
|
||||||
return SearchResult([], [], 0)
|
return SearchResult.empty()
|
||||||
|
|
||||||
res = SearchResult([], [], 0)
|
apps_conn = self._get_db_connection(DATABASE_APPS_FILE)
|
||||||
connection = self._get_db_connection(DB_APPS_PATH)
|
|
||||||
|
|
||||||
if connection:
|
if not apps_conn:
|
||||||
try:
|
return SearchResult.empty()
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute(query.SEARCH_APPS_BY_NAME_OR_DESCRIPTION.format(words, words))
|
|
||||||
|
|
||||||
found_map = {}
|
not_installed, found_map = [], {}
|
||||||
idx = 0
|
|
||||||
for r in cursor.fetchall():
|
|
||||||
app = AppImage(*r, i18n=self.i18n, custom_actions=self.custom_app_actions)
|
|
||||||
res.new.append(app)
|
|
||||||
found_map[self._gen_app_key(app)] = {'app': app, 'idx': idx}
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
finally:
|
try:
|
||||||
self._close_connection(DB_APPS_PATH, connection)
|
cursor = apps_conn.cursor()
|
||||||
|
cursor.execute(query.SEARCH_APPS_BY_NAME_OR_DESCRIPTION.format(words, words))
|
||||||
|
|
||||||
if res.new:
|
idx = 0
|
||||||
installed = self.read_installed(disk_loader, limit, only_apps=False, pkg_types=None, internet_available=True).installed
|
for r in cursor.fetchall():
|
||||||
|
app = AppImage(*r, i18n=self.i18n, custom_actions=self.custom_app_actions)
|
||||||
|
not_installed.append(app)
|
||||||
|
found_map[self._gen_app_key(app)] = {'app': app, 'idx': idx}
|
||||||
|
idx += 1
|
||||||
|
except:
|
||||||
|
self.logger.error("An exception happened while querying the 'apps' database")
|
||||||
|
traceback.print_exc()
|
||||||
|
apps_conn.close()
|
||||||
|
return SearchResult.empty()
|
||||||
|
|
||||||
if installed:
|
installed_found = []
|
||||||
for iapp in installed:
|
|
||||||
key = self._gen_app_key(iapp)
|
|
||||||
|
|
||||||
new_found = found_map.get(key)
|
if not_installed:
|
||||||
|
installed = self.read_installed(disk_loader=disk_loader, limit=limit,
|
||||||
|
only_apps=False,
|
||||||
|
pkg_types=None,
|
||||||
|
connection=apps_conn,
|
||||||
|
internet_available=True).installed
|
||||||
|
if installed:
|
||||||
|
for appim in installed:
|
||||||
|
key = self._gen_app_key(appim)
|
||||||
|
|
||||||
if new_found:
|
new_found = found_map.get(key)
|
||||||
del res.new[new_found['idx']]
|
|
||||||
res.installed.append(iapp)
|
|
||||||
|
|
||||||
res.total = len(res.installed) + len(res.new)
|
if new_found:
|
||||||
return res
|
del not_installed[new_found['idx']]
|
||||||
|
installed_found.append(appim)
|
||||||
|
|
||||||
def read_installed(self, disk_loader: DiskCacheLoader, limit: int = -1, only_apps: bool = False,
|
try:
|
||||||
pkg_types: Set[Type[SoftwarePackage]] = None, internet_available: bool = None, connection: sqlite3.Connection = None) -> SearchResult:
|
apps_conn.close()
|
||||||
res = SearchResult([], [], 0)
|
except:
|
||||||
|
self.logger.error("An exception happened when trying to close the connection to database file '{}'".format(DATABASE_APPS_FILE))
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
return SearchResult(new=not_installed, installed=installed_found, total=len(not_installed) + len(installed_found))
|
||||||
|
|
||||||
|
def read_installed(self, disk_loader: Optional[DiskCacheLoader], limit: int = -1, only_apps: bool = False,
|
||||||
|
pkg_types: Optional[Set[Type[SoftwarePackage]]] = None, internet_available: bool = None, connection: sqlite3.Connection = None) -> SearchResult:
|
||||||
|
installed_apps = []
|
||||||
|
res = SearchResult(installed_apps, [], 0)
|
||||||
|
|
||||||
if os.path.exists(INSTALLATION_PATH):
|
if os.path.exists(INSTALLATION_PATH):
|
||||||
installed = run_cmd('ls {}*/data.json'.format(INSTALLATION_PATH), print_error=False)
|
installed = run_cmd('ls {}*/data.json'.format(INSTALLATION_PATH), print_error=False)
|
||||||
@@ -243,19 +251,19 @@ class AppImageManager(SoftwareManager):
|
|||||||
app = AppImage(installed=True, i18n=self.i18n, custom_actions=self.custom_app_actions, **json.loads(f.read()))
|
app = AppImage(installed=True, i18n=self.i18n, custom_actions=self.custom_app_actions, **json.loads(f.read()))
|
||||||
app.icon_url = app.icon_path
|
app.icon_url = app.icon_path
|
||||||
|
|
||||||
res.installed.append(app)
|
installed_apps.append(app)
|
||||||
names.add("'{}'".format(app.name.lower()))
|
names.add("'{}'".format(app.name.lower()))
|
||||||
|
|
||||||
if res.installed:
|
if installed_apps:
|
||||||
con = self._get_db_connection(DB_APPS_PATH) if not connection else connection
|
apps_con = self._get_db_connection(DATABASE_APPS_FILE) if not connection else connection
|
||||||
|
|
||||||
if con:
|
if apps_con:
|
||||||
try:
|
try:
|
||||||
cursor = con.cursor()
|
cursor = apps_con.cursor()
|
||||||
cursor.execute(query.FIND_APPS_BY_NAME.format(','.join(names)))
|
cursor.execute(query.FIND_APPS_BY_NAME.format(','.join(names)))
|
||||||
|
|
||||||
for tup in cursor.fetchall():
|
for tup in cursor.fetchall():
|
||||||
for app in res.installed:
|
for app in installed_apps:
|
||||||
if app.name.lower() == tup[0].lower() and (not app.github or app.github.lower() == tup[1].lower()):
|
if app.name.lower() == tup[0].lower() and (not app.github or app.github.lower() == tup[1].lower()):
|
||||||
continuous_version = app.version == 'continuous'
|
continuous_version = app.version == 'continuous'
|
||||||
continuous_update = tup[2] == 'continuous'
|
continuous_update = tup[2] == 'continuous'
|
||||||
@@ -276,15 +284,16 @@ class AppImageManager(SoftwareManager):
|
|||||||
|
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
|
self.logger.error("An exception happened while querying the database file {}".format(apps_con))
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
if not connection:
|
if not connection: # the connection can only be closed if it was opened within this method
|
||||||
self._close_connection(DB_APPS_PATH, con)
|
apps_con.close()
|
||||||
|
|
||||||
ignored_updates = self._read_ignored_updates()
|
ignored_updates = self._read_ignored_updates()
|
||||||
|
|
||||||
if ignored_updates:
|
if ignored_updates:
|
||||||
for app in res.installed:
|
for app in installed_apps:
|
||||||
if app.supports_ignored_updates() and app.name in ignored_updates:
|
if app.supports_ignored_updates() and app.name in ignored_updates:
|
||||||
app.updates_ignored = True
|
app.updates_ignored = True
|
||||||
|
|
||||||
@@ -406,40 +415,53 @@ class AppImageManager(SoftwareManager):
|
|||||||
history = []
|
history = []
|
||||||
res = PackageHistory(pkg, history, -1)
|
res = PackageHistory(pkg, history, -1)
|
||||||
|
|
||||||
connection = self._get_db_connection(DB_APPS_PATH)
|
app_con = self._get_db_connection(DATABASE_APPS_FILE)
|
||||||
|
|
||||||
if connection:
|
if not app_con:
|
||||||
try:
|
return res
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
cursor.execute(query.FIND_APP_ID_BY_NAME_AND_GITHUB.format(pkg.name.lower(), pkg.github.lower() if pkg.github else ''))
|
try:
|
||||||
app_tuple = cursor.fetchone()
|
cursor = app_con.cursor()
|
||||||
|
|
||||||
if not app_tuple:
|
cursor.execute(query.FIND_APP_ID_BY_NAME_AND_GITHUB.format(pkg.name.lower(), pkg.github.lower() if pkg.github else ''))
|
||||||
self.logger.warning("Could not retrieve {} from the database {}".format(pkg, DB_APPS_PATH))
|
app_tuple = cursor.fetchone()
|
||||||
return res
|
|
||||||
finally:
|
|
||||||
self._close_connection(DB_APPS_PATH, connection)
|
|
||||||
|
|
||||||
connection = self._get_db_connection(DB_RELEASES_PATH)
|
if not app_tuple:
|
||||||
|
self.logger.warning("Could not retrieve {} from the database {}".format(pkg, DATABASE_APPS_FILE))
|
||||||
|
return res
|
||||||
|
except:
|
||||||
|
self.logger.error("An exception happened while querying the database file '{}'".format(DATABASE_APPS_FILE))
|
||||||
|
traceback.print_exc()
|
||||||
|
app_con.close()
|
||||||
|
return res
|
||||||
|
|
||||||
if connection:
|
app_con.close()
|
||||||
try:
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
releases = cursor.execute(query.FIND_RELEASES_BY_APP_ID.format(app_tuple[0]))
|
releases_con = self._get_db_connection(DATABASE_RELEASES_FILE)
|
||||||
|
|
||||||
if releases:
|
if not releases_con:
|
||||||
for idx, tup in enumerate(releases):
|
return res
|
||||||
history.append({'0_version': tup[0], '1_published_at': datetime.strptime(tup[2], '%Y-%m-%dT%H:%M:%SZ') if tup[2] else '', '2_url_download': tup[1]})
|
|
||||||
|
|
||||||
if res.pkg_status_idx == -1 and pkg.version == tup[0]:
|
try:
|
||||||
res.pkg_status_idx = idx
|
cursor = releases_con.cursor()
|
||||||
|
|
||||||
finally:
|
releases = cursor.execute(query.FIND_RELEASES_BY_APP_ID.format(app_tuple[0]))
|
||||||
self._close_connection(DB_RELEASES_PATH, connection)
|
|
||||||
|
|
||||||
return res
|
if releases:
|
||||||
|
for idx, tup in enumerate(releases):
|
||||||
|
history.append({'0_version': tup[0],
|
||||||
|
'1_published_at': datetime.strptime(tup[2], '%Y-%m-%dT%H:%M:%SZ') if tup[
|
||||||
|
2] else '', '2_url_download': tup[1]})
|
||||||
|
|
||||||
|
if res.pkg_status_idx == -1 and pkg.version == tup[0]:
|
||||||
|
res.pkg_status_idx = idx
|
||||||
|
|
||||||
|
return res
|
||||||
|
except:
|
||||||
|
self.logger.error("An exception happened while querying the database file '{}'".format(DATABASE_RELEASES_FILE))
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
releases_con.close()
|
||||||
|
|
||||||
def _find_desktop_file(self, folder: str) -> str:
|
def _find_desktop_file(self, folder: str) -> str:
|
||||||
for r, d, files in os.walk(folder):
|
for r, d, files in os.walk(folder):
|
||||||
@@ -452,7 +474,7 @@ class AppImageManager(SoftwareManager):
|
|||||||
if RE_ICON_ENDS_WITH.match(f):
|
if RE_ICON_ENDS_WITH.match(f):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def install(self, pkg: AppImage, root_password: str, disk_loader: DiskCacheLoader, watcher: ProcessWatcher) -> TransactionResult:
|
def install(self, pkg: AppImage, root_password: str, disk_loader: Optional[DiskCacheLoader], watcher: ProcessWatcher) -> TransactionResult:
|
||||||
handler = ProcessHandler(watcher)
|
handler = ProcessHandler(watcher)
|
||||||
|
|
||||||
out_dir = INSTALLATION_PATH + pkg.get_clean_name()
|
out_dir = INSTALLATION_PATH + pkg.get_clean_name()
|
||||||
@@ -587,22 +609,17 @@ class AppImageManager(SoftwareManager):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool):
|
def prepare(self, task_manager: TaskManager, root_password: str, internet_available: bool):
|
||||||
local_config = read_config(update_file=True)
|
|
||||||
interval = local_config['db_updater']['interval'] or 20 * 60
|
|
||||||
|
|
||||||
updater = DatabaseUpdater(task_man=task_manager,
|
|
||||||
i18n=self.context.i18n,
|
|
||||||
http_client=self.context.http_client, logger=self.context.logger,
|
|
||||||
db_locks=self.db_locks, interval=interval,
|
|
||||||
internet_checker=self.context.internet_checker)
|
|
||||||
if local_config['db_updater']['enabled']:
|
|
||||||
updater.start()
|
|
||||||
elif internet_available:
|
|
||||||
updater.download_databases() # only once
|
|
||||||
|
|
||||||
symlink_check = SymlinksVerifier(taskman=task_manager, i18n=self.i18n, logger=self.logger)
|
symlink_check = SymlinksVerifier(taskman=task_manager, i18n=self.i18n, logger=self.logger)
|
||||||
symlink_check.start()
|
symlink_check.start()
|
||||||
|
|
||||||
|
if internet_available:
|
||||||
|
updater = DatabaseUpdater(task_man=task_manager, i18n=self.context.i18n,
|
||||||
|
http_client=self.context.http_client, logger=self.context.logger)
|
||||||
|
|
||||||
|
if updater.should_update(read_config()):
|
||||||
|
updater.register_task()
|
||||||
|
updater.start()
|
||||||
|
|
||||||
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
def list_updates(self, internet_available: bool) -> List[PackageUpdate]:
|
||||||
res = self.read_installed(disk_loader=None, internet_available=internet_available)
|
res = self.read_installed(disk_loader=None, internet_available=internet_available)
|
||||||
|
|
||||||
@@ -615,12 +632,15 @@ class AppImageManager(SoftwareManager):
|
|||||||
return updates
|
return updates
|
||||||
|
|
||||||
def list_warnings(self, internet_available: bool) -> List[str]:
|
def list_warnings(self, internet_available: bool) -> List[str]:
|
||||||
pass
|
dbfiles = glob.glob('{}/*.db'.format(DATABASES_DIR))
|
||||||
|
|
||||||
|
if not dbfiles or len({f for f in (DATABASE_APPS_FILE, DATABASE_RELEASES_FILE) if f in dbfiles}) != 2:
|
||||||
|
return [self.i18n['appimage.warning.missing_db_files'].format(appimage=bold('AppImage'))]
|
||||||
|
|
||||||
def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]:
|
def list_suggestions(self, limit: int, filter_installed: bool) -> List[PackageSuggestion]:
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
connection = self._get_db_connection(DB_APPS_PATH)
|
connection = self._get_db_connection(DATABASE_APPS_FILE)
|
||||||
|
|
||||||
if connection:
|
if connection:
|
||||||
file = self.http_client.get(SUGGESTIONS_FILE)
|
file = self.http_client.get(SUGGESTIONS_FILE)
|
||||||
@@ -661,7 +681,7 @@ class AppImageManager(SoftwareManager):
|
|||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
self._close_connection(DB_APPS_PATH, connection)
|
connection.close()
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -705,34 +725,22 @@ class AppImageManager(SoftwareManager):
|
|||||||
config = read_config()
|
config = read_config()
|
||||||
max_width = floor(screen_width * 0.15)
|
max_width = floor(screen_width * 0.15)
|
||||||
|
|
||||||
enabled_opts = [InputOption(label=self.i18n['yes'].capitalize(), value=True),
|
opts = [
|
||||||
InputOption(label=self.i18n['no'].capitalize(), value=False)]
|
TextInputComponent(label=self.i18n['appimage.config.database.expiration'],
|
||||||
|
value=int(config['database']['expiration']) if isinstance(config['database']['expiration'], int) else '',
|
||||||
updater_opts = [
|
tooltip=self.i18n['appimage.config.database.expiration.tip'],
|
||||||
SingleSelectComponent(label=self.i18n['appimage.config.db_updates.activated'],
|
|
||||||
options=enabled_opts,
|
|
||||||
default_option=[o for o in enabled_opts if o.value == config['db_updater']['enabled']][0],
|
|
||||||
max_per_line=len(enabled_opts),
|
|
||||||
type_=SelectViewType.RADIO,
|
|
||||||
tooltip=self.i18n['appimage.config.db_updates.activated.tip'],
|
|
||||||
max_width=max_width,
|
|
||||||
id_='up_enabled'),
|
|
||||||
TextInputComponent(label=self.i18n['interval'],
|
|
||||||
value=str(config['db_updater']['interval']),
|
|
||||||
tooltip=self.i18n['appimage.config.db_updates.interval.tip'],
|
|
||||||
only_int=True,
|
only_int=True,
|
||||||
max_width=max_width,
|
max_width=max_width,
|
||||||
id_='up_int')
|
id_='appim_db_exp')
|
||||||
]
|
]
|
||||||
|
|
||||||
return PanelComponent([FormComponent(updater_opts, self.i18n['appimage.config.db_updates'])])
|
return PanelComponent([FormComponent(opts, self.i18n['appimage.config.database'])])
|
||||||
|
|
||||||
def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]:
|
def save_settings(self, component: PanelComponent) -> Tuple[bool, Optional[List[str]]]:
|
||||||
config = read_config()
|
config = read_config()
|
||||||
|
|
||||||
panel = component.components[0]
|
panel = component.components[0]
|
||||||
config['db_updater']['enabled'] = panel.get_component('up_enabled').get_selected()
|
config['database']['expiration'] = panel.get_component('appim_db_exp').get_int_value()
|
||||||
config['db_updater']['interval'] = panel.get_component('up_int').get_int_value()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
save_config(config, CONFIG_FILE)
|
save_config(config, CONFIG_FILE)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Database update
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=activated
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
|
appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=Update interval in SECONDS
|
|
||||||
appimage.custom_action.install_file=Install AppImage file
|
appimage.custom_action.install_file=Install AppImage file
|
||||||
appimage.custom_action.install_file.details=Installation details
|
appimage.custom_action.install_file.details=Installation details
|
||||||
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=S’està concedint el permís d’execució a {}
|
|||||||
appimage.task.db_update=Actualització de bases de dades
|
appimage.task.db_update=Actualització de bases de dades
|
||||||
appimage.task.symlink_check=Checking symlinks
|
appimage.task.symlink_check=Checking symlinks
|
||||||
appimage.uninstall.error.remove_folder=No s’ha pogut suprimir el directori d’instal·lació de l’aplicació {}
|
appimage.uninstall.error.remove_folder=No s’ha pogut suprimir el directori d’instal·lació de l’aplicació {}
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=Aplicacions publicades a https://appimage.github.io/
|
gem.appimage.info=Aplicacions publicades a https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Database update
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=activated
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
|
appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=Update interval in SECONDS
|
|
||||||
appimage.custom_action.install_file=Install AppImage file
|
appimage.custom_action.install_file=Install AppImage file
|
||||||
appimage.custom_action.install_file.details=Installation details
|
appimage.custom_action.install_file.details=Installation details
|
||||||
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=Ausführberechtigungen für {}
|
|||||||
appimage.task.db_update=Updating databases
|
appimage.task.db_update=Updating databases
|
||||||
appimage.task.symlink_check=Checking symlinks
|
appimage.task.symlink_check=Checking symlinks
|
||||||
appimage.uninstall.error.remove_folder=Anwendungordner {} konnte nicht entfernt werden
|
appimage.uninstall.error.remove_folder=Anwendungordner {} konnte nicht entfernt werden
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=Anwendungen veröffentlicht auf https://appimage.github.io/
|
gem.appimage.info=Anwendungen veröffentlicht auf https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Database update
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=activated
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
|
appimage.config.database.expiration.tip=appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=Update interval in SECONDS
|
|
||||||
appimage.custom_action.install_file=Install AppImage file
|
appimage.custom_action.install_file=Install AppImage file
|
||||||
appimage.custom_action.install_file.details=Installation details
|
appimage.custom_action.install_file.details=Installation details
|
||||||
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=Giving execution permission to {}
|
|||||||
appimage.task.db_update=Updating databases
|
appimage.task.db_update=Updating databases
|
||||||
appimage.task.symlink_check=Checking symlinks
|
appimage.task.symlink_check=Checking symlinks
|
||||||
appimage.uninstall.error.remove_folder=Could not remove the application installation directory {}
|
appimage.uninstall.error.remove_folder=Could not remove the application installation directory {}
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=Applications published at https://appimage.github.io/
|
gem.appimage.info=Applications published at https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Actualización de la base de datos
|
appimage.config.database=Base de datos
|
||||||
appimage.config.db_updates.activated=activada
|
appimage.config.database.expiration=Expiración
|
||||||
appimage.config.db_updates.activated.tip=Será posible buscar actualizaciones relacionadas con las aplicaciones instaladas
|
appimage.config.database.expiration.tip=Define el período (en minutos) en el que la base de datos será considerada actualizada durante el proceso de inicialización. Use 0 si siempre desea actualizarla.
|
||||||
appimage.config.db_updates.interval.tip=Intervalo de actualización en SEGUNDOS
|
|
||||||
appimage.custom_action.install_file=Instalar archivo AppImage
|
appimage.custom_action.install_file=Instalar archivo AppImage
|
||||||
appimage.custom_action.install_file.details=Detalles de instalación
|
appimage.custom_action.install_file.details=Detalles de instalación
|
||||||
appimage.custom_action.install_file.invalid_file=Debe definir un archivo AppImage válido
|
appimage.custom_action.install_file.invalid_file=Debe definir un archivo AppImage válido
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=Concediendo permiso de ejecución a {}
|
|||||||
appimage.task.db_update=Actualizando base de datos
|
appimage.task.db_update=Actualizando base de datos
|
||||||
appimage.task.symlink_check=Verificando links simbólicos
|
appimage.task.symlink_check=Verificando links simbólicos
|
||||||
appimage.uninstall.error.remove_folder=No se pudo eliminar el directorio de instalación de la aplicación {}
|
appimage.uninstall.error.remove_folder=No se pudo eliminar el directorio de instalación de la aplicación {}
|
||||||
|
appimage.warning.missing_db_files=No se encontraron archivos de la base de datos {appimage}. Algunas acciones no funcionarán correctamente.
|
||||||
gem.appimage.info=Aplicativos publicados en https://appimage.github.io/
|
gem.appimage.info=Aplicativos publicados en https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Mise à jour de la base de données
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=activé
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=La recherche de mises à jour liées aux applications installées sera possible
|
appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=Intervalle de mises à jour en SECONDES
|
|
||||||
appimage.custom_action.install_file=Installer fichier AppImage
|
appimage.custom_action.install_file=Installer fichier AppImage
|
||||||
appimage.custom_action.install_file.details=Détails d'installation
|
appimage.custom_action.install_file.details=Détails d'installation
|
||||||
appimage.custom_action.install_file.invalid_file=Vous devez définir un fichier AppImage valide
|
appimage.custom_action.install_file.invalid_file=Vous devez définir un fichier AppImage valide
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission={} est maintenant exécutable
|
|||||||
appimage.task.db_update=Mise à jour des bases de données
|
appimage.task.db_update=Mise à jour des bases de données
|
||||||
appimage.task.symlink_check=Verification des symlinks
|
appimage.task.symlink_check=Verification des symlinks
|
||||||
appimage.uninstall.error.remove_folder=Impossible de supprimer le répertoire d'installation {}
|
appimage.uninstall.error.remove_folder=Impossible de supprimer le répertoire d'installation {}
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=Applications publiées à https://appimage.github.io/
|
gem.appimage.info=Applications publiées à https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Database update
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=activated
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=It will be possible to check for updates related to the installed applications
|
appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=Update interval in SECONDS
|
|
||||||
appimage.custom_action.install_file=Install AppImage file
|
appimage.custom_action.install_file=Install AppImage file
|
||||||
appimage.custom_action.install_file.details=Installation details
|
appimage.custom_action.install_file.details=Installation details
|
||||||
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
appimage.custom_action.install_file.invalid_file=You must define a valid AppImage file
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=Gdare il permesso di esecuzione a {}
|
|||||||
appimage.task.db_update=Aggiornamento dei database
|
appimage.task.db_update=Aggiornamento dei database
|
||||||
appimage.task.symlink_check=Checking symlinks
|
appimage.task.symlink_check=Checking symlinks
|
||||||
appimage.uninstall.error.remove_folder=Impossibile rimuovere la directory di installazione dell'applicazione {}
|
appimage.uninstall.error.remove_folder=Impossibile rimuovere la directory di installazione dell'applicazione {}
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=Applicazioni pubblicate su https://appimage.github.io/
|
gem.appimage.info=Applicazioni pubblicate su https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Atualização da base de dados
|
appimage.config.database=Banco de dados
|
||||||
appimage.config.db_updates.activated=ativada
|
appimage.config.database.expiration=Expiração
|
||||||
appimage.config.db_updates.activated.tip=Será possível verificar se há atualizações para os aplicativos instalados
|
appimage.config.database.expiration.tip=Define o período (em minutos) no qual o banco de dados será considerado atualizado durante o processo de inicialização. Use 0 se você quiser que ele sempre seja atualizado.
|
||||||
appimage.config.db_updates.interval.tip=Intervalo de atualização em SEGUNDOS
|
|
||||||
appimage.custom_action.install_file=Instalar arquivo AppImage
|
appimage.custom_action.install_file=Instalar arquivo AppImage
|
||||||
appimage.custom_action.install_file.details=Detalhes de instalação
|
appimage.custom_action.install_file.details=Detalhes de instalação
|
||||||
appimage.custom_action.install_file.invalid_file=Você precisa definir um arquivo AppImage válido
|
appimage.custom_action.install_file.invalid_file=Você precisa definir um arquivo AppImage válido
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=Concedendo permissão de execução para {}
|
|||||||
appimage.task.db_update=Atualizando base de dados
|
appimage.task.db_update=Atualizando base de dados
|
||||||
appimage.task.symlink_check=Verificando links simbólicos
|
appimage.task.symlink_check=Verificando links simbólicos
|
||||||
appimage.uninstall.error.remove_folder=Não foi possível remover o diretóri ode instalação do aplicativo {}
|
appimage.uninstall.error.remove_folder=Não foi possível remover o diretóri ode instalação do aplicativo {}
|
||||||
|
appimage.warning.missing_db_files=Arquivos do banco de dados {appimage} não foram encontrados. Algumas ações não funcionarão corretamente.
|
||||||
gem.appimage.info=Aplicativos publicados em https://appimage.github.io/
|
gem.appimage.info=Aplicativos publicados em https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Обновление базы данных
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=Активировать
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=Позволит проверять обновления установленных приложений
|
appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=Интервал обновления в секундах
|
|
||||||
appimage.custom_action.install_file=Установить файл AppImage
|
appimage.custom_action.install_file=Установить файл AppImage
|
||||||
appimage.custom_action.install_file.details=Детали установки
|
appimage.custom_action.install_file.details=Детали установки
|
||||||
appimage.custom_action.install_file.invalid_file=Вы должны определить допустимый файл AppImage
|
appimage.custom_action.install_file.invalid_file=Вы должны определить допустимый файл AppImage
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission=Установить права на выполне
|
|||||||
appimage.task.db_update=Обновление базы данных
|
appimage.task.db_update=Обновление базы данных
|
||||||
appimage.task.symlink_check=Checking symlinks
|
appimage.task.symlink_check=Checking symlinks
|
||||||
appimage.uninstall.error.remove_folder=Не удалось удалить каталог приложения {}
|
appimage.uninstall.error.remove_folder=Не удалось удалить каталог приложения {}
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=Приложения, опубликованные на https://appimage.github.io/
|
gem.appimage.info=Приложения, опубликованные на https://appimage.github.io/
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
appimage.config.db_updates=Veritabanı güncelle
|
appimage.config.database=Database
|
||||||
appimage.config.db_updates.activated=Aktifleştirildi
|
appimage.config.database.expiration=Expiration
|
||||||
appimage.config.db_updates.activated.tip=Yüklü uygulamalarla ilgili güncellemeleri kontrol etmek mümkün olacak
|
appimage.config.database.expiration.tip=It defines the period (in minutes) in which the database will be considered up to date during the initialization process. Use 0 if you always want to update it.
|
||||||
appimage.config.db_updates.interval.tip=SANİYE cinsinden güncelleme aralığı
|
|
||||||
appimage.custom_action.install_file=AppImage dosyasını yükle
|
appimage.custom_action.install_file=AppImage dosyasını yükle
|
||||||
appimage.custom_action.install_file.details=Kurulum detayları
|
appimage.custom_action.install_file.details=Kurulum detayları
|
||||||
appimage.custom_action.install_file.invalid_file=Geçerli bir AppImage dosyası tanımlamanız gerekir
|
appimage.custom_action.install_file.invalid_file=Geçerli bir AppImage dosyası tanımlamanız gerekir
|
||||||
@@ -34,5 +33,6 @@ appimage.install.permission={} için yürütme izni veriliyor
|
|||||||
appimage.task.db_update=Veritabanları güncelleniyor
|
appimage.task.db_update=Veritabanları güncelleniyor
|
||||||
appimage.task.symlink_check=Checking symlinks
|
appimage.task.symlink_check=Checking symlinks
|
||||||
appimage.uninstall.error.remove_folder={} uygulama kurulum dizini kaldırılamadı
|
appimage.uninstall.error.remove_folder={} uygulama kurulum dizini kaldırılamadı
|
||||||
|
appimage.warning.missing_db_files={appimage} database files were not found. Some actions will not work properly.
|
||||||
gem.appimage.info=https://appimage.github.io/ adresinde yayınlanan uygulamalar
|
gem.appimage.info=https://appimage.github.io/ adresinde yayınlanan uygulamalar
|
||||||
gem.appimage.label=AppImage
|
gem.appimage.label=AppImage
|
||||||
|
|||||||
@@ -5,108 +5,151 @@ import os
|
|||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from typing import Optional
|
||||||
import requests
|
|
||||||
|
|
||||||
from bauh.api.abstract.handler import TaskManager, ProcessWatcher
|
from bauh.api.abstract.handler import TaskManager, ProcessWatcher
|
||||||
from bauh.api.http import HttpClient
|
from bauh.api.http import HttpClient
|
||||||
from bauh.commons.internet import InternetChecker
|
from bauh.gems.appimage import get_icon_path, INSTALLATION_PATH, SYMLINKS_DIR, util, DATABASES_TS_FILE, \
|
||||||
from bauh.gems.appimage import LOCAL_PATH, get_icon_path, INSTALLATION_PATH, SYMLINKS_DIR, util
|
DATABASES_DIR, DATABASE_APPS_FILE, DATABASE_RELEASES_FILE, URL_COMPRESSED_DATABASES
|
||||||
from bauh.gems.appimage.model import AppImage
|
from bauh.gems.appimage.model import AppImage
|
||||||
from bauh.view.util.translation import I18n
|
from bauh.view.util.translation import I18n
|
||||||
|
|
||||||
|
|
||||||
class DatabaseUpdater(Thread):
|
class DatabaseUpdater(Thread):
|
||||||
URL_DB = 'https://raw.githubusercontent.com/vinifmor/bauh-files/master/appimage/dbs.tar.gz'
|
COMPRESS_FILE_PATH = '{}/db.tar.gz'.format(DATABASES_DIR)
|
||||||
COMPRESS_FILE_PATH = LOCAL_PATH + '/db.tar.gz'
|
|
||||||
|
|
||||||
def __init__(self, task_man: TaskManager, i18n: I18n, http_client: HttpClient,
|
def __init__(self, task_man: TaskManager, i18n: I18n, http_client: HttpClient, logger: logging.Logger):
|
||||||
logger: logging.Logger, db_locks: dict, interval: int,
|
|
||||||
internet_checker: InternetChecker):
|
|
||||||
super(DatabaseUpdater, self).__init__(daemon=True)
|
super(DatabaseUpdater, self).__init__(daemon=True)
|
||||||
self.http_client = http_client
|
self.http_client = http_client
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.db_locks = db_locks
|
|
||||||
self.sleep_time = interval
|
|
||||||
self.i18n = i18n
|
self.i18n = i18n
|
||||||
self.task_man = task_man
|
self.task_man = task_man
|
||||||
self.task_id = 'appim_db'
|
self.task_id = 'appim_db'
|
||||||
self.internet_checker = internet_checker
|
|
||||||
|
def should_update(self, appimage_config: dict) -> bool:
|
||||||
|
ti = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
db_exp = int(appimage_config['database']['expiration'])
|
||||||
|
except ValueError:
|
||||||
|
self.logger.error("Could not parse settings property 'database.expiration': {}".format(appimage_config['database']['expiration']))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if db_exp <= 0:
|
||||||
|
self.logger.info("No expiration time configured for the AppImage database")
|
||||||
|
return True
|
||||||
|
|
||||||
|
files = {*glob.glob('{}/*'.format(DATABASES_DIR))}
|
||||||
|
|
||||||
|
if not files:
|
||||||
|
self.logger.warning('No database files on {}'.format(DATABASES_DIR))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if DATABASES_TS_FILE not in files:
|
||||||
|
self.logger.warning("No database timestamp file found ({})".format(DATABASES_TS_FILE))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if DATABASE_APPS_FILE not in files:
|
||||||
|
self.logger.warning("Database file '{}' not found".format(DATABASE_APPS_FILE))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if DATABASE_RELEASES_FILE not in files:
|
||||||
|
self.logger.warning("Database file '{}' not found".format(DATABASE_RELEASES_FILE))
|
||||||
|
return True
|
||||||
|
|
||||||
|
with open(DATABASES_TS_FILE) as f:
|
||||||
|
dbs_ts_str = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbs_timestamp = datetime.fromtimestamp(float(dbs_ts_str))
|
||||||
|
except:
|
||||||
|
self.logger.error('Could not parse the databases timestamp: {}'.format(dbs_ts_str))
|
||||||
|
traceback.print_exc()
|
||||||
|
return True
|
||||||
|
|
||||||
|
update = dbs_timestamp + timedelta(minutes=db_exp) <= datetime.utcnow()
|
||||||
|
self.logger.info('Finished. Took {0:.2f} seconds'.format(time.time() - ti))
|
||||||
|
return update
|
||||||
|
|
||||||
|
def register_task(self):
|
||||||
|
if self.task_man:
|
||||||
|
self.task_man.register_task(self.task_id, self.i18n['appimage.task.db_update'], get_icon_path())
|
||||||
|
|
||||||
def _finish_task(self):
|
def _finish_task(self):
|
||||||
if self.task_man:
|
if self.task_man:
|
||||||
self.task_man.update_progress(self.task_id, 100, None)
|
self.task_man.update_progress(self.task_id, 100, None)
|
||||||
self.task_man.finish_task(self.task_id)
|
self.task_man.finish_task(self.task_id)
|
||||||
self.task_man = None
|
self.task_man = None
|
||||||
|
self.logger.info("Finished")
|
||||||
|
|
||||||
|
def _update_task_progress(self, progress: float, substatus: Optional[str] = None):
|
||||||
|
if self.task_man:
|
||||||
|
self.task_man.update_progress(self.task_id, progress, substatus)
|
||||||
|
|
||||||
def download_databases(self):
|
def download_databases(self):
|
||||||
if self.task_man:
|
self._update_task_progress(1) # TODO add substatus
|
||||||
self.task_man.register_task(self.task_id, self.i18n['appimage.task.db_update'], get_icon_path())
|
|
||||||
self.task_man.update_progress(self.task_id, 10, None)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not self.internet_checker.is_available():
|
|
||||||
self._finish_task()
|
|
||||||
return
|
|
||||||
except requests.exceptions.ConnectionError:
|
|
||||||
self.logger.warning('The internet connection seems to be off.')
|
|
||||||
self._finish_task()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.logger.info('Retrieving AppImage databases')
|
self.logger.info('Retrieving AppImage databases')
|
||||||
|
|
||||||
|
database_timestamp = datetime.utcnow().timestamp()
|
||||||
try:
|
try:
|
||||||
res = self.http_client.get(self.URL_DB, session=False)
|
res = self.http_client.get(URL_COMPRESSED_DATABASES, session=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error("An error ocurred while downloading the AppImage database: {}".format(e.__class__.__name__))
|
self.logger.error("An error ocurred while downloading the AppImage database: {}".format(e.__class__.__name__))
|
||||||
res = None
|
res = None
|
||||||
|
|
||||||
if res:
|
if not res:
|
||||||
Path(LOCAL_PATH).mkdir(parents=True, exist_ok=True)
|
self.logger.warning('Could not download the database file {}'.format(URL_COMPRESSED_DATABASES))
|
||||||
|
|
||||||
with open(self.COMPRESS_FILE_PATH, 'wb+') as f:
|
|
||||||
f.write(res.content)
|
|
||||||
|
|
||||||
self.logger.info("Database file saved at {}".format(self.COMPRESS_FILE_PATH))
|
|
||||||
|
|
||||||
old_db_files = glob.glob(LOCAL_PATH + '/*.db')
|
|
||||||
|
|
||||||
if old_db_files:
|
|
||||||
self.logger.info('Deleting old database files')
|
|
||||||
for f in old_db_files:
|
|
||||||
self.db_locks[f].acquire()
|
|
||||||
try:
|
|
||||||
os.remove(f)
|
|
||||||
finally:
|
|
||||||
self.db_locks[f].release()
|
|
||||||
|
|
||||||
self.logger.info('Old database files deleted')
|
|
||||||
|
|
||||||
self.logger.info('Uncompressing {}'.format(self.COMPRESS_FILE_PATH))
|
|
||||||
|
|
||||||
try:
|
|
||||||
tf = tarfile.open(self.COMPRESS_FILE_PATH)
|
|
||||||
tf.extractall(LOCAL_PATH)
|
|
||||||
self.logger.info('Successfully uncompressed file {}'.format(self.COMPRESS_FILE_PATH))
|
|
||||||
except:
|
|
||||||
self.logger.error('Could not extract file {}'.format(self.COMPRESS_FILE_PATH))
|
|
||||||
traceback.print_exc()
|
|
||||||
finally:
|
|
||||||
self.logger.info('Deleting {}'.format(self.COMPRESS_FILE_PATH))
|
|
||||||
os.remove(self.COMPRESS_FILE_PATH)
|
|
||||||
self.logger.info('Successfully removed {}'.format(self.COMPRESS_FILE_PATH))
|
|
||||||
self._finish_task()
|
|
||||||
else:
|
|
||||||
self.logger.warning('Could not download the database file {}'.format(self.URL_DB))
|
|
||||||
self._finish_task()
|
self._finish_task()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._update_task_progress(25) # TODO add substatus
|
||||||
|
Path(DATABASES_DIR).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with open(self.COMPRESS_FILE_PATH, 'wb+') as f:
|
||||||
|
f.write(res.content)
|
||||||
|
|
||||||
|
self.logger.info("Database file saved at {}".format(self.COMPRESS_FILE_PATH))
|
||||||
|
|
||||||
|
self._update_task_progress(50) # TODO add substatus
|
||||||
|
old_db_files = glob.glob(DATABASES_DIR + '/*.db')
|
||||||
|
|
||||||
|
if old_db_files:
|
||||||
|
self.logger.info('Deleting old database files')
|
||||||
|
for f in old_db_files:
|
||||||
|
os.remove(f)
|
||||||
|
|
||||||
|
self.logger.info('Old database files deleted')
|
||||||
|
|
||||||
|
self._update_task_progress(75) # TODO add substatus
|
||||||
|
self.logger.info('Uncompressing {}'.format(self.COMPRESS_FILE_PATH))
|
||||||
|
|
||||||
|
try:
|
||||||
|
tf = tarfile.open(self.COMPRESS_FILE_PATH)
|
||||||
|
tf.extractall(DATABASES_DIR)
|
||||||
|
self.logger.info('Successfully uncompressed file {}'.format(self.COMPRESS_FILE_PATH))
|
||||||
|
except:
|
||||||
|
self.logger.error('Could not extract file {}'.format(self.COMPRESS_FILE_PATH))
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
|
self.logger.info('Deleting {}'.format(self.COMPRESS_FILE_PATH))
|
||||||
|
os.remove(self.COMPRESS_FILE_PATH)
|
||||||
|
self.logger.info('File {} deleted'.format(self.COMPRESS_FILE_PATH))
|
||||||
|
|
||||||
|
self._update_task_progress(95)
|
||||||
|
self.logger.info("Saving database timestamp {}".format(database_timestamp))
|
||||||
|
|
||||||
|
with open(DATABASES_TS_FILE, 'w+') as f:
|
||||||
|
f.write(str(database_timestamp))
|
||||||
|
|
||||||
|
self.logger.info("Database timestamp saved")
|
||||||
|
|
||||||
|
self._finish_task()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
self.download_databases()
|
||||||
self.download_databases()
|
|
||||||
self.logger.info('Sleeping')
|
|
||||||
time.sleep(self.sleep_time)
|
|
||||||
|
|
||||||
|
|
||||||
class SymlinksVerifier(Thread):
|
class SymlinksVerifier(Thread):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from bauh.api.abstract.cache import MemoryCache, MemoryCacheFactory
|
from bauh.api.abstract.cache import MemoryCache, MemoryCacheFactory
|
||||||
|
|
||||||
@@ -106,7 +107,7 @@ class DefaultMemoryCacheFactory(MemoryCacheFactory):
|
|||||||
self.expiration_time = expiration_time
|
self.expiration_time = expiration_time
|
||||||
self.cleaner = cleaner
|
self.cleaner = cleaner
|
||||||
|
|
||||||
def new(self, expiration: int = None) -> MemoryCache:
|
def new(self, expiration: Optional[int] = None) -> MemoryCache:
|
||||||
instance = DefaultMemoryCache(expiration if expiration is not None else self.expiration_time)
|
instance = DefaultMemoryCache(expiration if expiration is not None else self.expiration_time)
|
||||||
|
|
||||||
if self.cleaner:
|
if self.cleaner:
|
||||||
|
|||||||
Reference in New Issue
Block a user