feat: use github releases for installing extensions

This commit is contained in:
Vlad Stan 2023-01-12 15:33:32 +02:00
parent 38a132604b
commit 496346b3ba
6 changed files with 139 additions and 51 deletions

View file

@ -73,7 +73,7 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
async def add_installed_extension( async def add_installed_extension(
*, *,
ext_id: str, ext_id: str,
version, version: str,
active: bool, active: bool,
hash: str, hash: str,
meta: dict, meta: dict,

View file

@ -276,7 +276,7 @@ async def m009_create_installed_extensions_table(db):
""" """
CREATE TABLE IF NOT EXISTS installed_extensions ( CREATE TABLE IF NOT EXISTS installed_extensions (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
version INT NOT NULL, version TEXT NOT NULL,
active BOOLEAN DEFAULT false, active BOOLEAN DEFAULT false,
hash TEXT NOT NULL, hash TEXT NOT NULL,
meta TEXT NOT NULL DEFAULT '{}' meta TEXT NOT NULL DEFAULT '{}'

View file

@ -40,8 +40,14 @@
<q-card-section> <q-card-section>
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
<!-- hack must find better solution --> <q-img
v-if="extension.iconUrl"
:src="extension.iconUrl"
spinner-color="white"
style="max-width: 100%"
></q-img>
<q-icon <q-icon
v-else
:name="extension.icon" :name="extension.icon"
color="grey-5" color="grey-5"
style="font-size: 4rem" style="font-size: 4rem"
@ -107,34 +113,20 @@
</div> </div>
<div class="col-6"> <div class="col-6">
<q-rating <div class="float-right">
max="5" <small v-text="extension.stars"> </small>
v-model="maxStars" <q-rating
size="1.5em" max="1"
color="yellow" v-model="maxStars"
icon="star_border" size="1.5em"
icon-selected="star" color="yellow"
icon-half="star_half" icon="star"
readonly icon-selected="star"
no-dimming readonly
class="float-right" no-dimming
> >
<template v-slot:tip-1> </q-rating>
<q-tooltip>User Review Comming Soon</q-tooltip> </div>
</template>
<template v-slot:tip-2>
<q-tooltip>User Review Comming Soon</q-tooltip>
</template>
<template v-slot:tip-3>
<q-tooltip>User Review Comming Soon</q-tooltip>
</template>
<template v-slot:tip-4>
<q-tooltip>User Review Comming Soon</q-tooltip>
</template>
<template v-slot:tip-5>
<q-tooltip>User Review Comming Soon</q-tooltip>
</template>
</q-rating>
</div> </div>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
@ -279,6 +271,7 @@
inProgress: false inProgress: false
})) }))
this.filteredExtensions = this.extensions.concat([]) this.filteredExtensions = this.extensions.concat([])
console.log('### his.filteredExtensions', this.filteredExtensions)
}, },
mixins: [windowMixin] mixins: [windowMixin]
}) })

View file

@ -740,7 +740,6 @@ async def api_install_extension(
db_version = (await get_dbversions()).get(ext_id, 0) db_version = (await get_dbversions()).get(ext_id, 0)
await migrate_extension_database(extension, db_version) await migrate_extension_database(extension, db_version)
# disable by default
await add_installed_extension( await add_installed_extension(
ext_id=ext_id, ext_id=ext_id,
version=ext_info.version, version=ext_info.version,

View file

@ -105,7 +105,9 @@ async def extensions_install(
"name": ext.name, "name": ext.name,
"hash": ext.hash, "hash": ext.hash,
"icon": ext.icon, "icon": ext.icon,
"iconUrl": ext.icon_url,
"shortDescription": ext.short_description, "shortDescription": ext.short_description,
"stars": ext.stars,
"details": ext.details, "details": ext.details,
"dependencies": ext.dependencies, "dependencies": ext.dependencies,
"isInstalled": ext.id in installed_extensions, "isInstalled": ext.id in installed_extensions,

View file

@ -6,6 +6,7 @@ import sys
import urllib.request import urllib.request
import zipfile import zipfile
from http import HTTPStatus from http import HTTPStatus
from platform import release
from typing import List, NamedTuple, Optional from typing import List, NamedTuple, Optional
import httpx import httpx
@ -102,6 +103,27 @@ class ExtensionManager:
return output return output
class ExtensionRelease(BaseModel):
name: str
version: str
archive: str
description: str
@classmethod
def from_github_releases(cls, releases: dict) -> List["ExtensionRelease"]:
return list(
map(
lambda r: ExtensionRelease(
name=r["name"],
version=r["tag_name"],
archive=r["zipball_url"],
description=r["body"],
),
releases,
)
)
class InstallableExtension(BaseModel): class InstallableExtension(BaseModel):
id: str id: str
name: str name: str
@ -110,9 +132,12 @@ class InstallableExtension(BaseModel):
short_description: Optional[str] = None short_description: Optional[str] = None
details: Optional[str] = None details: Optional[str] = None
icon: Optional[str] = None icon: Optional[str] = None
icon_url: Optional[str] = None
dependencies: List[str] = [] dependencies: List[str] = []
is_admin_only: bool = False is_admin_only: bool = False
version: Optional[int] = 0 version: str = "none" # todo: move to Release
stars: int = 0
releases: Optional[List[ExtensionRelease]]
@property @property
def zip_path(self) -> str: def zip_path(self) -> str:
@ -194,6 +219,25 @@ class InstallableExtension(BaseModel):
shutil.rmtree(self.ext_upgrade_dir, True) shutil.rmtree(self.ext_upgrade_dir, True)
@classmethod
async def from_repo(cls, org, repository) -> Optional["InstallableExtension"]:
try:
repo, releases, config = await fetch_github_repo_info(org, repository)
return InstallableExtension(
id=repo["name"],
name=config.get("name"),
short_description=config.get("short_description"),
archive="xx",
hash="123",
stars=repo["stargazers_count"],
icon_url=icon_to_github_url(org, config.get("tile")),
releases=ExtensionRelease.from_github_releases(releases),
)
except Exception as e:
logger.warning(e)
return None
@classmethod @classmethod
async def get_extension_info(cls, ext_id: str, hash: str) -> "InstallableExtension": async def get_extension_info(cls, ext_id: str, hash: str) -> "InstallableExtension":
installable_extensions: List[ installable_extensions: List[
@ -229,27 +273,35 @@ class InstallableExtension(BaseModel):
try: try:
resp = await client.get(url) resp = await client.get(url)
if resp.status_code != 200: if resp.status_code != 200:
logger.warning( logger.warning(f"Cannot fetch extensions manifest at: {url}")
f"Unable to fetch extension list for repository: {url}"
)
continue continue
for e in resp.json()["extensions"]: manifest = resp.json()
extension_list += [ if "extensions" in manifest:
InstallableExtension( for e in manifest["extensions"] or []:
id=e["id"], extension_list += [
name=e["name"], InstallableExtension(
archive=e["archive"], id=e["id"],
hash=e["hash"], name=e["name"],
short_description=e["shortDescription"], archive=e["archive"],
details=e["details"] if "details" in e else "", hash=e["hash"],
icon=e["icon"], short_description=e["shortDescription"],
dependencies=e["dependencies"] details=e["details"] if "details" in e else "",
if "dependencies" in e icon=e["icon"],
else [], dependencies=e["dependencies"]
if "dependencies" in e
else [],
)
]
if "repos" in manifest:
for r in manifest["repos"]:
ext = await InstallableExtension.from_repo(
r["organisation"], r["repository"]
) )
] print("#### repo_extensions", ext)
if ext:
extension_list += [ext]
except Exception as e: except Exception as e:
logger.warning(e) logger.warning(f"Manifest {url} failed with '{str(e)}'")
return extension_list return extension_list
@ -317,3 +369,45 @@ def file_hash(filename):
while n := f.readinto(mv): while n := f.readinto(mv):
h.update(mv[:n]) h.update(mv[:n])
return h.hexdigest() return h.hexdigest()
def icon_to_github_url(org: str, path: Optional[str]) -> str:
if not path:
return ""
_, repo, *rest = path.split("/")
tail = "/".join(rest)
return f"https://github.com/{org}/{repo}/raw/main/{tail}"
async def fetch_github_repo_info(org: str, repository: str):
async with httpx.AsyncClient() as client:
repo_url = f"https://api.github.com/repos/{org}/{repository}"
resp = await client.get(repo_url)
if resp.status_code != 200:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Cannot fetch extension repo: {repo_url}",
)
repo = resp.json()
releases_url = f"https://api.github.com/repos/{org}/{repository}/releases"
resp = await client.get(releases_url)
if resp.status_code != 200:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Cannot fetch extension releases: {releases_url}",
)
releases = resp.json()
config_url = f"""https://raw.githubusercontent.com/{org}/{repository}/{repo["default_branch"]}/config.json"""
resp = await client.get(config_url)
if resp.status_code != 200:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail=f"Cannot fetch config for extension: {config_url}",
)
config = resp.json()
return repo, releases, config