11import { OsuFile } from "./OsuFile" ;
2+ import { BeatmapSet } from "./lazer.types" ;
23import { access } from "@main/lib/fs-promises" ;
34import { fail , ok } from "@shared/lib/rust-types/Result" ;
45import { assertNever } from "@shared/lib/tungsten/assertNever" ;
@@ -7,6 +8,7 @@ import fs from "graceful-fs";
78import os from "os" ;
89import path from "path/posix" ;
910import readline from "readline" ;
11+ import Realm from "realm" ;
1012
1113const bgFileNameRegex = / .* " (?< ! V i d e o .* ) ( .* ) " .* / ;
1214const beatmapSetIDRegex = / ( [ 0 - 9 ] + ) .* / ;
@@ -32,9 +34,6 @@ export type DirParseResult = Promise<
3234 Result < [ Table < Song > , Table < AudioSource > , Table < ImageSource > ] , string >
3335> ;
3436
35- // Overriding Buffer prototype because I'm lazy.
36- // Should probably get moved to another file, or make a wrapper instead.
37-
3837class BufferReader {
3938 buffer : Buffer ;
4039 pos : number ;
@@ -113,7 +112,129 @@ class BufferReader {
113112}
114113
115114export class OsuParser {
116- static async parseDatabase (
115+ static async parseLazerDatabase (
116+ databasePath : string ,
117+ update ?: ( i : number , total : number , file : string ) => any ,
118+ ) : DirParseResult {
119+ const currentDir = databasePath . replaceAll ( "\\" , "/" ) ;
120+
121+ const sourceRealm = path . join ( currentDir , "client.realm" ) ;
122+ const destinationRealm = path . join ( currentDir , "radio_client.realm" ) ;
123+
124+ // clone the realm file so it can be upgraded if needed by the realm sdk
125+ // without bricking the user's lazer installation
126+ fs . copyFileSync ( sourceRealm , destinationRealm ) ;
127+
128+ const realm = await Realm . open ( {
129+ path : currentDir + "/radio_client.realm" ,
130+ } ) ;
131+ const beatmapSets = realm . objects < BeatmapSet > ( "BeatmapSet" ) ;
132+
133+ const songTable = new Map < ResourceID , Song > ( ) ;
134+ const audioTable = new Map < ResourceID , AudioSource > ( ) ;
135+ const imageTable = new Map < ResourceID , ImageSource > ( ) ;
136+
137+ let i = 0 ;
138+ for ( const beatmapSet of beatmapSets ) {
139+ try {
140+ const beatmaps = beatmapSet . Beatmaps ;
141+
142+ for ( const beatmap of beatmaps ) {
143+ try {
144+ const song : Song = {
145+ lazer : true ,
146+ audio : "" ,
147+ osuFile : "" ,
148+ path : "" ,
149+ ctime : "" ,
150+ dateAdded : beatmapSet . DateAdded ,
151+ title : beatmap . Metadata . Title ,
152+ artist : beatmap . Metadata . Artist ,
153+ creator : beatmap . Metadata . Author ?. Username ?? "No Creator" ,
154+ bpm : [ [ beatmap . BPM ] ] ,
155+ duration : beatmap . Length ,
156+ diffs : [ beatmap . DifficultyName ?? "Unknown difficulty" ] ,
157+ } ;
158+
159+ song . osuFile = path . join (
160+ currentDir ,
161+ "files" ,
162+ beatmap . Hash [ 0 ] ,
163+ beatmap . Hash . substring ( 0 , 2 ) ,
164+ beatmap . Hash ,
165+ ) ;
166+
167+ const songHash = beatmapSet . Files . find (
168+ ( file ) => file . Filename . toLowerCase ( ) === beatmap . Metadata . AudioFile . toLowerCase ( ) ,
169+ ) ?. File . Hash ;
170+
171+ if ( songHash ) {
172+ song . audio = path . join (
173+ currentDir ,
174+ "files" ,
175+ songHash [ 0 ] ,
176+ songHash . substring ( 0 , 2 ) ,
177+ songHash ,
178+ ) ;
179+ }
180+
181+ const existingSong = songTable . get ( song . audio ) ;
182+ if ( existingSong ) {
183+ existingSong . diffs . push ( song . diffs [ 0 ] ) ;
184+ continue ;
185+ }
186+
187+ /* Note: in lots of places throughout the application, it relies on the song.path parameter, which in the
188+ stable parser is the path of the folder that holds all the files. This folder doesn't exist in lazer's
189+ file structure, so for now I'm just passing the audio location as the path parameter. In initial testing
190+ this doesn't seem to break anything but just leaving this note in case it does */
191+ song . path = song . audio ;
192+
193+ const bgHash = beatmapSet . Files . find (
194+ ( file ) => file . Filename === beatmap . Metadata . BackgroundFile ,
195+ ) ?. File . Hash ;
196+
197+ if ( bgHash ) {
198+ song . bg = path . join ( currentDir , "files" , bgHash [ 0 ] , bgHash . substring ( 0 , 2 ) , bgHash ) ;
199+ }
200+
201+ song . beatmapSetID = beatmapSet . OnlineID ;
202+
203+ songTable . set ( song . audio , song ) ;
204+ audioTable . set ( song . audio , {
205+ songID : song . audio ,
206+ path : song . audio ,
207+ ctime : String ( beatmapSet . DateAdded ) ,
208+ } ) ;
209+
210+ if ( update ) {
211+ update ( i + 1 , beatmapSets . length , song . title ) ;
212+ i ++ ;
213+ }
214+ } catch ( err ) {
215+ console . error ( "Error while parsing beatmap: " , err ) ;
216+ }
217+ }
218+ } catch ( err ) {
219+ console . error ( "Error while parsing beatmapset: " , err ) ;
220+ }
221+ }
222+
223+ // Done with the file now
224+ realm . close ( ) ;
225+
226+ // Delete the cloned lazer realm file(s)
227+ fs . unlinkSync ( path . join ( currentDir , "radio_client.realm" ) ) ;
228+ fs . unlinkSync ( path . join ( currentDir , "radio_client.realm.lock" ) ) ;
229+ fs . rmSync ( path . join ( currentDir , "radio_client.realm.management" ) , {
230+ recursive : true ,
231+ force : true ,
232+ } ) ;
233+
234+ return ok ( [ songTable , audioTable , imageTable ] ) ;
235+ }
236+
237+ static async parseStableDatabase (
117238 databasePath : string ,
118239 update ?: ( i : number , total : number , file : string ) => any ,
119240 ) : DirParseResult {
@@ -196,6 +317,7 @@ export class OsuParser {
196317 }
197318
198319 const song : Song = {
320+ lazer : false ,
199321 audio : "" ,
200322 osuFile : "" ,
201323 path : "" ,
@@ -303,11 +425,11 @@ export class OsuParser {
303425 db . readInt ( ) ; // last edit time
304426 db . readByte ( ) ; // mania scroll speed
305427
306- const audioFilePath = songsFolderPath + "/" + folder + "/" + audio_filename ;
307- const osuFilePath = songsFolderPath + "/" + folder + "/" + osu_filename ;
428+ const audioFilePath = path . join ( songsFolderPath , folder , audio_filename ) ;
429+ const osuFilePath = path . join ( songsFolderPath , folder , osu_filename ) ;
308430 song . osuFile = osuFilePath ;
309431 song . audio = audioFilePath ;
310- song . path = songsFolderPath + "/" + folder ;
432+ song . path = path . join ( songsFolderPath , folder ) ;
311433
312434 // Check if the song has already been processed, and add the diff name to the existing song if so
313435 const existingSong = songTable . get ( audioFilePath ) ;
@@ -323,7 +445,9 @@ export class OsuParser {
323445 }
324446
325447 const bgSrc = osuFile . value . props . get ( "bgSrc" ) ;
326- song . bg = songsFolderPath + "/" + folder + "/" + bgSrc ;
448+ if ( bgSrc ) {
449+ song . bg = path . join ( songsFolderPath , folder , bgSrc ) ;
450+ }
327451
328452 if ( song . audio != last_audio_filepath ) {
329453 songTable . set ( song . audio , song ) ;
0 commit comments