123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- import logging
- import os
- from shlex import quote
- from django.conf import settings
- from django.core.validators import MaxValueValidator, MinValueValidator
- from django.db import models
- from django.urls import reverse
- from django_extensions.db.fields import AutoSlugField
- from django_extensions.db.models import TimeStampedModel
- from emus.utils import ChoiceEnum
- from taggit.managers import TaggableManager
- logger = logging.getLogger(__name__)
- def get_screenshot_upload_path(instance, filename):
- return f"{instance.game_system.retropie_slug}/screenshots/{filename}"
- def get_marquee_upload_path(instance, filename):
- return f"{instance.game_system.retropie_slug}/marquee/{filename}"
- def get_video_upload_path(instance, filename):
- return f"{instance.game_system.retropie_slug}/videos/{filename}"
- def get_rom_upload_path(instance, filename):
- return f"{instance.game_system.retropie_slug}/{filename}"
- class BaseModel(TimeStampedModel):
- """A base model for providing name and slugged fields for organizational models"""
- name = models.CharField(max_length=255)
- slug = AutoSlugField(populate_from="name")
- class Meta:
- ordering = ["name"]
- abstract = True
- def slugify_function(self, content):
- for element in settings.REMOVE_FROM_SLUGS:
- content = content.replace(element, "-")
- return content.lower()
- def __str__(self):
- return self.name
- class StatisticsMixin(models.Model):
- class Meta:
- abstract = True
- @property
- def rating_avg(self):
- avg = self.game_set.aggregate(models.Avg("rating"))["rating__avg"]
- if avg:
- return int(100 * avg)
- return 0
- class Genre(BaseModel, StatisticsMixin):
- def get_absolute_url(self):
- return reverse("games:genre_detail", args=[self.slug])
- class Publisher(BaseModel, StatisticsMixin):
- def get_absolute_url(self):
- return reverse("games:publisher_detail", args=[self.slug])
- class Developer(BaseModel, StatisticsMixin):
- def get_absolute_url(self):
- return reverse("games:developer_detail", args=[self.slug])
- class GameSystem(BaseModel, StatisticsMixin):
- retropie_slug = models.CharField(
- blank=True,
- null=True,
- max_length=50,
- )
- color = models.CharField(
- blank=True,
- null=True,
- max_length=6,
- help_text="Hex value for console badges",
- )
- @property
- def defaults(self):
- return settings.GAME_SYSTEM_DEFAULTS.get(self.retropie_slug, None)
- @property
- def get_color(self):
- color = self.color
- if not color and self.defaults:
- color = self.defaults.get("color", "")
- return color
- @property
- def webretro_core(self):
- core = None
- if self.defaults:
- core = self.defaults.get("webretro_core", None)
- return core
- def get_absolute_url(self):
- return reverse("games:game_system_detail", args=[self.slug])
- class Game(BaseModel):
- class Region(ChoiceEnum):
- US = "USA"
- EU = "Europe"
- JP = "Japan"
- X = "Unknown"
- game_system = models.ForeignKey(
- GameSystem,
- on_delete=models.SET_NULL,
- null=True,
- )
- release_date = models.DateField(
- blank=True,
- null=True,
- )
- developer = models.ForeignKey(
- Developer,
- on_delete=models.SET_NULL,
- blank=True,
- null=True,
- )
- publisher = models.ForeignKey(
- Publisher,
- on_delete=models.SET_NULL,
- blank=True,
- null=True,
- )
- genre = models.ManyToManyField(
- Genre,
- )
- players = models.SmallIntegerField(
- default=1,
- )
- kid_game = models.BooleanField(
- default=False,
- )
- description = models.TextField(
- blank=True,
- null=True,
- )
- rating = models.FloatField(
- blank=True,
- null=True,
- validators=[MaxValueValidator(1), MinValueValidator(0)],
- )
- video = models.FileField(
- blank=True,
- null=True,
- max_length=300,
- upload_to=get_video_upload_path,
- )
- marquee = models.ImageField(
- blank=True,
- null=True,
- max_length=300,
- upload_to=get_marquee_upload_path,
- )
- screenshot = models.ImageField(
- blank=True,
- null=True,
- max_length=300,
- upload_to=get_screenshot_upload_path,
- )
- rom_file = models.FileField(
- blank=True,
- null=True,
- max_length=300,
- upload_to=get_rom_upload_path,
- )
- hack = models.BooleanField(
- default=False,
- )
- hack_version = models.CharField(
- max_length=255,
- blank=True,
- null=True,
- )
- english_patched = models.BooleanField(
- default=False,
- )
- english_patched_version = models.CharField(
- max_length=50,
- blank=True,
- null=True,
- )
- undub = models.BooleanField(
- default=False,
- )
- featured = models.BooleanField(
- default=False,
- )
- region = models.CharField(
- max_length=10,
- choices=Region.choices(),
- blank=True,
- null=True,
- )
- source = models.CharField(
- max_length=500,
- blank=True,
- null=True,
- )
- tags = TaggableManager()
- class Meta:
- ordering = ["game_system", "name"]
- def __str__(self):
- return f"{self.name} for {self.game_system}"
- def get_absolute_url(self):
- return reverse("games:game_detail", args=[self.slug])
- @property
- def rating_by_100(self) -> float:
- if self.rating:
- return int(100 * self.rating)
- return int(0)
- @property
- def rating_class(self):
- if self.rating_by_100 > 70:
- return "high"
- if self.rating_by_100 > 50:
- return "medium"
- return "low"
- @property
- def retroarch_core_path(self):
- path = None
- retroarch_core = self.game_system.defaults.get("retroarch_core", None)
- if retroarch_core:
- path = quote(
- os.path.join(
- settings.ROMS_DIR,
- "cores",
- retroarch_core + "_libretro.so",
- )
- )
- return path
- @property
- def webretro_url(self):
- if "webretro_core" in self.game_system.defaults.keys():
- return reverse("games:game_play_detail", args=[self.slug])
- def retroarch_cmd(self, platform="linux"):
- if platform != "linux":
- return ""
- if not self.retroarch_core_path:
- return ""
- if not self.rom_file:
- return ""
- rom_file = quote(self.rom_file.path)
- if not os.path.exists(self.retroarch_core_path):
- logger.info(f"Missing libretro core file at {self.retroarch_core_path}")
- return f"Libretro core not found at {self.retroarch_core_path}"
- return f"retroarch -L {self.retroarch_core_path} {rom_file} -v"
- def cmd(self, platform="linux"):
- cmd = None
- if self.retroarch_cmd(platform):
- return self.retroarch_cmd(platform)
- rom_file = quote(self.rom_file.path)
- emulator = self.game_system.defaults.get("emulator", None)
- if emulator == "PCSX2":
- cmd = f"{emulator} --console --fullscreen --nogui {rom_file}"
- return cmd
- class GameCollection(BaseModel):
- games = models.ManyToManyField(Game)
- game_system = models.ForeignKey(
- GameSystem,
- on_delete=models.SET_NULL,
- blank=True,
- null=True,
- )
- developer = models.ForeignKey(
- Developer,
- on_delete=models.SET_NULL,
- blank=True,
- null=True,
- )
- publisher = models.ForeignKey(
- Publisher,
- on_delete=models.SET_NULL,
- blank=True,
- null=True,
- )
- genre = models.ForeignKey(
- Genre,
- on_delete=models.SET_NULL,
- blank=True,
- null=True,
- )
- def __str__(self):
- return f"{self.name}"
- def get_absolute_url(self):
- return reverse("games:gamecollection_detail", args=[self.slug])
- @property
- def rating_avg(self):
- avg = self.games.aggregate(models.Avg("rating"))["rating__avg"]
- if avg:
- return int(100 * avg)
- return 0
- def export_to_file(self, dryrun=True):
- """Will dump this collection to a .cfg file in /tmp or
- our COLLECTIONS_DIR configured path if dryrun=False"""
- file_path = f"/tmp/custom-{self.slug}.cfg"
- if not dryrun:
- file_path = os.path.join(
- settings.COLLECTIONS_DIR, f"custom-{self.slug}.cfg"
- )
- with open(file_path, "w") as outfile:
- for game in self.games.all():
- outfile.write(game.rom_file.path + "\n")
|