mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-23 06:22:24 +01:00
178 lines
5.3 KiB
Python
178 lines
5.3 KiB
Python
# ContentDB
|
|
# Copyright (C) 2022 rubenwardy
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# 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 sys
|
|
from typing import List, Dict, Optional, Iterable
|
|
|
|
import sqlalchemy.orm
|
|
|
|
from app.logic.LogicError import LogicError
|
|
from app.models import Package, MetaPackage, PackageType, PackageState, PackageGameSupport
|
|
|
|
"""
|
|
get_game_support(package):
|
|
if package is a game:
|
|
return [ package ]
|
|
|
|
for all hard dependencies:
|
|
support = support AND get_meta_package_support(dep)
|
|
|
|
return support
|
|
|
|
get_meta_package_support(meta):
|
|
for package implementing mod name:
|
|
support = support OR get_game_support(package)
|
|
|
|
return support
|
|
"""
|
|
|
|
|
|
minetest_game_mods = {
|
|
"beds", "boats", "bucket", "carts", "default", "dungeon_loot", "env_sounds", "fire", "flowers",
|
|
"give_initial_stuff", "map", "player_api", "sethome", "spawn", "tnt", "walls", "wool",
|
|
"binoculars", "bones", "butterflies", "creative", "doors", "dye", "farming", "fireflies", "game_commands",
|
|
"keys", "mtg_craftguide", "screwdriver", "sfinv", "stairs", "vessels", "weather", "xpanes",
|
|
}
|
|
|
|
|
|
mtg_mod_blacklist = {
|
|
"pacman", "tutorial", "runorfall", "realtest_mt5", "mevo", "xaenvironment",
|
|
"survivethedays", "holidayhorrors",
|
|
}
|
|
|
|
|
|
class GameSupportResolver:
|
|
session: sqlalchemy.orm.Session
|
|
checked_packages = set()
|
|
checked_modnames = set()
|
|
resolved_packages: Dict[int, set[int]] = {}
|
|
resolved_modnames: Dict[int, set[int]] = {}
|
|
|
|
def __init__(self, session):
|
|
self.session = session
|
|
|
|
def resolve_for_meta_package(self, meta: MetaPackage, history: List[str]) -> set[int]:
|
|
print(f"Resolving for {meta.name}", file=sys.stderr)
|
|
|
|
key = meta.name
|
|
if key in self.resolved_modnames:
|
|
return self.resolved_modnames.get(key)
|
|
|
|
if key in self.checked_modnames:
|
|
print(f"Error, cycle found: {','.join(history)}", file=sys.stderr)
|
|
return set()
|
|
|
|
self.checked_modnames.add(key)
|
|
|
|
retval = set()
|
|
|
|
for package in meta.packages:
|
|
if package.state != PackageState.APPROVED:
|
|
continue
|
|
|
|
if meta.name in minetest_game_mods and package.name in mtg_mod_blacklist:
|
|
continue
|
|
|
|
ret = self.resolve(package, history)
|
|
if len(ret) == 0:
|
|
retval = set()
|
|
break
|
|
|
|
retval.update(ret)
|
|
|
|
self.resolved_modnames[key] = retval
|
|
return retval
|
|
|
|
def resolve(self, package: Package, history: List[str]) -> set[int]:
|
|
key: int = package.id
|
|
print(f"Resolving for {key}", file=sys.stderr)
|
|
|
|
history = history.copy()
|
|
history.append(package.getId())
|
|
|
|
if package.type == PackageType.GAME:
|
|
return {package.id}
|
|
|
|
if key in self.resolved_packages:
|
|
return self.resolved_packages.get(key)
|
|
|
|
if key in self.checked_packages:
|
|
print(f"Error, cycle found: {','.join(history)}", file=sys.stderr)
|
|
return set()
|
|
|
|
self.checked_packages.add(key)
|
|
|
|
if package.type != PackageType.MOD:
|
|
raise LogicError(500, "Got non-mod")
|
|
|
|
retval = set()
|
|
|
|
for dep in package.dependencies.filter_by(optional=False).all():
|
|
ret = self.resolve_for_meta_package(dep.meta_package, history)
|
|
if len(ret) == 0:
|
|
continue
|
|
elif len(retval) == 0:
|
|
retval.update(ret)
|
|
else:
|
|
retval.intersection_update(ret)
|
|
if len(retval) == 0:
|
|
raise LogicError(500, f"Detected game support contradiction, {key} may not be compatible with any games")
|
|
|
|
self.resolved_packages[key] = retval
|
|
return retval
|
|
|
|
def update_all(self) -> None:
|
|
for package in self.session.query(Package).filter(Package.type == PackageType.MOD, Package.state != PackageState.DELETED).all():
|
|
retval = self.resolve(package, [])
|
|
for game_id in retval:
|
|
game = self.session.query(Package).get(game_id)
|
|
support = PackageGameSupport(package, game, 1, True)
|
|
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: Dict[int, bool], confidence: int):
|
|
previous_supported: Dict[int, PackageGameSupport] = {}
|
|
for support in package.supported_games.all():
|
|
previous_supported[support.game.id] = support
|
|
|
|
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)
|
|
self.session.add(support)
|
|
elif lookup.confidence <= confidence:
|
|
lookup.supports = supports
|
|
lookup.confidence = confidence
|
|
|
|
for game, support in previous_supported.items():
|
|
if support.confidence == confidence:
|
|
self.session.delete(support)
|
|
|
|
def update(self, package: Package) -> None:
|
|
game_is_supported = {}
|
|
if package.enable_game_support_detection:
|
|
retval = self.resolve(package, [])
|
|
for game_id in retval:
|
|
game_is_supported[game_id] = True
|
|
|
|
self.set_supported(package, game_is_supported, 1)
|