class_name UploadFolder extends RefCounted ## A folder picked or dropped to upload as a new soundtrack: its MP3s, ready to send, and ## the other song files, which are left out because the server only takes MP3. ## ## The server wants one folder of songs, so songs in subfolders, such as disc folders, ## are taken too, with the disc's number in front of their names: "Disc 1/01 Title.mp3" ## is sent as "1-01 Title.mp3". With only one disc folder, its songs keep their names. ## Hidden files and folders are left out, and names get rid of the characters the server ## refuses. ## The only kind of song the server takes. const UPLOAD_EXTENSION: String = "mp3" ## Other song files, listed as left out so the user knows. const OTHER_SONG_EXTENSIONS: Array[String] = [ "ogg", "wav", "flac", "m4a", "aac", "opus", "wma", "ape", "aif", "aiff", ] ## Characters the server refuses in soundtrack and file names. const INVALID_NAME_CHARACTERS: String = "/\\:*?\"<>|" ## The folder. var path: String = "" ## The soundtrack's name to suggest: the folder's name. var suggested_name: String = "" ## The songs to upload, in order: [code]{"path": String, "name": String, "size": int}[/code], ## with the name to send each one as. var songs: Array[Dictionary] = [] ## The song files left out, as paths in the folder, e.g. [code]"Disc 2/03 Song.flac"[/code]. var skipped: Array[String] = [] ## The size of the songs to upload, in bytes. var total_size: int = 0 ## Reads the folder at [param folder]. static func scan(folder: String) -> UploadFolder: var result: UploadFolder = UploadFolder.new() result.path = folder.trim_suffix("/").trim_suffix("\\") result.suggested_name = clean_name(result.path.get_file()) result._add_files(result.path, "", "") var discs: Array[String] = [] for disc: String in _visible(DirAccess.get_directories_at(result.path)): if _has_songs(result.path.path_join(disc)): discs.append(disc) for disc: int in discs.size(): result._add_folder(result.path.path_join(discs[disc]), discs[disc], "%d-" % (disc + 1) if discs.size() > 1 else "") return result ## [param name] without the characters the server refuses, a leading dot, or a trailing ## dot or space. static func clean_name(name: String) -> String: var cleaned: String = "" for character: String in name: var control: bool = character.unicode_at(0) < 32 || character.unicode_at(0) == 127 cleaned += "-" if INVALID_NAME_CHARACTERS.contains(character) || control \ else character cleaned = cleaned.strip_edges().lstrip(".") while cleaned.ends_with(".") || cleaned.ends_with(" "): cleaned = cleaned.left(-1) return cleaned ## Whether there is anything to upload. func has_songs() -> bool: return !songs.is_empty() # Adds the files of [param folder] and of the folders in it, all with [param prefix] func _add_folder(folder: String, relative: String, prefix: String) -> void: _add_files(folder, relative, prefix) for inner: String in _visible(DirAccess.get_directories_at(folder)): _add_folder(folder.path_join(inner), relative.path_join(inner), prefix) func _add_files(folder: String, relative: String, prefix: String) -> void: for file_name: String in _visible(DirAccess.get_files_at(folder)): var extension: String = file_name.get_extension().to_lower() if extension == UPLOAD_EXTENSION: var file_path: String = folder.path_join(file_name) var size: int = FileAccess.get_size(file_path) songs.append({"path": file_path, "name": _free_name(prefix + clean_name(file_name)), "size": size}) total_size += size elif extension in OTHER_SONG_EXTENSIONS: skipped.append(relative.path_join(file_name) if !relative.is_empty() else file_name) # [param name], or with " (2)" and so on before its extension when a song already has it func _free_name(name: String) -> String: var taken: Array = songs.map(func(song: Dictionary) -> String: return song.name.to_lower()) var candidate: String = name var number: int = 2 while taken.has(candidate.to_lower()): candidate = "%s (%d).%s" % [name.get_basename(), number, name.get_extension()] number += 1 return candidate # Whether [param folder] or a folder in it has a song, to upload or to list as left out static func _has_songs(folder: String) -> bool: for file_name: String in _visible(DirAccess.get_files_at(folder)): var extension: String = file_name.get_extension().to_lower() if extension == UPLOAD_EXTENSION || extension in OTHER_SONG_EXTENSIONS: return true for inner: String in _visible(DirAccess.get_directories_at(folder)): if _has_songs(folder.path_join(inner)): return true return false # The names that are not hidden, in natural order, so "2" comes before "10" static func _visible(names: PackedStringArray) -> Array[String]: var shown: Array[String] = [] for name: String in names: if !name.begins_with("."): shown.append(name) shown.sort_custom(func(a: String, b: String) -> bool: return a.naturalnocasecmp_to(b) < 0) return shown