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(
*,
ext_id: str,
version,
version: str,
active: bool,
hash: str,
meta: dict,

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import sys
import urllib.request
import zipfile
from http import HTTPStatus
from platform import release
from typing import List, NamedTuple, Optional
import httpx
@ -102,6 +103,27 @@ class ExtensionManager:
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):
id: str
name: str
@ -110,9 +132,12 @@ class InstallableExtension(BaseModel):
short_description: Optional[str] = None
details: Optional[str] = None
icon: Optional[str] = None
icon_url: Optional[str] = None
dependencies: List[str] = []
is_admin_only: bool = False
version: Optional[int] = 0
version: str = "none" # todo: move to Release
stars: int = 0
releases: Optional[List[ExtensionRelease]]
@property
def zip_path(self) -> str:
@ -194,6 +219,25 @@ class InstallableExtension(BaseModel):
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
async def get_extension_info(cls, ext_id: str, hash: str) -> "InstallableExtension":
installable_extensions: List[
@ -229,27 +273,35 @@ class InstallableExtension(BaseModel):
try:
resp = await client.get(url)
if resp.status_code != 200:
logger.warning(
f"Unable to fetch extension list for repository: {url}"
)
logger.warning(f"Cannot fetch extensions manifest at: {url}")
continue
for e in resp.json()["extensions"]:
extension_list += [
InstallableExtension(
id=e["id"],
name=e["name"],
archive=e["archive"],
hash=e["hash"],
short_description=e["shortDescription"],
details=e["details"] if "details" in e else "",
icon=e["icon"],
dependencies=e["dependencies"]
if "dependencies" in e
else [],
manifest = resp.json()
if "extensions" in manifest:
for e in manifest["extensions"] or []:
extension_list += [
InstallableExtension(
id=e["id"],
name=e["name"],
archive=e["archive"],
hash=e["hash"],
short_description=e["shortDescription"],
details=e["details"] if "details" in e else "",
icon=e["icon"],
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:
logger.warning(e)
logger.warning(f"Manifest {url} failed with '{str(e)}'")
return extension_list
@ -317,3 +369,45 @@ def file_hash(filename):
while n := f.readinto(mv):
h.update(mv[:n])
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