@zthun/romulator-api 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,96 @@
1
- import { IZFileSystemNode } from '@zthun/crumbtrail-fs';
1
+ import { IZFileRepository, IZFileSystemNode, IZFileSystemService } from '@zthun/crumbtrail-fs';
2
2
  import { IZRomulatorConfigsService } from '../config/configs-service.mjs';
3
3
  export declare const ZRomulatorFilesToken: unique symbol;
4
+ /**
5
+ * Represents the service that you can use to
6
+ * scan the games folder for media, info, games, and systems.
7
+ */
4
8
  export interface IZRomulatorFilesService {
9
+ /**
10
+ * Retrieves all media found in the games .media folder.
11
+ *
12
+ * @returns
13
+ * A list of all media found in the game media folder.
14
+ */
5
15
  media(): Promise<IZFileSystemNode[]>;
16
+ /**
17
+ * Retrieves a single media node found in the .media folder.
18
+ *
19
+ * @param path -
20
+ * The path of the media node to retrieve. This will
21
+ * be relative to the configured games directory. If this
22
+ * starts with a root OS folder, then the path by itself
23
+ * is used.
24
+ *
25
+ * @returns
26
+ * The node with the given path or null if no such
27
+ * file exists.
28
+ */
29
+ media(path: string): Promise<IZFileSystemNode | null>;
30
+ /**
31
+ * Retrieves all systems found in the games folder.
32
+ *
33
+ * A system is a root folder that is a slug of a supported
34
+ * system.
35
+ *
36
+ * @returns
37
+ * A list of all systems found in the games folder.
38
+ */
39
+ systems(): Promise<IZFileSystemNode[]>;
40
+ /**
41
+ * Retrieves a single system found in the games folder.
42
+ *
43
+ * @param path -
44
+ * The id of the system, which is also the name of the folder.
45
+ *
46
+ * @returns
47
+ * The node that represents the system slug. Returns null if
48
+ * the folder does not exist or is not supported. Note
49
+ * that the path is relative to the configured games folder. If you
50
+ * want to supply a fully qualified absolute path, then this string
51
+ * should start with the root of an OS drive (not recommended).
52
+ */
53
+ systems(path: string): Promise<IZFileSystemNode | null>;
54
+ /**
55
+ * Retrieves all info found in the games .info folder.
56
+ *
57
+ * Info is the metadata scraped from a scraper service.
58
+ * The data format is stored in json.
59
+ *
60
+ * @returns
61
+ * A list of all info found in the games folder.
62
+ */
63
+ info(): Promise<IZFileSystemNode[]>;
64
+ /**
65
+ * Retrieves specific information found in the games .info folder.
66
+ *
67
+ * @param path -
68
+ * The path of the info node to retrieve.
69
+ *
70
+ * @returns
71
+ * The node with the given path or null if no such file exists.
72
+ */
73
+ info(path: string): Promise<IZFileSystemNode | null>;
6
74
  }
7
75
  export declare class ZRomulatorFilesService implements IZRomulatorFilesService {
8
76
  private readonly _configs;
77
+ private readonly _fileSystem;
9
78
  private static readonly MediaFolderName;
10
79
  private static readonly InfoFolderName;
11
80
  private _repository;
12
- private _folderWriter;
81
+ private _folderStream;
13
82
  private _globs;
14
- constructor(_configs: IZRomulatorConfigsService);
83
+ private _systems;
84
+ constructor(_configs: IZRomulatorConfigsService, _fileSystem: IZFileSystemService);
15
85
  private gamesFolder;
16
86
  private mediaFolder;
17
87
  private infoFolder;
18
- private repository;
88
+ private contents;
89
+ seed(): Promise<IZFileRepository>;
19
90
  media(): Promise<IZFileSystemNode[]>;
91
+ media(path: string): Promise<IZFileSystemNode | null>;
92
+ systems(): Promise<IZFileSystemNode[]>;
93
+ systems(path: string): Promise<IZFileSystemNode | null>;
94
+ info(): Promise<IZFileSystemNode[]>;
95
+ info(path: string): Promise<IZFileSystemNode | null>;
20
96
  }
package/dist/main.cjs CHANGED
@@ -631,19 +631,24 @@ function _ts_param$4(paramIndex, decorator) {
631
631
  const ZRomulatorFilesToken = Symbol("files");
632
632
  class ZRomulatorFilesService {
633
633
  _configs;
634
+ _fileSystem;
634
635
  static MediaFolderName = ".media";
635
636
  static InfoFolderName = ".info";
636
637
  _repository = new crumbtrailFs.ZFileRepository();
637
- _folderWriter = new crumbtrailFs.ZStreamFolder();
638
+ _folderStream = new crumbtrailFs.ZStreamFolder();
638
639
  _globs;
639
- constructor(_configs){
640
+ _systems;
641
+ constructor(_configs, _fileSystem){
640
642
  this._configs = _configs;
643
+ this._fileSystem = _fileSystem;
641
644
  const slugs = Object.values(romulatorClient.ZRomulatorSystemId);
642
645
  this._globs = [
643
646
  ".media/**",
644
647
  ".info/**",
645
648
  ...slugs.map((s)=>`${s}/*.*`)
646
649
  ];
650
+ this._systems = Object.values(romulatorClient.ZRomulatorSystemId);
651
+ this.seed();
647
652
  }
648
653
  async gamesFolder() {
649
654
  const config = await this._configs.get(romulatorClient.ZRomulatorConfigId.Games);
@@ -660,31 +665,55 @@ class ZRomulatorFilesService {
660
665
  const gamesFolder = await this.gamesFolder();
661
666
  return node_path.resolve(gamesFolder, ZRomulatorFilesService.InfoFolderName);
662
667
  }
663
- async repository() {
668
+ async contents(root, path) {
669
+ const repository = await this.seed();
670
+ const prefix = lodashEs.trimEnd(root, "/");
671
+ const folder = `${prefix}${node_path.sep}`;
672
+ let filter = new helpfulQuery.ZFilterBinaryBuilder().subject("path");
673
+ filter = path == null ? filter.startsWith().value(folder) : filter.equal().value(node_path.resolve(folder, path));
674
+ const sort = new helpfulQuery.ZSortBuilder().ascending("path").build();
675
+ const request = new helpfulQuery.ZDataRequestBuilder().filter(filter.build()).sort(sort).build();
676
+ const nodes = await repository.retrieve(request);
677
+ return path == null ? nodes : helpfulFn.firstDefined(null, lodashEs.first(nodes));
678
+ }
679
+ async seed() {
664
680
  const path = await this.gamesFolder();
665
681
  if (this._repository.path !== path) {
666
- await this._folderWriter.write(await this.mediaFolder());
667
- await this._folderWriter.write(await this.infoFolder());
682
+ await this._folderStream.write(await this.mediaFolder());
683
+ await this._folderStream.write(await this.infoFolder());
668
684
  await this._repository.initialize(path, this._globs);
669
685
  }
670
686
  return this._repository;
671
687
  }
672
- async media() {
673
- const repository = await this.repository();
674
- const prefix = lodashEs.trimEnd(await this.mediaFolder(), "/");
675
- const folder = `${prefix}${node_path.sep}`;
676
- const filter = new helpfulQuery.ZFilterBinaryBuilder().subject("path").startsWith().value(folder).build();
677
- const sort = new helpfulQuery.ZSortBuilder().ascending("path").build();
678
- const request = new helpfulQuery.ZDataRequestBuilder().filter(filter).sort(sort).build();
679
- return repository.retrieve(request);
688
+ async media(path) {
689
+ return this.contents(await this.mediaFolder(), path);
690
+ }
691
+ async systems(path) {
692
+ // Systems use directories. There's a maximum limit of about 200 systems.
693
+ // Since we don't actually need to read any metadata or scan through thousands
694
+ // of unknown folders, we can use the supported system ids to just grab the
695
+ // systems that we need. Since the file repository does a stat anyway, we can just
696
+ // grab the systems from the file system and it should be fast enough. These are all
697
+ // folders, so we don't even need the stats for them and we can assume folders.
698
+ const games = await this.gamesFolder();
699
+ const folders = path == null ? this._systems.map((s)=>`${s}/`) : node_path.resolve(games, path);
700
+ const items = await this._fileSystem.search(folders, {
701
+ cwd: games
702
+ });
703
+ return path == null ? items : helpfulFn.firstDefined(null, lodashEs.first(items));
704
+ }
705
+ async info(path) {
706
+ return this.contents(await this.infoFolder(), path);
680
707
  }
681
708
  }
682
709
  ZRomulatorFilesService = _ts_decorate$8([
683
710
  common.Injectable(),
684
711
  _ts_param$4(0, common.Inject(ZRomulatorConfigsToken)),
712
+ _ts_param$4(1, common.Inject(crumbtrailNest.ZFileSystemToken)),
685
713
  _ts_metadata$4("design:type", Function),
686
714
  _ts_metadata$4("design:paramtypes", [
687
- typeof IZRomulatorConfigsService === "undefined" ? Object : IZRomulatorConfigsService
715
+ typeof IZRomulatorConfigsService === "undefined" ? Object : IZRomulatorConfigsService,
716
+ typeof IZFileSystemService === "undefined" ? Object : IZFileSystemService
688
717
  ])
689
718
  ], ZRomulatorFilesService);
690
719
 
@@ -699,6 +728,7 @@ class ZRomulatorFilesModule {
699
728
  ZRomulatorFilesModule = _ts_decorate$7([
700
729
  common.Module({
701
730
  imports: [
731
+ crumbtrailNest.ZFileSystemModule,
702
732
  ZRomulatorConfigsModule
703
733
  ],
704
734
  providers: [
@@ -1005,13 +1035,16 @@ function _ts_param$1(paramIndex, decorator) {
1005
1035
  }
1006
1036
  const ZRomulatorSystemsToken = Symbol("romulator-systems-service");
1007
1037
  class ZRomulatorSystemsService {
1008
- _file;
1009
- _configs;
1038
+ _files;
1010
1039
  logger;
1011
1040
  _logger;
1012
- constructor(_file, _configs, logger){
1013
- this._file = _file;
1014
- this._configs = _configs;
1041
+ _fileStream = new crumbtrailFs.ZStreamFile({
1042
+ cache: {
1043
+ maxFiles: Object.keys(romulatorClient.ZRomulatorSystemId).length + 1
1044
+ }
1045
+ });
1046
+ constructor(_files, logger){
1047
+ this._files = _files;
1015
1048
  this.logger = logger;
1016
1049
  this._logger = new lumberjackyLog.ZLoggerContext("ZRomulatorSystemsService", logger);
1017
1050
  }
@@ -1020,18 +1053,8 @@ class ZRomulatorSystemsService {
1020
1053
  const size = helpfulFn.firstDefined(Infinity, req.size);
1021
1054
  let msg = `Retrieving systems page, ${page}, with size, ${size}.`;
1022
1055
  this._logger.log(new lumberjackyLog.ZLogEntryBuilder().info().message(msg).build());
1023
- const { contents } = await this._configs.get(romulatorClient.ZRomulatorConfigId.Games);
1024
- const { gamesFolder } = contents;
1025
- const { fallback } = romulatorClient.ZRomulatorConfigGamesMetadata.gamesFolder();
1026
- const _folder = helpfulFn.firstDefined(fallback, gamesFolder);
1027
- const cwd = helpfulFn.detokenize(_folder, process.env);
1028
- msg = `Looking for systems in ${cwd}`;
1029
- this._logger.log(new lumberjackyLog.ZLogEntryBuilder().info().message(msg).build());
1030
- const searchOptions = {
1031
- cwd
1032
- };
1033
- const folders = await this._file.search("*/", searchOptions);
1034
- const systems = folders.map((folder)=>folder.path).map((path)=>node_path.basename(path)).map((slug)=>this._createSystemFromSlug(slug)).filter((system)=>system != null);
1056
+ const folders = await this._files.systems();
1057
+ const systems = await Promise.all(folders.map((folder)=>folder.path).map((path)=>node_path.basename(path)).filter((slug)=>romulatorClient.isSystemId(slug)).map((slug)=>this._createSystemFromSlug(slug)));
1035
1058
  msg = `Found ${systems.length} systems`;
1036
1059
  this._logger.log(new lumberjackyLog.ZLogEntryBuilder().info().message(msg).build());
1037
1060
  const sourceOptions = new helpfulQuery.ZDataSourceStaticOptionsBuilder().search(new helpfulQuery.ZDataSearchFields([
@@ -1045,27 +1068,46 @@ class ZRomulatorSystemsService {
1045
1068
  return new helpfulQuery.ZPageBuilder().data(data).count(count).build();
1046
1069
  }
1047
1070
  async get(id) {
1048
- const all = await this.list(new helpfulQuery.ZDataRequestBuilder().build());
1049
- const system = lodashEs.find(all.data, (system)=>system.id === id);
1050
- if (!system) {
1051
- throw new common.NotFoundException(`Unable to find system with id, ${id}.`);
1071
+ // The system path should be the slug itself.
1072
+ if (!romulatorClient.isSystemId(id)) {
1073
+ const message = `The specified system slug, ${id}, is not supported.`;
1074
+ throw new common.NotFoundException(message);
1075
+ }
1076
+ const node = await this._files.systems(id);
1077
+ if (node == null) {
1078
+ const message = `System with slug, ${id}, was not found.`;
1079
+ throw new common.NotFoundException(message);
1052
1080
  }
1053
- return system;
1081
+ return this._createSystemFromSlug(id);
1054
1082
  }
1055
- _createSystemFromSlug(slug) {
1056
- // A romulator system's slug should read the metadata.json file from the root
1057
- return ZRomulatorSystemKnown.from(slug);
1083
+ async _createSystemFromSlug(slug) {
1084
+ const system = new romulatorClient.ZRomulatorSystemBuilder().id(slug);
1085
+ const path = `${slug}/info.json`;
1086
+ const info = await this._files.info(path);
1087
+ if (info == null) {
1088
+ // Best we can do right now.
1089
+ return system.build();
1090
+ }
1091
+ try {
1092
+ const contents = await this._fileStream.read(info.path);
1093
+ const json = JSON.parse(contents.toString());
1094
+ return system.assign(json).redact().build();
1095
+ } catch (e) {
1096
+ // Best we can do
1097
+ const err = helpfulFn.createError(e);
1098
+ const msg = `Cannot read system metadata, ${err.message}`;
1099
+ this._logger.log(new lumberjackyLog.ZLogEntryBuilder().error().message(msg).build());
1100
+ return system.build();
1101
+ }
1058
1102
  }
1059
1103
  }
1060
1104
  ZRomulatorSystemsService = _ts_decorate$3([
1061
1105
  common.Injectable(),
1062
- _ts_param$1(0, common.Inject(crumbtrailNest.ZFileSystemToken)),
1063
- _ts_param$1(1, common.Inject(ZRomulatorConfigsToken)),
1064
- _ts_param$1(2, common.Inject(lumberjackyNest.ZLoggerToken)),
1106
+ _ts_param$1(0, common.Inject(ZRomulatorFilesToken)),
1107
+ _ts_param$1(1, common.Inject(lumberjackyNest.ZLoggerToken)),
1065
1108
  _ts_metadata$1("design:type", Function),
1066
1109
  _ts_metadata$1("design:paramtypes", [
1067
- typeof IZFileSystemService === "undefined" ? Object : IZFileSystemService,
1068
- typeof IZRomulatorConfigsService === "undefined" ? Object : IZRomulatorConfigsService,
1110
+ typeof IZRomulatorFilesService === "undefined" ? Object : IZRomulatorFilesService,
1069
1111
  typeof IZLogger === "undefined" ? Object : IZLogger
1070
1112
  ])
1071
1113
  ], ZRomulatorSystemsService);
@@ -1139,8 +1181,7 @@ class ZRomulatorSystemsModule {
1139
1181
  ZRomulatorSystemsModule = _ts_decorate$1([
1140
1182
  common.Module({
1141
1183
  imports: [
1142
- ZRomulatorConfigsModule,
1143
- crumbtrailNest.ZFileSystemModule,
1184
+ ZRomulatorFilesModule,
1144
1185
  lumberjackyNest.ZLoggerModule
1145
1186
  ],
1146
1187
  controllers: [