Ejemplo n.º 1
0
    def update(self, machine: Machine) -> None:
        # Prioritize (lowest to highest):
        # * Parent Title
        # * Parent Name
        # * Title
        # * Name
        data = {
            **self.data.get(Machine.normalize(machine.title), {}),
            **self.data.get(
                Machine.normalize(Playlist.name_from(machine.name)), {}),
        }
        if machine.parent_name:
            data = {
                **self.data.get(Machine.normalize(machine.parent_title), {}),
                **self.data.get(Machine.normalize(Playlist.name_from(machine.parent_name)), {}),
                **data,
            }

        # Genre
        genre = data.get('genre')
        if genre:
            # Prefer Duckstation genres over scraped genres
            machine.genres.clear()
            machine.genres.add(genre)

        # Language
        language = data.get('language')
        if language:
            machine.languages.add(language)

        # Num. Players (override only if Duckstation thinks more are supported)
        players = data.get('players')
        if players and (not machine.players or machine.players < players):
            machine.players = players
Ejemplo n.º 2
0
    def install_machine(self, machine: Machine) -> bool:
        for attempt in range(INSTALL_MAX_ATTEMPTS):
            try:
                machine.install()
                return True
            except Exception as e:
                logging.error(f'[{machine.name}] Install failed')
                traceback.print_exc()
                time.sleep(INSTALL_RETRY_WAIT_TIME)

        return False
Ejemplo n.º 3
0
    def load(self) -> None:
        self.metadata = {}

        with self.install_path.open() as file:
            data = json.load(file)
            for game in data:
                if 'genre' in game or 'language' in game or 'maxPlayers' in game:
                    name = game['name']
                    title = Machine.title_from(name)
                    playlist_name = Playlist.name_from(name)

                    # Shared: Find metadata shared between titles under different regions
                    shared_metadata = self.data.get(
                        Machine.normalize(title)) or {}
                    players = game.get('maxPlayers')
                    genre = game.get('genre')

                    # Shared: Prioritize most # of players identified
                    if players and (not shared_metadata.get('players')
                                    or shared_metadata['players'] < players):
                        shared_metadata['players'] = players

                    # Shared: Prioritize more specific genres (approximated by length)
                    if genre and (not shared_metadata.get('genre') or
                                  len(shared_metadata['genre']) < len(genre)):
                        genre = self.GENRE_REPLACE_REGEX.sub('', genre)
                        shared_metadata['genre'] = genre

                    self.set_data(title, shared_metadata)

                    if title == playlist_name:
                        # There was no region specified, so we provide a default language if one's there
                        if language:
                            shared_metadata['language'] = language
                    else:
                        # Region-specific metadata
                        game_metadata = {}
                        language = game.get('language')

                        if language:
                            game_metadata['language'] = language

                        self.set_data(playlist_name, game_metadata)
Ejemplo n.º 4
0
    def update(self, machine: Machine) -> None:
        group = self.custom_groups.get(machine.disc_title) or machine.disc_title

        # If the group isn't already tracked, then we know this is the primary parent
        if group not in self.group_parents:
            self.group_parents[group] = machine.name

        # Only attach a parent if it's different from this machine
        if not machine.parent_name and self.group_parents[group] != machine.name:
            machine.parent_name = self.group_parents[group]
Ejemplo n.º 5
0
 def iter_machines(self) -> Generator[None, Machine, None]:
     if self.datlist:
         # Read from an internal dat list
         for machine_attrs in self.datlist:
             machine = Machine.from_dict(self, machine_attrs)
             machine.custom_context = self.system.context_for(machine)
             yield machine
     else:
         # Read from an external dat file
         doc = lxml.etree.iterparse(str(self.dat.target_path.path), tag=('game', 'machine'))
         for event, element in doc:
             if Machine.is_installable(element):
                 machine = Machine.from_xml(self, element)
                 machine.custom_context = self.system.context_for(machine)
                 if self.filter_set.allow(machine):
                     yield machine
             else:
                 logging.debug(f"[{element.get('name')}] Ignored (not installable)")
             
             element.clear()
Ejemplo n.º 6
0
    def load(self) -> None:
        self.custom_groups = {}
        self.group_parents = {}

        with self.install_path.open() as f:
            data = json.loads(f.read())
            for parent_name, clone_disc_titles in data.items():
                parent_disc_title = Machine.title_from(parent_name, disc=True)
                self.group_parents[parent_disc_title] = parent_name
                
                for clone_disc_title in clone_disc_titles:
                    self.custom_groups[clone_disc_title] = parent_disc_title
Ejemplo n.º 7
0
    def find_matching_key(self, machine: Machine, all_keys: Set[str]):
        keys = [
            machine.name, machine.title, machine.parent_name,
            machine.parent_title
        ]
        for key in keys:
            if not key:
                continue

            if key in all_keys:
                return key
            else:
                normalized_key = Machine.normalize(key)
                if normalized_key in all_keys:
                    return normalized_key
Ejemplo n.º 8
0
    def output_metadata(self) -> None:
        gamelist_path = self.tmpdir.joinpath('gamelist.xml')
        if not gamelist_path.exists():
            print('No new games to merge')
            return

        # Merge the metadata from the gamelist with the existing
        # data
        doc = lxml.etree.iterparse(str(gamelist_path), tag=('game'))
        for event, element in doc:
            name = Path(element.find('path').text).stem
            rating = element.find('rating').text
            genres_csv = element.find('genre').text

            # Only add the metadata if:
            # * It succeeded
            # * Configured to override all machines or
            # * Configured to override just the machines scraped
            if name not in self.failed_scrapes and (self.override == OverrideConfig.ALL or name in self.scrapes):
                title = Machine.title_from(name)
                if title not in self.metadata:
                    self.metadata[title] = {'genres': [], 'rating': None}

                data = self.metadata[title]
                if genres_csv:
                    data['genres'] = re.split(', *', genres_csv)

                if rating:
                    data['rating'] = rating

            element.clear()

        # Save it to the configured metadata path
        with open(self.metadata_path, 'w') as file:
            json.dump(self.metadata, file,
                indent=4,
                sort_keys=True,
            )
Ejemplo n.º 9
0
 def set_data(self, key: str, key_data) -> None:
     self.data[key] = key_data
     self.data[Machine.normalize(key)] = key_data
Ejemplo n.º 10
0
 def enable_machine(self, machine: Machine, system_dir: SystemDir) -> None:
     machine.enable(system_dir)
Ejemplo n.º 11
0
    def list(self) -> List[Machine]:
        # Machines guaranteed to be installed
        machines_to_install = set()

        # Machines that are candidates until we've gone through all of them
        machine_candidates = {}

        # Normalized title => groupd id
        machine_groups = {}

        for romset in self.iter_romsets():
            # Machines that are installable or required by installable machines
            machines_to_track = set()

            for machine in romset.iter_machines():
                # Set external emulator metadata
                self.metadata_set.update(machine)

                # Set whether the machine is a favorite
                machine.favorite = self.favorites_set.allow(
                    machine) == FilterReason.ALLOW

                allow_reason = self.filter_set.allow(machine)
                if allow_reason:
                    # Track this machine and all machines it depends on
                    machines_to_track.update(machine.dependent_machine_names)
                    machine.track()

                    # Group the machine based on its parent/self title (w/ disc).
                    # We don't want to rely on the group name because (a) we don't always
                    # have a Parent/Clone relationship map and (b) the flags are
                    # less stable.
                    normalized_disc_title = Machine.normalize(
                        machine.disc_title)
                    normalized_parent_disc_title = Machine.normalize(
                        machine.parent_disc_title)
                    group = (
                        normalized_parent_disc_title
                        and machine_groups.get(normalized_parent_disc_title)
                    ) or machine_groups.get(
                        normalized_disc_title
                    ) or normalized_parent_disc_title or normalized_disc_title
                    machine_groups[normalized_disc_title] = group
                    if normalized_parent_disc_title:
                        machine_groups[normalized_parent_disc_title] = group

                    # Force the machine to be installed if it was allowed by an override
                    if allow_reason == FilterReason.OVERRIDE:
                        machines_to_install.add(machine)

                        # Avoid installing anything else in the group that was a candidate
                        machine_candidates[group] = machine

                    # If a priority is defined, the user is asking for a 1G1R setup.
                    # In that case, we either choose a machine that was explicitly overridden
                    # for install or we choose the highest priority machine in the group.
                    if self.machine_priority.enabled:
                        existing = machine_candidates.get(group)

                        if not existing:
                            # First time we've seen this group: make the machine the default
                            machine_candidates[group] = machine
                        elif existing not in machines_to_install:
                            # Decide which of the two machines to install based on the
                            # predefined priority order
                            prioritized_machines = self.machine_priority.sort(
                                [existing, machine])
                            machine_candidates[group] = prioritized_machines[0]
                            logging.debug(
                                f'[{prioritized_machines[1].name}] Skip (PriorityFilter)'
                            )
                    else:
                        # No priority defined: Add all machines
                        machines_to_install.add(machine)
                elif not machine.is_clone:
                    # We track all parent/bios machines in case they're needed as a dependency
                    # in future machines.  We'll confirm later on with `machines_to_track`.
                    machine.track()

            # Free memory by removing machines we didn't need to keep
            for name in romset.machine_names:
                if name not in machines_to_track:
                    romset.remove(name)

        # Add all the candidates now that we've gone through all the machines
        machines_to_install.update(machine_candidates.values())

        # Sort by name
        return sorted(machines_to_install, key=lambda machine: machine.name)