Add option to disable game support detection

This commit is contained in:
rubenwardy 2022-06-25 02:27:51 +01:00
parent 42841896d1
commit d9e65f7c3a
8 changed files with 120 additions and 61 deletions

@ -328,7 +328,7 @@ def update_screenshot_sizes():
@action("Detect game support") @action("Detect game support")
def detect_game_support(): def detect_game_support():
resolver = GameSupportResolver() resolver = GameSupportResolver(db.session)
resolver.update_all() resolver.update_all()
db.session.commit() db.session.commit()

@ -17,11 +17,11 @@ import typing
from urllib.parse import quote as urlescape from urllib.parse import quote as urlescape
from flask import render_template from flask import render_template
from flask_babel import lazy_gettext, gettext from celery import uuid
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_login import login_required from flask_login import login_required
from jinja2 import Markup from jinja2 import Markup
from sqlalchemy import or_, func, and_ from sqlalchemy import func
from sqlalchemy.orm import joinedload, subqueryload from sqlalchemy.orm import joinedload, subqueryload
from wtforms import * from wtforms import *
from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
@ -29,7 +29,7 @@ from wtforms.validators import *
from app.querybuilder import QueryBuilder from app.querybuilder import QueryBuilder
from app.rediscache import has_key, set_key from app.rediscache import has_key, set_key
from app.tasks.importtasks import importRepoScreenshot from app.tasks.importtasks import importRepoScreenshot, checkZipRelease
from app.utils import * from app.utils import *
from . import bp, get_package_tabs from . import bp, get_package_tabs
from app.logic.LogicError import LogicError from app.logic.LogicError import LogicError
@ -628,6 +628,7 @@ def similar(package):
class GameSupportForm(FlaskForm): class GameSupportForm(FlaskForm):
enable_support_detection = BooleanField(lazy_gettext("Enable support detection based on dependencies"))
supported = StringField(lazy_gettext("Supported games (Comma-separated)"), [Optional()]) supported = StringField(lazy_gettext("Supported games (Comma-separated)"), [Optional()])
unsupported = StringField(lazy_gettext("Unsupported games (Comma-separated)"), [Optional()]) unsupported = StringField(lazy_gettext("Unsupported games (Comma-separated)"), [Optional()])
submit = SubmitField(lazy_gettext("Save")) submit = SubmitField(lazy_gettext("Save"))
@ -646,21 +647,38 @@ def game_support(package):
form = GameSupportForm() if can_edit else None form = GameSupportForm() if can_edit else None
if request.method == "GET": if request.method == "GET":
form.enable_support_detection.data = package.enable_game_support_detection
manual_supported_games = package.supported_games.filter_by(confidence=8).all() manual_supported_games = package.supported_games.filter_by(confidence=8).all()
form.supported.data = ", ".join([x.game.name for x in manual_supported_games if x.supports]) form.supported.data = ", ".join([x.game.name for x in manual_supported_games if x.supports])
form.unsupported.data = ", ".join([x.game.name for x in manual_supported_games if not x.supports]) form.unsupported.data = ", ".join([x.game.name for x in manual_supported_games if not x.supports])
if form and form.validate_on_submit(): if form and form.validate_on_submit():
resolver = GameSupportResolver() resolver = GameSupportResolver(db.session)
game_is_supported = []
for game in get_games_from_csv(form.supported.data or ""):
game_is_supported.append((game, True))
for game in get_games_from_csv(form.unsupported.data or ""):
game_is_supported.append((game, False))
resolver.set_supported(package, game_is_supported, 8)
db.session.commit()
return redirect(package.getURL("packages.game_support")) game_is_supported = {}
for game in get_games_from_csv(db.session, form.supported.data or ""):
game_is_supported[game.id] = True
for game in get_games_from_csv(db.session, form.unsupported.data or ""):
game_is_supported[game.id] = False
resolver.set_supported(package, game_is_supported, 8)
next_url = package.getURL("packages.game_support")
if form.enable_support_detection.data != package.enable_game_support_detection:
package.enable_game_support_detection = form.enable_support_detection.data
if package.enable_game_support_detection:
db.session.commit()
release = package.releases.first()
if release:
task_id = uuid()
checkZipRelease.apply_async((release.id, release.file_path), task_id=task_id)
next_url = url_for("tasks.check", id=task_id, r=next_url)
else:
package.supported_games.filter_by(confidence=1).delete()
db.session.commit()
return redirect(next_url)
return render_template("packages/game_support.html", package=package, form=form, return render_template("packages/game_support.html", package=package, form=form,
tabs=get_package_tabs(current_user, package), current_tab="game_support") tabs=get_package_tabs(current_user, package), current_tab="game_support")

@ -16,11 +16,12 @@
import sys import sys
from typing import List, Dict, Optional, Iterable
from typing import List, Dict, Optional, Iterator, Iterable, Tuple import sqlalchemy.orm
from app.logic.LogicError import LogicError from app.logic.LogicError import LogicError
from app.models import Package, MetaPackage, PackageType, PackageState, PackageGameSupport, db from app.models import Package, MetaPackage, PackageType, PackageState, PackageGameSupport
""" """
get_game_support(package): get_game_support(package):
@ -82,11 +83,15 @@ class PackageSet:
class GameSupportResolver: class GameSupportResolver:
session: sqlalchemy.orm.Session
checked_packages = set() checked_packages = set()
checked_metapackages = set() checked_metapackages = set()
resolved_packages: Dict[str, PackageSet] = {} resolved_packages: Dict[str, PackageSet] = {}
resolved_metapackages: Dict[str, PackageSet] = {} resolved_metapackages: Dict[str, PackageSet] = {}
def __init__(self, session):
self.session = session
def resolve_for_meta_package(self, meta: MetaPackage, history: List[str]) -> PackageSet: def resolve_for_meta_package(self, meta: MetaPackage, history: List[str]) -> PackageSet:
print(f"Resolving for {meta.name}", file=sys.stderr) print(f"Resolving for {meta.name}", file=sys.stderr)
@ -120,8 +125,6 @@ class GameSupportResolver:
return retval return retval
def resolve(self, package: Package, history: List[str]) -> PackageSet: def resolve(self, package: Package, history: List[str]) -> PackageSet:
db.session.merge(package)
key = package.getId() key = package.getId()
print(f"Resolving for {key}", file=sys.stderr) print(f"Resolving for {key}", file=sys.stderr)
@ -160,47 +163,41 @@ class GameSupportResolver:
return retval return retval
def update_all(self) -> None: def update_all(self) -> None:
for package in Package.query.filter(Package.type == PackageType.MOD, Package.state != PackageState.DELETED).all(): for package in self.session.query(Package).filter(Package.type == PackageType.MOD, Package.state != PackageState.DELETED).all():
retval = self.resolve(package, []) retval = self.resolve(package, [])
for game in retval: for game in retval:
support = PackageGameSupport(package, game, 1, True) support = PackageGameSupport(package, game, 1, True)
db.session.add(support) self.session.add(support)
""" """
Update game supported package on a package, given the confidence. Update game supported package on a package, given the confidence.
Higher confidences outweigh lower ones. Higher confidences outweigh lower ones.
""" """
def set_supported(self, package: Package, game_is_supported: List[Tuple[Package, bool]], confidence: int): def set_supported(self, package: Package, game_is_supported: Dict[int, bool], confidence: int):
previous_supported: Dict[int, PackageGameSupport] = {} previous_supported: Dict[int, PackageGameSupport] = {}
for support in package.supported_games.all(): for support in package.supported_games.all():
db.session.merge(support.game)
previous_supported[support.game.id] = support previous_supported[support.game.id] = support
seen_game = {} for game_id, supports in game_is_supported.items():
for game, supports in game_is_supported: game = self.session.query(Package).get(game_id)
if seen_game.get(game.id): lookup = previous_supported.pop(game_id, None)
continue
seen_game[game.id] = True
lookup = previous_supported.pop(game.id, None)
if lookup is None: if lookup is None:
support = PackageGameSupport(package, game, confidence, supports) support = PackageGameSupport(package, game, confidence, supports)
db.session.add(support) self.session.add(support)
elif lookup.confidence <= confidence: elif lookup.confidence <= confidence:
lookup.supports = supports lookup.supports = supports
lookup.confidence = confidence lookup.confidence = confidence
db.session.merge(lookup)
for game, support in previous_supported.items(): for game, support in previous_supported.items():
if support.confidence == confidence: if support.confidence == confidence:
db.session.delete(support) self.session.delete(support)
def update(self, package: Package) -> None: def update(self, package: Package) -> None:
retval = self.resolve(package, []) game_is_supported = {}
if package.enable_game_support_detection:
game_is_supported = [] retval = self.resolve(package, [])
for game in retval: for game in retval:
game_is_supported.append((game, True)) game_is_supported[game.id] = True
self.set_supported(package, game_is_supported, 1) self.set_supported(package, game_is_supported, 1)

@ -414,6 +414,8 @@ class Package(db.Model):
forums = db.Column(db.Integer, nullable=True) forums = db.Column(db.Integer, nullable=True)
video_url = db.Column(db.String(200), nullable=True, default=None) video_url = db.Column(db.String(200), nullable=True, default=None)
enable_game_support_detection = db.Column(db.Boolean, nullable=False, default=True)
provides = db.relationship("MetaPackage", secondary=PackageProvides, order_by=db.asc("name"), back_populates="packages") provides = db.relationship("MetaPackage", secondary=PackageProvides, order_by=db.asc("name"), back_populates="packages")
dependencies = db.relationship("Dependency", back_populates="depender", lazy="dynamic", foreign_keys=[Dependency.depender_id]) dependencies = db.relationship("Dependency", back_populates="depender", lazy="dynamic", foreign_keys=[Dependency.depender_id])

@ -14,9 +14,12 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import gitdb
import json import json
import os, shutil, gitdb import os
import shutil
from zipfile import ZipFile from zipfile import ZipFile
from git import GitCommandError from git import GitCommandError
from git_archive_all import GitArchiver from git_archive_all import GitArchiver
from kombu import uuid from kombu import uuid
@ -27,7 +30,7 @@ from app.utils import randomString, post_bot_message, addSystemNotification, add
from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_temp_dir from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_temp_dir
from .minetestcheck import build_tree, MinetestCheckError, ContentType from .minetestcheck import build_tree, MinetestCheckError, ContentType
from ..logic.LogicError import LogicError from ..logic.LogicError import LogicError
from ..logic.game_support import GameSupportResolver, PackageSet from ..logic.game_support import GameSupportResolver
from ..logic.packages import do_edit_package, ALIASES from ..logic.packages import do_edit_package, ALIASES
from ..utils.image import get_image_size from ..utils.image import get_image_size
@ -73,6 +76,25 @@ def getMeta(urlstr, author):
return result return result
@celery.task()
def releaseUpdateGameSupport(package_id: int, supported_games, unsupported_games):
with db.create_session({})() as session:
package = session.query(Package).get(package_id)
resolver = GameSupportResolver(session)
game_is_supported = {}
if supported_games:
for game in get_games_from_csv(session, supported_games):
game_is_supported[game.id] = True
if unsupported_games:
for game in get_games_from_csv(session, unsupported_games):
game_is_supported[game.id] = False
resolver.set_supported(package, game_is_supported, 10)
resolver.update(package)
session.commit()
def postReleaseCheckUpdate(self, release: PackageRelease, path): def postReleaseCheckUpdate(self, release: PackageRelease, path):
try: try:
tree = build_tree(path, expected_type=ContentType[release.package.type.name], tree = build_tree(path, expected_type=ContentType[release.package.type.name],
@ -115,20 +137,6 @@ def postReleaseCheckUpdate(self, release: PackageRelease, path):
for meta in getMetaPackages(optional_depends): for meta in getMetaPackages(optional_depends):
db.session.add(Dependency(package, meta=meta, optional=True)) db.session.add(Dependency(package, meta=meta, optional=True))
# Update game supports
if package.type == PackageType.MOD:
resolver = GameSupportResolver()
game_is_supported = []
if "supported_games" in tree.meta:
for game in get_games_from_csv(tree.meta["supported_games"]):
game_is_supported.append((game, True))
if "unsupported_games" in tree.meta:
for game in get_games_from_csv(tree.meta["unsupported_games"]):
game_is_supported.append((game, False))
resolver.set_supported(package, game_is_supported, 10)
resolver.update(package)
# Update min/max # Update min/max
if tree.meta.get("min_minetest_version"): if tree.meta.get("min_minetest_version"):
release.min_rel = MinetestRelease.get(tree.meta["min_minetest_version"], None) release.min_rel = MinetestRelease.get(tree.meta["min_minetest_version"], None)
@ -145,6 +153,10 @@ def postReleaseCheckUpdate(self, release: PackageRelease, path):
except IOError: except IOError:
pass pass
# Update game support
if package.type == PackageType.MOD:
releaseUpdateGameSupport.delay(package.id, tree.meta.get("supported_games"), tree.meta.get("unsupported_games"))
return tree return tree
except MinetestCheckError as err: except MinetestCheckError as err:

@ -68,18 +68,23 @@
</div> </div>
{% if form and package.checkPerm(current_user, "EDIT_PACKAGE") and current_user not in package.maintainers %} {% if form %}
<h2> <h2>Options</h2>
{{ _("Added by Editor") }}
<i class="ml-2 fas fa-user-edit"></i>
</h2>
{% from "macros/forms.html" import render_field, render_checkbox_field, render_submit_field %} {% from "macros/forms.html" import render_field, render_checkbox_field, render_submit_field %}
<form method="POST" action="" class="tableform"> <form method="POST" action="" class="tableform">
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{{ render_field(form.supported) }} {{ render_checkbox_field(form.enable_support_detection) }}
{{ render_field(form.unsupported) }}
{% if package.checkPerm(current_user, "EDIT_PACKAGE") and current_user not in package.maintainers %}
<h3>
{{ _("Added by Editor") }}
<i class="ml-2 fas fa-user-edit"></i>
</h3>
{{ render_field(form.supported) }}
{{ render_field(form.unsupported) }}
{% endif %}
{{ render_submit_field(form.submit, class_="mt-4 btn btn-primary") }} {{ render_submit_field(form.submit, class_="mt-4 btn btn-primary") }}
</form> </form>

@ -18,6 +18,7 @@
from functools import wraps from functools import wraps
from typing import List from typing import List
import sqlalchemy.orm
from flask import abort, redirect, url_for, request from flask import abort, redirect, url_for, request
from flask_login import current_user from flask_login import current_user
from sqlalchemy import or_, and_ from sqlalchemy import or_, and_
@ -136,14 +137,14 @@ def post_bot_message(package: Package, title: str, message: str):
thread.replies.append(reply) thread.replies.append(reply)
def get_games_from_csv(csv: str) -> List[Package]: def get_games_from_csv(session: sqlalchemy.orm.Session, csv: str) -> List[Package]:
retval = [] retval = []
supported_games_raw = csv.split(",") supported_games_raw = csv.split(",")
for game_name in supported_games_raw: for game_name in supported_games_raw:
game_name = game_name.strip() game_name = game_name.strip()
if game_name.endswith("_game"): if game_name.endswith("_game"):
game_name = game_name[:-5] game_name = game_name[:-5]
games = Package.query.filter(and_(Package.state==PackageState.APPROVED, Package.type==PackageType.GAME, games = session.query(Package).filter(and_(Package.state==PackageState.APPROVED, Package.type==PackageType.GAME,
or_(Package.name==game_name, Package.name==game_name + "_game"))).all() or_(Package.name==game_name, Package.name==game_name + "_game"))).all()
retval.extend(games) retval.extend(games)

@ -0,0 +1,24 @@
"""empty message
Revision ID: 8425c06b7d77
Revises: 8807a5279793
Create Date: 2022-06-25 00:26:29.841145
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '8425c06b7d77'
down_revision = '8807a5279793'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('package', sa.Column('enable_game_support_detection', sa.Boolean(), nullable=False, server_default="true"))
def downgrade():
op.drop_column('package', 'enable_game_support_detection')