File access refactoring for multiple library directories

This commit is contained in:
Luke Hubmayer-Werner 2020-05-03 00:11:01 +09:30
parent 8facf97440
commit 914b00f50d
5 changed files with 133 additions and 68 deletions

View File

@ -17,6 +17,8 @@ _global_script_class_icons={
config/name="Rhythm" config/name="Rhythm"
run/main_scene="res://main.tscn" run/main_scene="res://main.tscn"
config/use_custom_user_dir=true
config/custom_user_dir_name="RhythmGame"
config/icon="res://assets/spritesheet.png" config/icon="res://assets/spritesheet.png"
[audio] [audio]

View File

@ -1,10 +1,25 @@
#extends Object #extends Object
extends Node 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') # 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/' # '/storage/emulated/0/Android/data/au.ufeff.rhythmgame/'
# '/sdcard/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: func directory_list(directory: String, hidden: bool, sort:=true) -> Dictionary:
# Sadly there's no filelist sugar so we make our own # Sadly there's no filelist sugar so we make our own
@ -79,7 +94,7 @@ func scan_library():
# Our format # Our format
song_defs[key] = FileLoader.load_folder('%s/%s' % [rootdir, key]) song_defs[key] = FileLoader.load_folder('%s/%s' % [rootdir, key])
print('Loaded song directory: %s' % 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: if song_defs[key]['genre'] in genres:
genres[song_defs[key]['genre']].append(key) genres[song_defs[key]['genre']].append(key)
else: else:
@ -90,24 +105,29 @@ func scan_library():
for i in min(len(diffs), len(default_difficulty_keys)): for i in min(len(diffs), len(default_difficulty_keys)):
chart_difficulties[default_difficulty_keys[i]] = diffs[i] chart_difficulties[default_difficulty_keys[i]] = diffs[i]
song_defs[key]['chart_difficulties'] = chart_difficulties song_defs[key]['chart_difficulties'] = chart_difficulties
elif dir.file_exists(key + '/collection.json'): 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 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(): for key in collection.keys():
if key != 'songs': if key != 'songs':
base_dict[key] = collection[key] base_dict[key] = collection[key]
for song_key in collection['songs'].keys(): for song_key in collection['songs'].keys():
var song_dict = collection['songs'][song_key] var song_dict = collection['songs'][song_key]
var song_def = base_dict.duplicate() var song_def = base_dict.duplicate()
song_defs[song_key] = song_def
for key in song_dict.keys(): for key in song_dict.keys():
song_def[key] = song_dict[key] 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: if song_defs[song_key]['genre'] in genres:
genres[song_defs[song_key]['genre']].append(song_key) genres[song_defs[song_key]['genre']].append(song_key)
else: else:
genres[song_defs[song_key]['genre']] = [song_key] genres[song_defs[song_key]['genre']] = [song_key]
else: else:
var files_by_ext = find_by_extensions(directory_list(rootdir + '/' + key, false).files) var files_by_ext = find_by_extensions(directory_list(rootdir + '/' + key, false).files)
if 'sm' in files_by_ext: if 'sm' in files_by_ext:
@ -545,11 +565,12 @@ func load_folder(folder, filename='song'):
result.directory = folder result.directory = folder
return result return result
func load_filelist(filelist: Array): func load_filelist(filelist: Array, directory=''):
var charts = {} var charts = {}
var key := 1 var key := 1
for filename in filelist: for filename in filelist:
var extension: String = filename.rsplit('.', true, 1)[-1] var extension: String = filename.rsplit('.', true, 1)[-1]
filename = directory.rstrip('/') + '/' + filename
match extension: match extension:
'rgtm': # multiple charts 'rgtm': # multiple charts
var res = RGT.load_file(filename) var res = RGT.load_file(filename)
@ -570,7 +591,7 @@ func load_filelist(filelist: Array):
return charts return charts
func load_ogg(filename) -> AudioStreamOGGVorbis: func direct_load_ogg(filename) -> AudioStreamOGGVorbis:
var audiostream = AudioStreamOGGVorbis.new() var audiostream = AudioStreamOGGVorbis.new()
var oggfile = File.new() var oggfile = File.new()
oggfile.open(filename, File.READ) oggfile.open(filename, File.READ)
@ -578,9 +599,74 @@ func load_ogg(filename) -> AudioStreamOGGVorbis:
oggfile.close() oggfile.close()
return audiostream return audiostream
func load_image(filename) -> ImageTexture: var fallback_audiostream = AudioStreamOGGVorbis.new()
var tex = ImageTexture.new() func load_ogg(filename) -> AudioStreamOGGVorbis:
var img = Image.new() 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) img.load(filename)
tex.create_from_image(img) tex.create_from_image(img)
return tex 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

View File

@ -9,9 +9,9 @@ class MultilangStr:
var en := '' setget set_english var en := '' setget set_english
func _init(native='', translit='', english=''): func _init(native='', translit='', english=''):
self.n = native self.n = native
if not translit.empty(): if translit and not translit.empty():
self.tl = translit self.tl = translit
if not english.empty: if english and not english.empty():
self.en = english self.en = english
# func get_native() -> String: # func get_native() -> String:
# return n # return n
@ -39,6 +39,7 @@ class Song:
var bpm_values: Array var bpm_values: Array
var dynamic_bpm: bool var dynamic_bpm: bool
var genre: String var genre: String
var filepath: String # For now this excludes the 'songs/' bit.
var tile_filename: String var tile_filename: String
var audio_filelist: Array var audio_filelist: Array
var video_filelist: Array var video_filelist: Array
@ -64,6 +65,7 @@ class Song:
if 'bpm' in values: if 'bpm' in values:
BPM = values['bpm'] BPM = values['bpm']
filepath = values.get('filepath', '')
tile_filename = values.get('tile_filename', '%s.png'%values.get('index', 'tile')) tile_filename = values.get('tile_filename', '%s.png'%values.get('index', 'tile'))
audio_filelist = values.get('audio_filelist', ['%s.ogg'%values.get('index', 'audio')]) audio_filelist = values.get('audio_filelist', ['%s.ogg'%values.get('index', 'audio')])
video_filelist = values.get('video_filelist', ['%s.webm'%values.get('index', 'video')]) video_filelist = values.get('video_filelist', ['%s.webm'%values.get('index', 'video')])
@ -92,18 +94,29 @@ class Song:
var all_songs = {} var all_songs = {}
var genre_ids = {} var genre_ids = {} # String: int
var genre_titles = [] var genre_titles = [] # Strings
var genre_songs = [] 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 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 = {} 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): func get_song_tile_texture(song_key):
if song_key in tile_tex_cache: if song_key in tile_tex_cache:
return tile_tex_cache[song_key] return tile_tex_cache[song_key]
elif song_key in all_songs: 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] return tile_tex_cache[song_key]
else: else:
print_debug('Invalid song_key: ', song_key) print_debug('Invalid song_key: ', song_key)
@ -112,7 +125,7 @@ func get_song_charts(song_key):
if song_key in charts_cache: if song_key in charts_cache:
return charts_cache[song_key] return charts_cache[song_key]
elif song_key in all_songs: 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] return charts_cache[song_key]
else: else:
print_debug('Invalid song_key: ', song_key) print_debug('Invalid song_key: ', song_key)

View File

@ -37,60 +37,26 @@ var GenreFont := preload('res://assets/MenuGenreFont.tres')
var ScoreFont := preload('res://assets/MenuScoreFont.tres') var ScoreFont := preload('res://assets/MenuScoreFont.tres')
var snd_interact := preload('res://assets/softclap.wav') var snd_interact := preload('res://assets/softclap.wav')
var userroot : String = FileLoader.userroot
func scan_library(): func scan_library():
var results = FileLoader.scan_library() var results = FileLoader.scan_library()
song_defs = results.song_defs song_defs = results.song_defs
song_images = results.song_images song_images = results.song_images
genres = results.genres genres = results.genres
func save_score() -> int:
func save_score(): var data = {'score_data': scorescreen_score_data, 'song_key': scorescreen_song_key}
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.
var dt = scorescreen_datetime 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] var filename = 'scores/%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) var err = FileLoader.save_json(filename, data)
if err != OK: if err == OK:
print(err)
return err
file.store_string(json)
file.close()
scorescreen_saved = true scorescreen_saved = true
return err
func load_score(filename): func load_score(filename):
var rootdir = userroot + 'scores' var result = FileLoader.load_json('scores/%s'%filename)
var dir = Directory.new() if not (result is Dictionary):
var err = dir.make_dir_recursive(rootdir) print('An error occurred while trying to access the chosen score file: ', result)
if err != OK: return result
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 data = {} var data = {}
for key in result.score_data: for key in result.score_data:
var value = {} var value = {}
@ -106,11 +72,8 @@ func load_score(filename):
set_menu_mode(MenuMode.SCORE_SCREEN) set_menu_mode(MenuMode.SCORE_SCREEN)
func _ready(): func _ready():
print('user:// root is: ', OS.get_user_data_dir())
print('Root for songs and scores is: ', userroot)
scan_library() scan_library()
NoteHandler.connect('finished_song', self, 'finished_song') 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. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta): 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 diff_color := GameTheme.COLOR_DIFFICULTY[difficulty*2]
var rect := Rect2(position.x, position.y, size, size) 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_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 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) 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: if title_text:

View File

@ -575,7 +575,7 @@ func load_track(data: Dictionary, difficulty_idx: int):
bpm = data.bpm_values[0] bpm = data.bpm_values[0]
sync_offset_audio = data.audio_offsets[0] sync_offset_audio = data.audio_offsets[0]
sync_offset_video = data.video_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]) var videostream = load(data.directory + '/' + data.video_filelist[0])
MusicPlayer.set_stream(audiostream) MusicPlayer.set_stream(audiostream)