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")
def detect_game_support():
resolver = GameSupportResolver()
resolver = GameSupportResolver(db.session)
resolver.update_all()
db.session.commit()

@ -17,11 +17,11 @@ import typing
from urllib.parse import quote as urlescape
from flask import render_template
from flask_babel import lazy_gettext, gettext
from celery import uuid
from flask_wtf import FlaskForm
from flask_login import login_required
from jinja2 import Markup
from sqlalchemy import or_, func, and_
from sqlalchemy import func
from sqlalchemy.orm import joinedload, subqueryload
from wtforms import *
from wtforms_sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
@ -29,7 +29,7 @@ from wtforms.validators import *
from app.querybuilder import QueryBuilder
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 . import bp, get_package_tabs
from app.logic.LogicError import LogicError
@ -628,6 +628,7 @@ def similar(package):
class GameSupportForm(FlaskForm):
enable_support_detection = BooleanField(lazy_gettext("Enable support detection based on dependencies"))
supported = StringField(lazy_gettext("Supported games (Comma-separated)"), [Optional()])
unsupported = StringField(lazy_gettext("Unsupported games (Comma-separated)"), [Optional()])
submit = SubmitField(lazy_gettext("Save"))
@ -646,21 +647,38 @@ def game_support(package):
form = GameSupportForm() if can_edit else None
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()
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])
if form and form.validate_on_submit():
resolver = GameSupportResolver()
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()
resolver = GameSupportResolver(db.session)
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,
tabs=get_package_tabs(current_user, package), current_tab="game_support")

@ -16,11 +16,12 @@
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.models import Package, MetaPackage, PackageType, PackageState, PackageGameSupport, db
from app.models import Package, MetaPackage, PackageType, PackageState, PackageGameSupport
"""
get_game_support(package):
@ -82,11 +83,15 @@ class PackageSet:
class GameSupportResolver:
session: sqlalchemy.orm.Session
checked_packages = set()
checked_metapackages = set()
resolved_packages: 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:
print(f"Resolving for {meta.name}", file=sys.stderr)
@ -120,8 +125,6 @@ class GameSupportResolver:
return retval
def resolve(self, package: Package, history: List[str]) -> PackageSet:
db.session.merge(package)
key = package.getId()
print(f"Resolving for {key}", file=sys.stderr)
@ -160,47 +163,41 @@ class GameSupportResolver:
return retval
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, [])
for game in retval:
support = PackageGameSupport(package, game, 1, True)
db.session.add(support)
self.session.add(support)
"""
Update game supported package on a package, given the confidence.
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] = {}
for support in package.supported_games.all():
db.session.merge(support.game)
previous_supported[support.game.id] = support
seen_game = {}
for game, supports in game_is_supported:
if seen_game.get(game.id):
continue
seen_game[game.id] = True
lookup = previous_supported.pop(game.id, None)
for game_id, supports in game_is_supported.items():
game = self.session.query(Package).get(game_id)
lookup = previous_supported.pop(game_id, None)
if lookup is None:
support = PackageGameSupport(package, game, confidence, supports)
db.session.add(support)
self.session.add(support)
elif lookup.confidence <= confidence:
lookup.supports = supports
lookup.confidence = confidence
db.session.merge(lookup)
for game, support in previous_supported.items():
if support.confidence == confidence:
db.session.delete(support)
self.session.delete(support)
def update(self, package: Package) -> None:
retval = self.resolve(package, [])
game_is_supported = []
for game in retval:
game_is_supported.append((game, True))
game_is_supported = {}
if package.enable_game_support_detection:
retval = self.resolve(package, [])
for game in retval:
game_is_supported[game.id] = True
self.set_supported(package, game_is_supported, 1)

@ -414,6 +414,8 @@ class Package(db.Model):
forums = db.Column(db.Integer, nullable=True)
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")
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
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import gitdb
import json
import os, shutil, gitdb
import os
import shutil
from zipfile import ZipFile
from git import GitCommandError
from git_archive_all import GitArchiver
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 .minetestcheck import build_tree, MinetestCheckError, ContentType
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 ..utils.image import get_image_size
@ -73,6 +76,25 @@ def getMeta(urlstr, author):
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):
try:
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):
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
if tree.meta.get("min_minetest_version"):
release.min_rel = MinetestRelease.get(tree.meta["min_minetest_version"], None)
@ -145,6 +153,10 @@ def postReleaseCheckUpdate(self, release: PackageRelease, path):
except IOError:
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
except MinetestCheckError as err:

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

@ -18,6 +18,7 @@
from functools import wraps
from typing import List
import sqlalchemy.orm
from flask import abort, redirect, url_for, request
from flask_login import current_user
from sqlalchemy import or_, and_
@ -136,14 +137,14 @@ def post_bot_message(package: Package, title: str, message: str):
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 = []
supported_games_raw = csv.split(",")
for game_name in supported_games_raw:
game_name = game_name.strip()
if game_name.endswith("_game"):
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()
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')