From 914b00f50da939c7b83a394dab0d843f5151e6ed Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Sun, 3 May 2020 00:11:01 +0930 Subject: [PATCH] File access refactoring for multiple library directories --- project.godot | 2 + scripts/FileLoader.gd | 108 ++++++++++++++++++++++++++++++++++++----- scripts/Library.gd | 27 ++++++++--- scripts/Menu.gd | 62 +++++------------------ scripts/NoteHandler.gd | 2 +- 5 files changed, 133 insertions(+), 68 deletions(-) diff --git a/project.godot b/project.godot index c07f30d..5522a41 100644 --- a/project.godot +++ b/project.godot @@ -17,6 +17,8 @@ _global_script_class_icons={ config/name="Rhythm" run/main_scene="res://main.tscn" +config/use_custom_user_dir=true +config/custom_user_dir_name="RhythmGame" config/icon="res://assets/spritesheet.png" [audio] diff --git a/scripts/FileLoader.gd b/scripts/FileLoader.gd index 29b99c6..9c86135 100644 --- a/scripts/FileLoader.gd +++ b/scripts/FileLoader.gd @@ -1,10 +1,25 @@ #extends Object extends Node -var userroot := 'user://' if OS.get_name() != 'Android' else '/storage/emulated/0/RhythmGame/' +const ERROR_CODES := [ + 'OK', 'FAILED', 'ERR_UNAVAILABLE', 'ERR_UNCONFIGURED', 'ERR_UNAUTHORIZED', 'ERR_PARAMETER_RANGE_ERROR', + 'ERR_OUT_OF_MEMORY', 'ERR_FILE_NOT_FOUND', 'ERR_FILE_BAD_DRIVE', 'ERR_FILE_BAD_PATH','ERR_FILE_NO_PERMISSION', + 'ERR_FILE_ALREADY_IN_USE', 'ERR_FILE_CANT_OPEN', 'ERR_FILE_CANT_WRITE', 'ERR_FILE_CANT_READ', 'ERR_FILE_UNRECOGNIZED', + 'ERR_FILE_CORRUPT', 'ERR_FILE_MISSING_DEPENDENCIES', 'ERR_FILE_EOF', 'ERR_CANT_OPEN', 'ERR_CANT_CREATE', 'ERR_QUERY_FAILED', + 'ERR_ALREADY_IN_USE', 'ERR_LOCKED', 'ERR_TIMEOUT', 'ERR_CANT_CONNECT', 'ERR_CANT_RESOLVE', 'ERR_CONNECTION_ERROR', + 'ERR_CANT_ACQUIRE_RESOURCE', 'ERR_CANT_FORK', 'ERR_INVALID_DATA', 'ERR_INVALID_PARAMETER', 'ERR_ALREADY_EXISTS', + 'ERR_DOES_NOT_EXIST', 'ERR_DATABASE_CANT_READ', 'ERR_DATABASE_CANT_WRITE', 'ERR_COMPILATION_FAILED', 'ERR_METHOD_NOT_FOUND', + 'ERR_LINK_FAILED', 'ERR_SCRIPT_FAILED', 'ERR_CYCLIC_LINK', 'ERR_INVALID_DECLARATION', 'ERR_DUPLICATE_SYMBOL', + 'ERR_PARSE_ERROR', 'ERR_BUSY', 'ERR_SKIP', 'ERR_HELP', 'ERR_BUG' +] + +var userroot := OS.get_user_data_dir().rstrip('/')+'/' if OS.get_name() != 'Android' else '/storage/emulated/0/RhythmGame/' +var PATHS := [userroot] # The following would probably work. One huge caveat is that permission needs to be manually granted by the user in app settings as we can't use OS.request_permission('WRITE_EXTERNAL_STORAGE') # '/storage/emulated/0/Android/data/au.ufeff.rhythmgame/' # '/sdcard/Android/data/au.ufeff.rhythmgame/' +func _ready() -> void: + print('Library paths: ', PATHS) func directory_list(directory: String, hidden: bool, sort:=true) -> Dictionary: # Sadly there's no filelist sugar so we make our own @@ -79,7 +94,7 @@ func scan_library(): # Our format song_defs[key] = FileLoader.load_folder('%s/%s' % [rootdir, key]) print('Loaded song directory: %s' % key) - song_images[key] = FileLoader.load_image('%s/%s/%s' % [rootdir, key, song_defs[key]['tile_filename']]) +# song_images[key] = FileLoader.load_image('%s/%s' % [key, song_defs[key]['tile_filename']]) if song_defs[key]['genre'] in genres: genres[song_defs[key]['genre']].append(key) else: @@ -90,24 +105,29 @@ func scan_library(): for i in min(len(diffs), len(default_difficulty_keys)): chart_difficulties[default_difficulty_keys[i]] = diffs[i] song_defs[key]['chart_difficulties'] = chart_difficulties + elif dir.file_exists(key + '/collection.json'): - var collection = FileLoader.load_folder('%s/%s' % [rootdir, key], 'collection') + var dir_collection = '%s/%s' % [rootdir, key] + var collection = FileLoader.load_folder(dir_collection, 'collection') collections[key] = collection - var base_dict = {} # Top level of the collection dict contains defaults for every song in it + var base_dict = {'filepath': key+'/'} # Top level of the collection dict contains defaults for every song in it for key in collection.keys(): if key != 'songs': base_dict[key] = collection[key] for song_key in collection['songs'].keys(): var song_dict = collection['songs'][song_key] var song_def = base_dict.duplicate() - song_defs[song_key] = song_def for key in song_dict.keys(): song_def[key] = song_dict[key] - song_images[song_key] = FileLoader.load_image('%s/%s/%s.png' % [rootdir, key, song_key]) + Library.add_song(song_key, song_def) + # Legacy compat stuff + song_defs[song_key] = song_def +# song_images[song_key] = FileLoader.load_image('%s/%s/%s.png' % [rootdir, key, song_key]) if song_defs[song_key]['genre'] in genres: genres[song_defs[song_key]['genre']].append(song_key) else: genres[song_defs[song_key]['genre']] = [song_key] + else: var files_by_ext = find_by_extensions(directory_list(rootdir + '/' + key, false).files) if 'sm' in files_by_ext: @@ -545,11 +565,12 @@ func load_folder(folder, filename='song'): result.directory = folder return result -func load_filelist(filelist: Array): +func load_filelist(filelist: Array, directory=''): var charts = {} var key := 1 for filename in filelist: var extension: String = filename.rsplit('.', true, 1)[-1] + filename = directory.rstrip('/') + '/' + filename match extension: 'rgtm': # multiple charts var res = RGT.load_file(filename) @@ -570,7 +591,7 @@ func load_filelist(filelist: Array): return charts -func load_ogg(filename) -> AudioStreamOGGVorbis: +func direct_load_ogg(filename) -> AudioStreamOGGVorbis: var audiostream = AudioStreamOGGVorbis.new() var oggfile = File.new() oggfile.open(filename, File.READ) @@ -578,9 +599,74 @@ func load_ogg(filename) -> AudioStreamOGGVorbis: oggfile.close() return audiostream -func load_image(filename) -> ImageTexture: - var tex = ImageTexture.new() - var img = Image.new() +var fallback_audiostream = AudioStreamOGGVorbis.new() +func load_ogg(filename) -> AudioStreamOGGVorbis: + var file = File.new() + for root in PATHS: + var filename1 = root + filename + if file.file_exists(filename1): + return direct_load_ogg(filename1) + return fallback_audiostream + +func direct_load_image(filename) -> ImageTexture: + var tex := ImageTexture.new() + var img := Image.new() img.load(filename) tex.create_from_image(img) return tex + +var fallback_texture := ImageTexture.new() +func load_image(filename) -> ImageTexture: + var file = File.new() + for root in PATHS: + var filename1 = root + filename + if file.file_exists(filename1): + return direct_load_image(filename1) + print('File not found: ', filename) + return fallback_texture + + +func init_directory(directory: String): + var dir = Directory.new() + var err = dir.make_dir_recursive(directory) + if err != OK: + print('An error occurred while trying to create the scores directory: ', err, ERROR_CODES[err]) + return err + +func save_json(filename: String, data: Dictionary): + filename = userroot + filename + var dir = filename.rsplit('/', true, 1)[0] + var err = FileLoader.init_directory(dir) + if err != OK: + print('Error making directory for JSON file: ', err, ERROR_CODES[err]) + return err + var json = JSON.print(data) + var file = File.new() + err = file.open(filename, File.WRITE) + if err != OK: + print('Error saving JSON file: ', err, ERROR_CODES[err]) + return err + file.store_string(json) + file.close() + return OK + +func load_json(filename: String): + var file = File.new() + var err + for root in PATHS: + var filename1 = root + filename + if file.file_exists(filename1): + err = file.open(filename1, File.READ) + if err != OK: + print('An error occurred while trying to open file: ', filename1, err, ERROR_CODES[err]) + continue # return err + var result_json = JSON.parse(file.get_as_text()) + file.close() + if result_json.error != OK: + print('Error: ', result_json.error) + print('Error Line: ', result_json.error_line) + print('Error String: ', result_json.error_string) + return result_json.error + return result_json.result + print('File not found in any libraries: ', filename) + return ERR_FILE_NOT_FOUND diff --git a/scripts/Library.gd b/scripts/Library.gd index a5fdf0e..a8dd034 100644 --- a/scripts/Library.gd +++ b/scripts/Library.gd @@ -9,9 +9,9 @@ class MultilangStr: var en := '' setget set_english func _init(native='', translit='', english=''): self.n = native - if not translit.empty(): + if translit and not translit.empty(): self.tl = translit - if not english.empty: + if english and not english.empty(): self.en = english # func get_native() -> String: # return n @@ -39,6 +39,7 @@ class Song: var bpm_values: Array var dynamic_bpm: bool var genre: String + var filepath: String # For now this excludes the 'songs/' bit. var tile_filename: String var audio_filelist: Array var video_filelist: Array @@ -64,6 +65,7 @@ class Song: if 'bpm' in values: BPM = values['bpm'] + filepath = values.get('filepath', '') tile_filename = values.get('tile_filename', '%s.png'%values.get('index', 'tile')) audio_filelist = values.get('audio_filelist', ['%s.ogg'%values.get('index', 'audio')]) video_filelist = values.get('video_filelist', ['%s.webm'%values.get('index', 'video')]) @@ -92,18 +94,29 @@ class Song: var all_songs = {} -var genre_ids = {} -var genre_titles = [] -var genre_songs = [] +var genre_ids = {} # String: int +var genre_titles = [] # Strings +var genre_songs = [] # Dictionaries of key: Song var tile_tex_cache = {} # We'll need some way of managing this later since holding all the tiles in memory might be expensive var charts_cache = {} +func add_song(key: String, data: Dictionary): + if not data.has('index'): + data['index'] = key + var song = Song.new(data) + all_songs[key] = song + if not genre_ids.has(song.genre): + genre_ids[song.genre] = len(genre_titles) + genre_titles.append(song.genre) + genre_songs.append({}) + genre_songs[genre_ids[song.genre]][key] = song + func get_song_tile_texture(song_key): if song_key in tile_tex_cache: return tile_tex_cache[song_key] elif song_key in all_songs: - tile_tex_cache[song_key] = load(all_songs[song_key].tile_filename) + tile_tex_cache[song_key] = FileLoader.load_image('songs/' + all_songs[song_key].filepath.rstrip('/') + '/' + all_songs[song_key].tile_filename) return tile_tex_cache[song_key] else: print_debug('Invalid song_key: ', song_key) @@ -112,7 +125,7 @@ func get_song_charts(song_key): if song_key in charts_cache: return charts_cache[song_key] elif song_key in all_songs: - charts_cache[song_key] = FileLoader.load_filelist(all_songs[song_key].chart_filelist) + charts_cache[song_key] = FileLoader.load_filelist(all_songs[song_key].chart_filelist, all_songs[song_key].filepath) return charts_cache[song_key] else: print_debug('Invalid song_key: ', song_key) diff --git a/scripts/Menu.gd b/scripts/Menu.gd index a2e3c6e..64f4417 100644 --- a/scripts/Menu.gd +++ b/scripts/Menu.gd @@ -37,60 +37,26 @@ var GenreFont := preload('res://assets/MenuGenreFont.tres') var ScoreFont := preload('res://assets/MenuScoreFont.tres') var snd_interact := preload('res://assets/softclap.wav') -var userroot : String = FileLoader.userroot - func scan_library(): var results = FileLoader.scan_library() song_defs = results.song_defs song_images = results.song_images genres = results.genres - -func save_score(): - var rootdir = userroot + 'scores' - var dir = Directory.new() - var err = dir.make_dir_recursive(rootdir) - if err != OK: - print('An error occurred while trying to create the scores directory: ', err) - return err - var data = {} - data.score_data = scorescreen_score_data - data.song_key = scorescreen_song_key - var json = JSON.print(data) - var file = File.new() -# var filename = rootdir + '/{year}{month}{day}T{hour}{minute}{second}.json'.format(scorescreen_datetime) - # So uh. Can't zero-pad using the string.format() method. This sucks. +func save_score() -> int: + var data = {'score_data': scorescreen_score_data, 'song_key': scorescreen_song_key} var dt = scorescreen_datetime - var filename = rootdir + '/%04d%02d%02dT%02d%02d%02d.json'%[dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second] - err = file.open(filename, File.WRITE) - if err != OK: - print(err) - return err - file.store_string(json) - file.close() - scorescreen_saved = true + var filename = 'scores/%04d%02d%02dT%02d%02d%02d.json'%[dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second] + var err = FileLoader.save_json(filename, data) + if err == OK: + scorescreen_saved = true + return err func load_score(filename): - var rootdir = userroot + 'scores' - var dir = Directory.new() - var err = dir.make_dir_recursive(rootdir) - if err != OK: - print('An error occurred while trying to create the scores directory: ', err) - return err - - var file = File.new() - err = file.open(rootdir + '/' + filename, File.READ) - if err != OK: - print('An error occurred while trying to access the chosen score file: ', err) - return err - var result_json = JSON.parse(file.get_as_text()) - file.close() - if result_json.error != OK: - print('Error: ', result_json.error) - print('Error Line: ', result_json.error_line) - print('Error String: ', result_json.error_string) - return result_json.error - var result = result_json.result + var result = FileLoader.load_json('scores/%s'%filename) + if not (result is Dictionary): + print('An error occurred while trying to access the chosen score file: ', result) + return result var data = {} for key in result.score_data: var value = {} @@ -106,11 +72,8 @@ func load_score(filename): set_menu_mode(MenuMode.SCORE_SCREEN) func _ready(): - print('user:// root is: ', OS.get_user_data_dir()) - print('Root for songs and scores is: ', userroot) scan_library() NoteHandler.connect('finished_song', self, 'finished_song') -# load_score('20191211T234131.json') # For testing purposes # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta): @@ -141,7 +104,8 @@ func draw_songtile(song_key, position, size, title_text:=false, difficulty=selec var diff_color := GameTheme.COLOR_DIFFICULTY[difficulty*2] var rect := Rect2(position.x, position.y, size, size) draw_rect(Rect2(position.x - outline_px, position.y - outline_px, size + outline_px*2, size + outline_px*2), diff_color) - draw_texture_rect(song_images[song_key], rect, false) +# draw_texture_rect(song_images[song_key], rect, false) + draw_texture_rect(Library.get_song_tile_texture(song_key), rect, false) # Draw track difficulty rating draw_string_centered(GenreFont, Vector2(position.x+size-24, position.y+size-56), song_defs[song_key]['chart_difficulties'].get(Library.Song.default_difficulty_keys[difficulty], 0), diff_color) if title_text: diff --git a/scripts/NoteHandler.gd b/scripts/NoteHandler.gd index f8e94b4..d0a5c71 100644 --- a/scripts/NoteHandler.gd +++ b/scripts/NoteHandler.gd @@ -575,7 +575,7 @@ func load_track(data: Dictionary, difficulty_idx: int): bpm = data.bpm_values[0] sync_offset_audio = data.audio_offsets[0] sync_offset_video = data.video_offsets[0] - var audiostream = FileLoader.load_ogg(data.directory + '/' + data.audio_filelist[0]) + var audiostream = FileLoader.direct_load_ogg(data.directory + '/' + data.audio_filelist[0]) var videostream = load(data.directory + '/' + data.video_filelist[0]) MusicPlayer.set_stream(audiostream)