"""Package exposing fdroid_dl.Config."""
# -*- coding: utf-8 -*-
import logging
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
import copy
import json
import os
import os.path
import shutil
from tempfile import NamedTemporaryFile
from .repoconfig import RepoConfig
from .metadata import Metadata
from .index import Index
from ..json import GenericJSONEncoder
LOGGER = logging.getLogger('model.Config')
[docs]class Config(MutableMapping):
"""
Main Class responsible for handling the f-droid.json config file.
This Class represents the config file used by fdroid-dl and will default to
https://f-droid.org/repo/ as bas repo and add at least the
**org.fdroid.fdroid** app for download into the queue.
"""
DEFAULTS = {
"f-droid": {
'https://f-droid.org/repo/': {
"apps": ["org.fdroid.fdroid"]
}
},
"metadata": {}
}
def __init__(self, filename='fdroid-dl.json', repo_dir='./repo',
metadata_dir='./metadata', cache_dir='.cache', apk_versions=1):
"""
Parameters
----------
filename : str
filename including path where to store the generated config file
repo_dir : str
path to f-droid repo, apk files will be downloded here
metadata_dir : str
path to f-droid metadata directory, images and descriptions will
be stored here
cache_dir : str
directory to store extracted and parsed index.json files from
f-droid repo
apk_versions: int
how many versions of apk files should be downloaded
"""
self.__filename = filename
self.__repo = repo_dir
self.__metadata_dir = metadata_dir
self.__cache_dir = cache_dir
self.__store = {}
self.__indices = {}
self.__metadata = None
self.__apk_versions = apk_versions
self.__init_defaults()
self.__prepare_fs()
@property
def filename(self):
return str(self.__filename)
@property
def repo_dir(self):
return str(self.__repo)
@property
def metadata_dir(self):
return str(self.__metadata_dir)
@property
def cache_dir(self):
return str(self.__cache_dir)
@property
def store(self):
return str(self.__store)
@property
def metadata(self):
return self.__metadata
@property
def size(self):
return len(self.__store.keys())
@property
def apk_versions(self):
return int(self.__apk_versions)
def __init_defaults(self):
self.__store = copy.deepcopy(Config.DEFAULTS)
for key in Config.DEFAULTS['f-droid'].keys():
cfg = RepoConfig(key, self.__store['f-droid'][key], self)
self.__store['f-droid'][cfg.url] = cfg
self.__metadata = Metadata(self, Config.DEFAULTS.get('metadata', {}))
def __prepare_fs(self):
if not os.path.exists(self.__repo):
os.makedirs(self.__repo)
if not os.path.exists(self.__metadata_dir):
os.makedirs(self.__metadata_dir)
if not os.path.exists(self.__cache_dir):
os.makedirs(self.__cache_dir)
[docs] def load(self, file=None):
"""
Loads given file handler or filename into Config object and resolves.
"""
if file is None:
file = self.__filename
if not hasattr(file, 'read'):
if not os.path.exists(file):
with open(file, 'w') as fl:
fl.write('{}')
file = open(file, 'r')
if os.path.isfile(self.__filename):
with open(self.__filename) as config_file:
try:
file_data = json.load(config_file)
if 'f-droid' in file_data:
for key in file_data['f-droid'].keys():
cfg = RepoConfig(
key, file_data['f-droid'][key], self)
self.__store['f-droid'][cfg.url] = cfg
if 'metadata' in file_data:
self.__metadata = Metadata(self, file_data['metadata'])
self.__store['metadata'] = self.__metadata
except Exception:
LOGGER.exception("Fatal error reading %s", config_file)
def save(self):
with NamedTemporaryFile(mode='w') as tmp:
json.dump(self.__store, tmp, sort_keys=True,
indent=4, cls=GenericJSONEncoder)
tmp.flush()
shutil.copy(tmp.name, self.__filename)
return self
@property
def repos(self):
for key in self.__store['f-droid'].keys():
cfg = self.__store['f-droid'][key]
if not isinstance(cfg, RepoConfig):
self.__store['f-droid'][key] = RepoConfig(key, cfg, self)
yield cfg
@property
def indices(self):
for repo in self.repos:
if repo.url in self.__indices:
yield self.__indices[repo.url]
elif os.path.exists(repo.filename):
with Index.from_json(repo.filename, key=repo.url) as idx:
self.__indices[repo.url] = idx
yield idx
[docs] def repo(self, url):
"""
Return RepoConfig based on url.
Searches in config file for given url and returns the corresponding
RepoConfig Class representation of it. if the repo is marked as
having errors it will not be found. A KeyError is being raised if
given url is not found or has an error node in the json file.
Parameters
----------
key : str
url of repo in config file
Returns
-------
fdroid_dl.model.Index
index found for given key
Raises
------
KeyError
raised if given url is not found or is in error state
"""
if url in self.__store['f-droid']:
cfg = self.__store['f-droid'][url]
if not isinstance(cfg, RepoConfig):
self.__store['f-droid'][url] = RepoConfig(url, cfg, self)
if 'error' not in cfg:
return cfg
raise KeyError("repo with url: %s not found" % url)
[docs] def index(self, url):
"""
Return Index based on url.
Searches for downloaded index file based on repository url as
configured in config file.
Parameters
----------
url : str
url of repo in config file
Returns
-------
fdroid_dl.model.Index
index found for given url
"""
repo = self.repo(url)
if repo.url in self.__indices:
return self.__indices[repo.url]
if os.path.exists(repo.filename):
with Index.from_json(repo.filename, key=repo.url) as idx:
self.__indices[repo.url] = idx
return self.__indices[repo.url]
raise KeyError(
"index with url: %s not found on filesystem file: %s" %
(url, repo.filename))
def __repr__(self):
"""."""
return "<Config: %s>" % str(self.__store)
#######################
# implement "dict"
#######################
def __getitem__(self, key):
"""."""
return self.__store[key]
def __setitem__(self, key, value):
"""."""
self.__store[key] = value
def __delitem__(self, key):
"""."""
del self.__store[key]
def __iter__(self):
"""."""
return iter(self.__store)
def __len__(self):
"""."""
return len(self.__store)
#######################
# implement "with"
#######################
def __enter__(self):
"""."""
self.load()
return self
def __exit__(self, type, value, traceback):
"""."""
self.save()