mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 07:17:17 +00:00
add watcher cache support
This commit is contained in:
parent
e7736e985f
commit
71196dc58b
@ -17,7 +17,11 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from typing import Dict, List, Optional, Tuple
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.repository import Repository
|
from ahriman.core.repository import Repository
|
||||||
@ -30,7 +34,9 @@ class Watcher:
|
|||||||
package status watcher
|
package status watcher
|
||||||
:ivar architecture: repository architecture
|
:ivar architecture: repository architecture
|
||||||
:ivar known: list of known packages. For the most cases `packages` should be used instead
|
:ivar known: list of known packages. For the most cases `packages` should be used instead
|
||||||
|
:ivar logger: class logger
|
||||||
:ivar repository: repository object
|
:ivar repository: repository object
|
||||||
|
:ivar status: daemon status
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, architecture: str, config: Configuration) -> None:
|
def __init__(self, architecture: str, config: Configuration) -> None:
|
||||||
@ -39,12 +45,21 @@ class Watcher:
|
|||||||
:param architecture: repository architecture
|
:param architecture: repository architecture
|
||||||
:param config: configuration instance
|
:param config: configuration instance
|
||||||
'''
|
'''
|
||||||
|
self.logger = logging.getLogger('http')
|
||||||
|
|
||||||
self.architecture = architecture
|
self.architecture = architecture
|
||||||
self.repository = Repository(architecture, config)
|
self.repository = Repository(architecture, config)
|
||||||
|
|
||||||
self.known: Dict[str, Tuple[Package, BuildStatus]] = {}
|
self.known: Dict[str, Tuple[Package, BuildStatus]] = {}
|
||||||
self.status = BuildStatus()
|
self.status = BuildStatus()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cache_path(self) -> str:
|
||||||
|
'''
|
||||||
|
:return: path to dump with json cache
|
||||||
|
'''
|
||||||
|
return os.path.join(self.repository.paths.root, 'cache.json')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def packages(self) -> List[Tuple[Package, BuildStatus]]:
|
def packages(self) -> List[Tuple[Package, BuildStatus]]:
|
||||||
'''
|
'''
|
||||||
@ -52,6 +67,41 @@ class Watcher:
|
|||||||
'''
|
'''
|
||||||
return list(self.known.values())
|
return list(self.known.values())
|
||||||
|
|
||||||
|
def _cache_load(self) -> None:
|
||||||
|
'''
|
||||||
|
update current state from cache
|
||||||
|
'''
|
||||||
|
def parse_single(properties: Dict[str, Any]) -> None:
|
||||||
|
package = Package.from_json(properties['package'])
|
||||||
|
status = BuildStatus(**properties['status'])
|
||||||
|
if package.base in self.known:
|
||||||
|
self.known[package.base] = (package, status)
|
||||||
|
|
||||||
|
if not os.path.isfile(self.cache_path):
|
||||||
|
return
|
||||||
|
with open(self.cache_path) as cache:
|
||||||
|
dump = json.load(cache)
|
||||||
|
for item in dump['packages']:
|
||||||
|
try:
|
||||||
|
parse_single(item)
|
||||||
|
except Exception:
|
||||||
|
self.logger.exception(f'cannot parse item f{item} to package', exc_info=True)
|
||||||
|
|
||||||
|
def _cache_save(self) -> None:
|
||||||
|
'''
|
||||||
|
dump current cache to filesystem
|
||||||
|
'''
|
||||||
|
dump = {
|
||||||
|
'packages': [
|
||||||
|
{
|
||||||
|
'package': package.view(),
|
||||||
|
'status': status.view()
|
||||||
|
} for package, status in self.packages
|
||||||
|
]
|
||||||
|
}
|
||||||
|
with open(self.cache_path, 'w') as cache:
|
||||||
|
json.dump(dump, cache)
|
||||||
|
|
||||||
def get(self, base: str) -> Tuple[Package, BuildStatus]:
|
def get(self, base: str) -> Tuple[Package, BuildStatus]:
|
||||||
'''
|
'''
|
||||||
get current package base build status
|
get current package base build status
|
||||||
@ -71,6 +121,7 @@ class Watcher:
|
|||||||
else:
|
else:
|
||||||
_, status = current
|
_, status = current
|
||||||
self.known[package.base] = (package, status)
|
self.known[package.base] = (package, status)
|
||||||
|
self._cache_load()
|
||||||
|
|
||||||
def remove(self, base: str) -> None:
|
def remove(self, base: str) -> None:
|
||||||
'''
|
'''
|
||||||
@ -78,6 +129,7 @@ class Watcher:
|
|||||||
:param base: package base
|
:param base: package base
|
||||||
'''
|
'''
|
||||||
self.known.pop(base, None)
|
self.known.pop(base, None)
|
||||||
|
self._cache_save()
|
||||||
|
|
||||||
def update(self, base: str, status: BuildStatusEnum, package: Optional[Package]) -> None:
|
def update(self, base: str, status: BuildStatusEnum, package: Optional[Package]) -> None:
|
||||||
'''
|
'''
|
||||||
@ -90,6 +142,7 @@ class Watcher:
|
|||||||
package, _ = self.known[base]
|
package, _ = self.known[base]
|
||||||
full_status = BuildStatus(status)
|
full_status = BuildStatus(status)
|
||||||
self.known[base] = (package, full_status)
|
self.known[base] = (package, full_status)
|
||||||
|
self._cache_save()
|
||||||
|
|
||||||
def update_self(self, status: BuildStatusEnum) -> None:
|
def update_self(self, status: BuildStatusEnum) -> None:
|
||||||
'''
|
'''
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Union
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
class BuildStatusEnum(Enum):
|
class BuildStatusEnum(Enum):
|
||||||
@ -71,3 +71,13 @@ class BuildStatus:
|
|||||||
'''
|
'''
|
||||||
self.status = BuildStatusEnum(status) if status else BuildStatusEnum.Unknown
|
self.status = BuildStatusEnum(status) if status else BuildStatusEnum.Unknown
|
||||||
self.timestamp = timestamp or int(datetime.datetime.utcnow().timestamp())
|
self.timestamp = timestamp or int(datetime.datetime.utcnow().timestamp())
|
||||||
|
|
||||||
|
def view(self) -> Dict[str, Any]:
|
||||||
|
'''
|
||||||
|
generate json status view
|
||||||
|
:return: json-friendly dictionary
|
||||||
|
'''
|
||||||
|
return {
|
||||||
|
'status': self.status.value,
|
||||||
|
'timestamp': self.timestamp
|
||||||
|
}
|
||||||
|
@ -23,10 +23,10 @@ import aur # type: ignore
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from pyalpm import vercmp # type: ignore
|
from pyalpm import vercmp # type: ignore
|
||||||
from srcinfo.parse import parse_srcinfo # type: ignore
|
from srcinfo.parse import parse_srcinfo # type: ignore
|
||||||
from typing import Dict, List, Optional, Set, Type
|
from typing import Any, Dict, List, Optional, Set, Type
|
||||||
|
|
||||||
from ahriman.core.alpm.pacman import Pacman
|
from ahriman.core.alpm.pacman import Pacman
|
||||||
from ahriman.core.exceptions import InvalidPackageInfo
|
from ahriman.core.exceptions import InvalidPackageInfo
|
||||||
@ -76,31 +76,6 @@ class Package:
|
|||||||
'''
|
'''
|
||||||
return f'{self.aur_url}/packages/{self.base}'
|
return f'{self.aur_url}/packages/{self.base}'
|
||||||
|
|
||||||
def actual_version(self, paths: RepositoryPaths) -> str:
|
|
||||||
'''
|
|
||||||
additional method to handle VCS package versions
|
|
||||||
:param paths: repository paths instance
|
|
||||||
:return: package version if package is not VCS and current version according to VCS otherwise
|
|
||||||
'''
|
|
||||||
if not self.is_vcs:
|
|
||||||
return self.version
|
|
||||||
|
|
||||||
from ahriman.core.build_tools.task import Task
|
|
||||||
|
|
||||||
clone_dir = os.path.join(paths.cache, self.base)
|
|
||||||
logger = logging.getLogger('build_details')
|
|
||||||
Task.fetch(clone_dir, self.git_url)
|
|
||||||
|
|
||||||
# update pkgver first
|
|
||||||
check_output('makepkg', '--nodeps', '--nobuild', exception=None, cwd=clone_dir, logger=logger)
|
|
||||||
# generate new .SRCINFO and put it to parser
|
|
||||||
srcinfo_source = check_output('makepkg', '--printsrcinfo', exception=None, cwd=clone_dir, logger=logger)
|
|
||||||
srcinfo, errors = parse_srcinfo(srcinfo_source)
|
|
||||||
if errors:
|
|
||||||
raise InvalidPackageInfo(errors)
|
|
||||||
|
|
||||||
return self.full_version(srcinfo.get('epoch'), srcinfo['pkgver'], srcinfo['pkgrel'])
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_archive(cls: Type[Package], path: str, pacman: Pacman, aur_url: str) -> Package:
|
def from_archive(cls: Type[Package], path: str, pacman: Pacman, aur_url: str) -> Package:
|
||||||
'''
|
'''
|
||||||
@ -142,6 +117,23 @@ class Package:
|
|||||||
|
|
||||||
return cls(srcinfo['pkgbase'], version, aur_url, packages)
|
return cls(srcinfo['pkgbase'], version, aur_url, packages)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package:
|
||||||
|
'''
|
||||||
|
construct package properties from json dump
|
||||||
|
:param dump: json dump body
|
||||||
|
:return: package properties
|
||||||
|
'''
|
||||||
|
packages = {
|
||||||
|
key: PackageDescription(**value)
|
||||||
|
for key, value in dump.get('packages', {})
|
||||||
|
}
|
||||||
|
return Package(
|
||||||
|
base=dump['base'],
|
||||||
|
version=dump['version'],
|
||||||
|
aur_url=dump['aur_url'],
|
||||||
|
packages=packages)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dependencies(path: str) -> Set[str]:
|
def dependencies(path: str) -> Set[str]:
|
||||||
'''
|
'''
|
||||||
@ -196,6 +188,31 @@ class Package:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise InvalidPackageInfo(str(e))
|
raise InvalidPackageInfo(str(e))
|
||||||
|
|
||||||
|
def actual_version(self, paths: RepositoryPaths) -> str:
|
||||||
|
'''
|
||||||
|
additional method to handle VCS package versions
|
||||||
|
:param paths: repository paths instance
|
||||||
|
:return: package version if package is not VCS and current version according to VCS otherwise
|
||||||
|
'''
|
||||||
|
if not self.is_vcs:
|
||||||
|
return self.version
|
||||||
|
|
||||||
|
from ahriman.core.build_tools.task import Task
|
||||||
|
|
||||||
|
clone_dir = os.path.join(paths.cache, self.base)
|
||||||
|
logger = logging.getLogger('build_details')
|
||||||
|
Task.fetch(clone_dir, self.git_url)
|
||||||
|
|
||||||
|
# update pkgver first
|
||||||
|
check_output('makepkg', '--nodeps', '--nobuild', exception=None, cwd=clone_dir, logger=logger)
|
||||||
|
# generate new .SRCINFO and put it to parser
|
||||||
|
srcinfo_source = check_output('makepkg', '--printsrcinfo', exception=None, cwd=clone_dir, logger=logger)
|
||||||
|
srcinfo, errors = parse_srcinfo(srcinfo_source)
|
||||||
|
if errors:
|
||||||
|
raise InvalidPackageInfo(errors)
|
||||||
|
|
||||||
|
return self.full_version(srcinfo.get('epoch'), srcinfo['pkgver'], srcinfo['pkgrel'])
|
||||||
|
|
||||||
def is_outdated(self, remote: Package, paths: RepositoryPaths) -> bool:
|
def is_outdated(self, remote: Package, paths: RepositoryPaths) -> bool:
|
||||||
'''
|
'''
|
||||||
check if package is out-of-dated
|
check if package is out-of-dated
|
||||||
@ -206,3 +223,10 @@ class Package:
|
|||||||
remote_version = remote.actual_version(paths) # either normal version or updated VCS
|
remote_version = remote.actual_version(paths) # either normal version or updated VCS
|
||||||
result: int = vercmp(self.version, remote_version)
|
result: int = vercmp(self.version, remote_version)
|
||||||
return result < 0
|
return result < 0
|
||||||
|
|
||||||
|
def view(self) -> Dict[str, Any]:
|
||||||
|
'''
|
||||||
|
generate json package view
|
||||||
|
:return: json-friendly dictionary
|
||||||
|
'''
|
||||||
|
return asdict(self)
|
||||||
|
@ -33,7 +33,7 @@ class AhrimanView(BaseView):
|
|||||||
get current service status
|
get current service status
|
||||||
:return: 200 with service status object
|
:return: 200 with service status object
|
||||||
'''
|
'''
|
||||||
return json_response(AhrimanView.status_view(self.service.status))
|
return json_response(self.service.status.view())
|
||||||
|
|
||||||
async def post(self) -> Response:
|
async def post(self) -> Response:
|
||||||
'''
|
'''
|
||||||
|
@ -17,14 +17,9 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from dataclasses import asdict
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from aiohttp.web import View
|
from aiohttp.web import View
|
||||||
|
|
||||||
from ahriman.core.watcher.watcher import Watcher
|
from ahriman.core.watcher.watcher import Watcher
|
||||||
from ahriman.models.build_status import BuildStatus
|
|
||||||
from ahriman.models.package import Package
|
|
||||||
|
|
||||||
|
|
||||||
class BaseView(View):
|
class BaseView(View):
|
||||||
@ -39,28 +34,3 @@ class BaseView(View):
|
|||||||
'''
|
'''
|
||||||
watcher: Watcher = self.request.app['watcher']
|
watcher: Watcher = self.request.app['watcher']
|
||||||
return watcher
|
return watcher
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def package_view(package: Package, status: BuildStatus) -> Dict[str, Any]:
|
|
||||||
'''
|
|
||||||
generate json package view
|
|
||||||
:param package: package definitions
|
|
||||||
:param status: package build status
|
|
||||||
:return: json-friendly dictionary
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
'status': BaseView.status_view(status),
|
|
||||||
'package': asdict(package)
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def status_view(status: BuildStatus) -> Dict[str, Any]:
|
|
||||||
'''
|
|
||||||
generate json status view
|
|
||||||
:param status: build status
|
|
||||||
:return: json-friendly dictionary
|
|
||||||
'''
|
|
||||||
return {
|
|
||||||
'status': status.status.value,
|
|
||||||
'timestamp': status.timestamp
|
|
||||||
}
|
|
||||||
|
@ -41,7 +41,10 @@ class PackageView(BaseView):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise HTTPNotFound()
|
raise HTTPNotFound()
|
||||||
|
|
||||||
response = PackageView.package_view(package, status)
|
response = {
|
||||||
|
'package': package.view(),
|
||||||
|
'status': status.view()
|
||||||
|
}
|
||||||
return json_response(response)
|
return json_response(response)
|
||||||
|
|
||||||
async def delete(self) -> Response:
|
async def delete(self) -> Response:
|
||||||
@ -71,7 +74,7 @@ class PackageView(BaseView):
|
|||||||
data = await self.request.json()
|
data = await self.request.json()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
package = Package(**data['package']) if 'package' in data else None
|
package = Package.from_json(data['package']) if 'package' in data else None
|
||||||
status = BuildStatusEnum(data['status'])
|
status = BuildStatusEnum(data['status'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPBadRequest(text=str(e))
|
raise HTTPBadRequest(text=str(e))
|
||||||
|
@ -33,8 +33,10 @@ class PackagesView(BaseView):
|
|||||||
:return: 200 with package description on success
|
:return: 200 with package description on success
|
||||||
'''
|
'''
|
||||||
response = [
|
response = [
|
||||||
PackagesView.package_view(package, status)
|
{
|
||||||
for package, status in self.service.packages
|
'package': package.view(),
|
||||||
|
'status': status.view()
|
||||||
|
} for package, status in self.service.packages
|
||||||
]
|
]
|
||||||
return json_response(response)
|
return json_response(response)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user