class_name UploadPopup extends BasePopup ## Adds a new soundtrack to the server from a folder: picked with Add soundtrack… in the ## statistics, or dropped on the window. ## ## Lists the folder's songs (see [UploadFolder]), lets the name be changed, and uploads ## the songs one at a time with a progress bar. A song that fails is sent again a couple ## of times before the upload is cancelled. The server then adds the soundtrack at once, ## without a sync. ## Emitted when the server has added the soundtrack. signal soundtrack_added(soundtrack: SoundtrackWithSongList) ## How many times a song that failed is sent again before the upload is given up. const RETRIES: int = 2 ## The folder shown. var folder: UploadFolder # The upload on the server, while there is one var _upload_id: String = "" # The song being sent, and how often it failed var _next: int = 0 var _failures: int = 0 var _uploading: bool = false @onready var name_field: LineEdit = %NameField @onready var choose_folder_button: Button = %ChooseFolderButton @onready var folder_label: Label = %FolderLabel @onready var song_list: VBoxContainer = %SongList @onready var summary_label: Label = %SummaryLabel @onready var upload_progress: ProgressBar = %UploadProgress @onready var status_label: Label = %StatusLabel @onready var cancel_button: Button = %CancelButton @onready var upload_button: Button = %UploadButton @onready var folder_dialog: FileDialog = %FolderDialog func _ready() -> void: choose_folder_button.pressed.connect(folder_dialog.popup_centered) folder_dialog.dir_selected.connect(show_folder) cancel_button.pressed.connect(close) upload_button.pressed.connect(upload) name_field.text_changed.connect(func(_text: String) -> void: _update_buttons()) name_field.text_submitted.connect(func(_text: String) -> void: upload()) ## Asks for a folder, then shows it. func choose_folder() -> void: folder_dialog.popup_centered() ## Lists the songs in the folder at [param path] and opens the popup. func show_folder(path: String) -> void: folder = UploadFolder.scan(path) name_field.text = folder.suggested_name folder_label.text = "From " + short_path(folder.path) folder_label.tooltip_text = folder.path _fill_song_list() summary_label.text = summary_text(folder) upload_progress.visible = false status_label.visible = false upload_button.visible = true cancel_button.text = "Cancel" if Settings.is_offline(): _show_status("Adding a soundtrack needs the server, not a music folder.") elif !folder.has_songs(): _show_status("There are no MP3 files in %s." % folder.path.get_file()) _set_uploading(false) open() ## Starts uploading the songs as a soundtrack with the name in the name field. func upload() -> void: if upload_button.disabled || !upload_button.visible: return if Settings.currently_syncing: _show_status("The server is syncing. Try again when it is done.") return _set_uploading(true) upload_progress.max_value = folder.songs.size() upload_progress.value = 0 upload_progress.visible = true _show_status("Starting the upload…") MusicServerClient.start_upload(UploadFolder.clean_name(name_field.text), _on_started, func() -> void: _stop("Could not start the upload")) ## Stops an upload under way, and closes the popup. func close() -> void: if _uploading: _uploading = false _cancel_on_server() super() ## The last two parts of [param path], e.g. "…/Soundtracks/Chrono Trigger", since the ## start of a long path says the least. static func short_path(path: String) -> String: var parts: PackedStringArray = path.replace("\\", "/").split("/", false) if parts.size() <= 2: return path return "…/" + "/".join(parts.slice(-2)) ## "30 songs, 245.1 MB", and how many files are left out. static func summary_text(shown: UploadFolder) -> String: var text: String = "%d %s, %s" % [shown.songs.size(), "song" if shown.songs.size() == 1 else "songs", SettingsPopup.size_text(shown.total_size)] if !shown.skipped.is_empty(): text += ". %d %s left out: only MP3 files can be uploaded." % [shown.skipped.size(), "file" if shown.skipped.size() == 1 else "files"] return text func _on_started(upload_info: Upload) -> void: if !_uploading: # Closed while the server was answering MusicServerClient.cancel_upload(upload_info.id) return _upload_id = upload_info.id _next = 0 _failures = 0 _send_next() func _send_next() -> void: if !_uploading: return if _next >= folder.songs.size(): _show_status("Adding the soundtrack…") MusicServerClient.finish_upload(_upload_id, _on_finished, func() -> void: _stop("Could not add the soundtrack")) return var song: Dictionary = folder.songs[_next] _show_status("Uploading %d of %d: %s" % [_next + 1, folder.songs.size(), song.name]) # One song at a time, so only one is held in memory var bytes: PackedByteArray = FileAccess.get_file_as_bytes(song.path) if bytes.is_empty(): MusicServerClient.last_error = "the file could not be read" _stop("Could not upload " + song.name) return MusicServerClient.upload_song(_upload_id, song.name, bytes, _on_song_sent, _on_song_failed) func _on_song_sent() -> void: _next += 1 _failures = 0 upload_progress.value = _next _send_next() func _on_song_failed() -> void: _failures += 1 if _failures <= RETRIES && _uploading: _send_next() return _stop("Could not upload " + folder.songs[_next].name) func _on_finished(soundtrack: SoundtrackWithSongList) -> void: _upload_id = "" _uploading = false upload_progress.value = upload_progress.max_value _show_status("Added %s with %d %s." % [soundtrack.name, soundtrack.songs.size(), "song" if soundtrack.songs.size() == 1 else "songs"]) upload_button.visible = false cancel_button.text = "Close" _set_uploading(false) soundtrack_added.emit(soundtrack) # Gives up, says why with the server's reason, and removes what was uploaded func _stop(what: String) -> void: var reason: String = MusicServerClient.last_error _cancel_on_server() _uploading = false upload_progress.visible = false _set_uploading(false) _show_status("%s: %s." % [what, reason if !reason.is_empty() else "the server could not be reached"]) func _cancel_on_server() -> void: if !_upload_id.is_empty(): MusicServerClient.cancel_upload(_upload_id) _upload_id = "" func _set_uploading(uploading: bool) -> void: _uploading = uploading name_field.editable = !uploading choose_folder_button.disabled = uploading _update_buttons() func _update_buttons() -> void: upload_button.disabled = _uploading || folder == null || !folder.has_songs() \ || UploadFolder.clean_name(name_field.text).is_empty() || Settings.is_offline() func _fill_song_list() -> void: Settings.delete_children(song_list) var row: int = 0 for song: Dictionary in folder.songs: song_list.add_child(ListRow.create("%s · %s" % [song.name, SettingsPopup.size_text(song.size)], row, &"TextMedium")) row += 1 for skipped: String in folder.skipped: var list_row: ListRow = ListRow.create("%s · not MP3, left out" % skipped, row, &"TextMedium") list_row.label.self_modulate.a = 0.5 song_list.add_child(list_row) row += 1 func _show_status(message: String) -> void: status_label.text = message status_label.visible = true