1
0
Fork 0
mirror of https://github.com/YunoHost/apps.git synced 2024-09-03 20:06:07 +02:00

Use pathlib, fix ruff issues

This commit is contained in:
Salamandar 2024-03-18 23:06:09 +01:00
parent b44b882474
commit 9d9748a218
5 changed files with 110 additions and 107 deletions

View file

@ -0,0 +1 @@
#!/usr/bin/env python3

View file

@ -1,45 +1,39 @@
import time
import re
import toml
#!/usr/bin/env python3
import base64
import hashlib
import hmac
import os
import string
import random
import urllib
import json
import re
import string
import sys
from slugify import slugify
from flask import (
Flask,
send_from_directory,
render_template,
session,
redirect,
request,
)
from flask_babel import Babel
import time
import urllib
from pathlib import Path
import toml
from flask import Flask, redirect, render_template, request, send_from_directory, session
from flask.typing import ResponseReturnValue
from flask_babel import Babel # type: ignore
from flask_babel import gettext as _
from github import Github, InputGitAuthor
from slugify import slugify
sys.path = [os.path.dirname(__file__)] + sys.path
from utils import (
get_locale,
get_catalog,
get_wishlist,
get_stars,
get_app_md_and_screenshots,
save_wishlist_submit_for_ratelimit,
from .utils import (
check_wishlist_submit_ratelimit,
get_app_md_and_screenshots,
get_catalog,
get_locale,
get_stars,
get_wishlist,
save_wishlist_submit_for_ratelimit,
)
app = Flask(__name__, static_url_path="/assets", static_folder="assets")
try:
config = toml.loads(open("config.toml").read())
except Exception as e:
config = toml.loads(Path("config.toml").open().read())
except RuntimeError:
print(
"You should create a config.toml with the appropriate key/values, cf config.toml.example"
)
@ -126,8 +120,8 @@ def popularity_json():
@app.route("/app/<app_id>")
def app_info(app_id):
infos = get_catalog()["apps"].get(app_id)
app_folder = os.path.join(config["APPS_CACHE"], app_id)
if not infos or not os.path.exists(app_folder):
app_folder = Path(config["APPS_CACHE"]) / app_id
if not infos or not app_folder.exists():
return f"App {app_id} not found", 404
get_app_md_and_screenshots(app_folder, infos)
@ -144,7 +138,7 @@ def app_info(app_id):
@app.route("/app/<app_id>/<action>")
def star_app(app_id, action):
def star_app(app_id: str, action) -> ResponseReturnValue:
assert action in ["star", "unstar"]
if app_id not in get_catalog()["apps"] and app_id not in get_wishlist():
return _("App %(app_id) not found", app_id=app_id), 404
@ -153,26 +147,23 @@ def star_app(app_id, action):
_("You must be logged in to be able to star an app")
+ "<br/><br/>"
+ _(
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users."
"<br/><br/>"
"'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: "
"entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
),
401,
)
app_star_folder = os.path.join(".stars", app_id)
app_star_for_this_user = os.path.join(
".stars", app_id, session.get("user", {})["id"]
)
app_star_folder = Path(".stars") / app_id
app_star_for_this_user = app_star_folder / (session.get("user", {})["id"])
if not os.path.exists(app_star_folder):
os.mkdir(app_star_folder)
app_star_folder.mkdir(exist_ok=True)
if action == "star":
open(app_star_for_this_user, "w").write("")
app_star_for_this_user.open("w").write("")
elif action == "unstar":
try:
os.remove(app_star_for_this_user)
except FileNotFoundError:
pass
app_star_folder.unlink(missing_ok=True)
if app_id in get_catalog()["apps"]:
return redirect(f"/app/{app_id}")
@ -203,7 +194,10 @@ def add_to_wishlist():
_("You must be logged in to submit an app to the wishlist")
+ "<br/><br/>"
+ _(
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users."
"<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more "
"specifically: entering at least 5 topics, reading at least 30 posts, and spending at least "
"10 minutes reading posts."
)
)
return render_template(
@ -258,7 +252,8 @@ def add_to_wishlist():
check_wishlist_submit_ratelimit(session["user"]["username"]) is True
and session["user"]["bypass_ratelimit"] is False,
_(
"Proposing wishlist additions is limited to once every 15 days per user. Please try again in a few days."
"Proposing wishlist additions is limited to once every 15 days per user. "
"Please try again in a few days."
),
),
(len(name) >= 3, _("App name should be at least 3 characters")),
@ -298,7 +293,8 @@ def add_to_wishlist():
for keyword in boring_keywords_to_check_for_people_not_reading_the_instructions
),
_(
"Please focus on what the app does, without using marketing, fuzzy terms, or repeating that the app is 'free' and 'self-hostable'."
"Please focus on what the app does, without using marketing, fuzzy terms, "
"or repeating that the app is 'free' and 'self-hostable'."
),
),
(
@ -342,7 +338,8 @@ def add_to_wishlist():
csrf_token=csrf_token,
successmsg=None,
errormsg=_(
"An entry with the name %(slug)s already exists in the wishlist, instead, you can <a href='%(url)s'>add a star to the app to show your interest</a>.",
"An entry with the name %(slug)s already exists in the wishlist, instead, "
"you can <a href='%(url)s'>add a star to the app to show your interest</a>.",
slug=slug,
url=url,
),
@ -362,12 +359,13 @@ def add_to_wishlist():
# Get the commit base for the new branch, and create it
commit_sha = repo.get_branch(repo.default_branch).commit.sha
repo.create_git_ref(ref=f"refs/heads/{new_branch}", sha=commit_sha)
except exception as e:
except Exception as e:
print("… Failed to create branch ?")
print(e)
url = "https://github.com/YunoHost/apps/pulls?q=is%3Apr+is%3Aopen+wishlist"
errormsg = _(
"Failed to create the pull request to add the app to the wishlist… Maybe there's already <a href='%(url)s'>a waiting PR for this app</a>? Else, please report the issue to the YunoHost team.",
"Failed to create the pull request to add the app to the wishlist… Maybe there's already "
"<a href='%(url)s'>a waiting PR for this app</a>? Else, please report the issue to the YunoHost team.",
url=url,
)
return render_template(
@ -419,7 +417,8 @@ Description: {description}
url = f"https://github.com/YunoHost/apps/pull/{pr.number}"
successmsg = _(
"Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. You can track progress here: <a href='%(url)s'>%(url)s</a>",
"Your proposed app has succesfully been submitted. It must now be validated by the YunoHost team. "
"You can track progress here: <a href='%(url)s'>%(url)s</a>",
url=url,
)
@ -495,7 +494,10 @@ def sso_login_callback():
_("Unfortunately, login was denied.")
+ "<br/><br/>"
+ _(
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users.<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more specifically: entering at least 5 topics, reading at least 30 posts, and spending at least 10 minutes reading posts."
"Note that, due to various abuses, we restricted login on the app store to 'trust level 1' users."
"<br/><br/>'Trust level 1' is obtained after interacting a minimum with the forum, and more "
"specifically: entering at least 5 topics, reading at least 30 posts, and spending at least "
"10 minutes reading posts."
),
403,
)

View file

@ -1,14 +1,17 @@
import os
#!/usr/bin/env python3
install_dir = os.path.dirname(__file__)
from pathlib import Path
install_dir = Path(__file__).resolve().parent
command = f"{install_dir}/venv/bin/gunicorn"
pythonpath = install_dir
pythonpath = str(install_dir)
workers = 4
user = "appstore"
bind = f"unix:{install_dir}/sock"
pid = "/run/gunicorn/appstore-pid"
errorlog = "/var/log/appstore/error.log"
accesslog = "/var/log/appstore/access.log"
access_log_format = '%({X-Real-IP}i)s %({X-Forwarded-For}i)s %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
access_log_format = \
'%({X-Real-IP}i)s %({X-Forwarded-For}i)s %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
loglevel = "warning"
capture_output = True

View file

@ -1,9 +0,0 @@
Flask==2.3.2
python-slugify
PyGithub
toml
pycmarkgfm
gunicorn
emoji
Babel
Flask-Babel

View file

@ -1,15 +1,22 @@
import time
#!/usr/bin/env python3
import base64
import os
import json
import toml
import os
import subprocess
import time
from hashlib import md5
from pathlib import Path
import pycmarkgfm
import tomlkit
from emoji import emojize
from flask import request
from hashlib import md5
AVAILABLE_LANGUAGES = ["en"] + os.listdir("translations")
TRANSLATIONS_DIR = Path(__file__).parent / "translations"
AVAILABLE_LANGUAGES = ["en"] + [str(d) for d in TRANSLATIONS_DIR.glob("*/")]
def get_locale():
@ -19,12 +26,13 @@ def get_locale():
def get_catalog():
path = "../builds/default/v3/apps.json"
mtime = os.path.getmtime(path)
path = Path("../builds/default/v3/apps.json").resolve()
mtime = path.stat().st_mtime
if get_catalog.mtime_catalog != mtime:
get_catalog.mtime_catalog = mtime
catalog = json.load(open(path))
catalog = json.load(path.open())
catalog["categories"] = {c["id"]: c for c in catalog["categories"]}
catalog["antifeatures"] = {c["id"]: c for c in catalog["antifeatures"]}
@ -58,11 +66,11 @@ get_catalog()
def get_wishlist():
path = "../wishlist.toml"
mtime = os.path.getmtime(path)
path = Path("../wishlist.toml").resolve()
mtime = path.stat().st_mtime
if get_wishlist.mtime_wishlist != mtime:
get_wishlist.mtime_wishlist = mtime
get_wishlist.cache_wishlist = toml.load(open(path))
get_wishlist.cache_wishlist = tomlkit.load(path.open())
return get_wishlist.cache_wishlist
@ -96,26 +104,22 @@ get_stars()
def check_wishlist_submit_ratelimit(user):
dir_ = os.path.join(".wishlist_ratelimit")
if not os.path.exists(dir_):
os.mkdir(dir_)
dir_ = Path(".wishlist_ratelimit").resolve()
dir_.mkdir(exist_ok=True)
f = dir_ / md5(user.encode()).hexdigest()
f = os.path.join(dir_, md5(user.encode()).hexdigest())
return not os.path.exists(f) or (time.time() - os.path.getmtime(f)) > (
return not f.exists() or (time.time() - f.stat().st_mtime) > (
15 * 24 * 3600
) # 15 days
def save_wishlist_submit_for_ratelimit(user):
dir_ = os.path.join(".wishlist_ratelimit")
if not os.path.exists(dir_):
os.mkdir(dir_)
dir_ = Path(".wishlist_ratelimit").resolve()
dir_.mkdir(exist_ok=True)
f = os.path.join(dir_, md5(user.encode()).hexdigest())
open(f, "w").write("")
f = dir_ / md5(user.encode()).hexdigest()
f.touch()
def human_to_binary(size: str) -> int:
@ -133,7 +137,7 @@ def human_to_binary(size: str) -> int:
try:
size_ = float(size)
except Exception:
raise Exception(f"Failed to convert size {size} to float")
raise Exception(f"Failed to convert size {size} to float") # noqa: B904
return int(size_ * factor[suffix])
@ -141,46 +145,48 @@ def human_to_binary(size: str) -> int:
def get_app_md_and_screenshots(app_folder, infos):
locale = get_locale()
if locale != "en" and os.path.exists(
os.path.join(app_folder, "doc", f"DESCRIPTION_{locale}.md")
):
description_path = os.path.join(app_folder, "doc", f"DESCRIPTION_{locale}.md")
elif os.path.exists(os.path.join(app_folder, "doc", "DESCRIPTION.md")):
description_path = os.path.join(app_folder, "doc", "DESCRIPTION.md")
description_path_localized = app_folder / "doc" / f"DESCRIPTION_{locale}.md"
description_path_generic = app_folder / "doc" / "DESCRIPTION.md"
if locale != "en" and description_path_localized.exists():
description_path = description_path_localized
elif description_path_generic.exists():
description_path = description_path_generic
else:
description_path = None
if description_path:
with open(description_path) as f:
with description_path.open() as f:
infos["full_description_html"] = emojize(
pycmarkgfm.gfm_to_html(f.read()), language="alias"
)
else:
infos["full_description_html"] = infos["manifest"]["description"][locale]
if locale != "en" and os.path.exists(
os.path.join(app_folder, "doc", f"PRE_INSTALL_{locale}.md")
):
pre_install_path = os.path.join(app_folder, "doc", f"PRE_INSTALL_{locale}.md")
elif os.path.exists(os.path.join(app_folder, "doc", "PRE_INSTALL.md")):
pre_install_path = os.path.join(app_folder, "doc", "PRE_INSTALL.md")
preinstall_path_localized = app_folder / "doc" / f"PRE_INSTALL_{locale}.md"
preinstall_path_generic = app_folder / "doc" / "PRE_INSTALL.md"
if locale != "en" and preinstall_path_localized.exists():
pre_install_path = preinstall_path_localized
elif preinstall_path_generic.exists():
pre_install_path = preinstall_path_generic
else:
pre_install_path = None
if pre_install_path:
with open(pre_install_path) as f:
with pre_install_path.open() as f:
infos["pre_install_html"] = emojize(
pycmarkgfm.gfm_to_html(f.read()), language="alias"
)
infos["screenshot"] = None
screenshots_folder = os.path.join(app_folder, "doc", "screenshots")
screenshots_folder = app_folder / "doc" / "screenshots"
if os.path.exists(screenshots_folder):
with os.scandir(screenshots_folder) as it:
if screenshots_folder.exists():
with screenshots_folder.iterdir() as it:
for entry in it:
ext = os.path.splitext(entry.name)[1].replace(".", "").lower()
ext = entry.suffix.lower()
if entry.is_file() and ext in ("png", "jpg", "jpeg", "webp", "gif"):
with open(entry.path, "rb") as img_file:
with entry.open("rb") as img_file:
data = base64.b64encode(img_file.read()).decode("utf-8")
infos["screenshot"] = (
f"data:image/{ext};charset=utf-8;base64,{data}"