/// <summary> /// Creates an instance of <see cref="ZipFileProvider"/> using a zip file as stream, uses it to initialize /// a <seealso cref="FileModule"/>, and adds the latter to a module container, /// giving it the specified <paramref name="name"/> if not <see langword="null"/>. /// </summary> /// <typeparam name="TContainer">The type of the module container.</typeparam> /// <param name="this">The <typeparamref name="TContainer"/> on which this method is called.</param> /// <param name="name">The name.</param> /// <param name="baseRoute">The base route of the module.</param> /// <param name="zipFileStream">The zip file as stream.</param> /// <param name="configure">A callback used to configure the module.</param> /// <returns><paramref name="this"/> with a <see cref="FileModule"/> added.</returns> /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception> /// <seealso cref="FileModule"/> /// <seealso cref="ZipFileProvider"/> /// <seealso cref="IWebModuleContainer.Modules"/> /// <seealso cref="IComponentCollection{T}.Add"/> public static TContainer WithZipFileStream <TContainer>( this TContainer @this, string?name, string baseRoute, Stream zipFileStream, Action <FileModule>?configure = null) where TContainer : class, IWebModuleContainer { #pragma warning disable CA2000 // Call Dispose on disposable - Ownership of provider is transferred to module var provider = new ZipFileProvider(zipFileStream); #pragma warning restore CA2000 try { var module = new FileModule(baseRoute, provider); return(WithModule(@this, name, module, configure)); } catch { provider.Dispose(); throw; } }
public void HandleRequest(HttpListenerContext context) { var req = context.Request; var resp = context.Response; try { if (!_mod.IsBeatSaberInstalled || !_mod.IsInstalledBeatSaberModded) { resp.BadRequest("Modded Beat Saber is not installed!"); _showToast("Can't upload.", "Modded Beat Saber is not installed!"); return; } var ct = req.ContentType; if (!ct.StartsWith("multipart/form-data")) { resp.BadRequest("Expected content-type of multipart/form-data"); return; } Dictionary <string, MemoryStream> files = new Dictionary <string, MemoryStream>(); var parser = new HttpMultipartParser.StreamingMultipartFormDataParser(req.InputStream); parser.FileHandler = (name, fileName, type, disposition, buffer, bytes) => { if (name != "file") { Log.LogMsg($"Got extra form value named {name}, ignoring it"); return; } MemoryStream s = null; if (files.ContainsKey(fileName)) { s = files[fileName]; } else { s = new MemoryStream(); files.Add(fileName, s); } s.Write(buffer, 0, bytes); }; parser.Run(); if (files.Count < 1) { resp.BadRequest("Didn't get any useable files."); return; } bool forceOverwrite = false; if (!string.IsNullOrWhiteSpace(req.Url.Query)) { foreach (string kvp in req.Url.Query.TrimStart('?').Split("&")) { var split = kvp.Split('='); if (split.Count() < 1) { continue; } if (split[0].ToLower() == "overwrite") { forceOverwrite = true; break; } } } foreach (var file in files.Keys.ToList()) { var s = files[file]; byte[] b = s.ToArray(); files.Remove(file); s.Dispose(); try { //TODO: duplicate code on determining file type with what's in file download... need another method in importmanager to determine file if (file.ToLower().EndsWith("json") || file.ToLower().EndsWith("bplist")) { _getImportManager().ImportFile(file, "application/json", b); } else { MemoryStream ms = new MemoryStream(b); try { var provider = new ZipFileProvider(ms, file, FileCacheMode.None, true, QuestomAssets.Utils.FileUtils.GetTempDirectory()); try { _getImportManager().ImportFromFileProvider(provider, () => { provider.Dispose(); ms.Dispose(); }, overwriteIfExists: forceOverwrite); } catch { provider.Dispose(); throw; } } catch { ms.Dispose(); throw; } } } catch (ImportException iex) { _showToast($"Unable to import file", $"There was an error importing the file {file}: {iex.FriendlyMessage}", ClientModels.ToastType.Error, 5); } catch (Exception ex) { _showToast($"Unable to process file", $"There was an error processing the file {file}.", ClientModels.ToastType.Error, 5); } } resp.Ok(); } catch (Exception ex) { Log.LogErr("Exception handling mod install step 1!", ex); resp.StatusCode = 500; } }
public void ImportFile(string filename, string mimeType, byte[] fileData) { var ext = Path.GetExtension(filename).ToLower().ToLower().TrimStart('.'); if (ext == "bplist" || ext == "json") { try { //oh boy a playlist! BPList bplist; using (MemoryStream ms = new MemoryStream(fileData)) using (StreamReader sr = new StreamReader(ms)) using (JsonTextReader tr = new JsonTextReader(sr)) bplist = new JsonSerializer().Deserialize <BPList>(tr); if (bplist.PlaylistTitle == null || bplist.Songs == null) { Log.LogErr($"Playlist title is null and it's songlist is null from file {filename}."); _showToast("Playlist Failed", $"{filename} did not seem to have a playlist in it.", ToastType.Error); return; } Log.LogMsg($"Creating playlist '{bplist.PlaylistTitle}'"); var bspl = new BeatSaberPlaylist() { PlaylistID = GetPlaylistID(filename, bplist), CoverImageBytes = bplist.Image, PlaylistName = bplist.PlaylistTitle }; var plOp = new AddOrUpdatePlaylistOp(bspl); _getEngine().OpManager.QueueOp(plOp); plOp.FinishedEvent.WaitOne(); if (plOp.Status == OpStatus.Failed) { Log.LogErr("Exception in ImportFile handling a bplist playlist!", plOp.Exception); _showToast("Playlist Failed", $"Unable to create playlist '{bplist.PlaylistTitle}'!", ToastType.Error); return; } else if (plOp.Status == OpStatus.Complete) { //SUCCESS! except what a fail on this poorly structured if/else block } else { throw new Exception("Playlist op finished with a completely unexpected status."); } WebClient client = new WebClient(); var downloads = new List <Download>(); var ops = new ConcurrentBag <AssetOp>(); Debouncey <bool> configDebouncer = new Debouncey <bool>(5000, false); configDebouncer.Debounced += (s, e) => { _getConfig().Config = _getEngine().GetCurrentConfig(); }; foreach (var song in bplist.Songs) { string url = Constants.BEATSAVER_ROOT; if (!string.IsNullOrWhiteSpace(song.Key)) { url += string.Format(Constants.BEATSAVER_KEY_API, song.Key); } else if (!string.IsNullOrWhiteSpace(song.Hash)) { url += string.Format(Constants.BEATSAVER_HASH_API, song.Hash); } else { Log.LogErr($"Song '{song.SongName ?? "(null)"}' in playlist '{bplist.PlaylistTitle}' has no hash or key."); var dl = new Download("http://localhost", false); dl.SetStatus(DownloadStatus.Failed, $"Song '{song.SongName ?? "(null)"}' in playlist '{bplist.PlaylistTitle}' has no hash or key."); downloads.Add(dl); continue; } try { var bsaver = client.DownloadString(url); JToken jt = JToken.Parse(bsaver); var downloadUrl = jt.Value <string>("downloadURL"); if (string.IsNullOrWhiteSpace(downloadUrl)) { Log.LogErr($"Song '{song.SongName ?? "(null)"}' in playlist '{bplist.PlaylistTitle}' did not have a downloadURL in the response from beat saver."); var dl = new Download("http://localhost", false); dl.SetStatus(DownloadStatus.Failed, $"Song '{song.SongName ?? "(null)"}' in playlist '{bplist.PlaylistTitle}' did not have a downloadURL in the response from beat saver."); downloads.Add(dl); continue; } try { var dl = _getDownloadManager().DownloadFile(Constants.BEATSAVER_ROOT.CombineFwdSlash(downloadUrl), false); dl.StatusChanged += (s, e) => { if (e.Status == DownloadStatus.Failed) { Log.LogErr($"Song '{song.SongName}' in playlist '{bplist.PlaylistTitle}' failed to download."); } else if (e.Status == DownloadStatus.Processed) { try { MemoryStream ms = new MemoryStream(dl.DownloadedData); try { var provider = new ZipFileProvider(ms, dl.DownloadedFilename, FileCacheMode.None, true, FileUtils.GetTempDirectory()); try { var op = ImportSongFile(provider, () => { provider.Dispose(); ms.Dispose(); }, bspl, true); op.OpFinished += (so, eo) => { if (eo.Status == OpStatus.Complete) { configDebouncer.EventRaised(this, true); } }; ops.Add(op); } catch { provider.Dispose(); throw; } } catch { ms.Dispose(); throw; } } catch (Exception ex) { Log.LogErr($"Song '{song.SongName}' in playlist '{bplist.PlaylistTitle}' threw an exception on adding to playlist", ex); } } }; downloads.Add(dl); } catch (Exception ex) { Log.LogErr($"Exception making secondary call to get download readirect URL for '{downloadUrl}' on beat saver!", ex); var dl = new Download("http://localhost", false); dl.SetStatus(DownloadStatus.Failed, $"Failed to get download information for '{downloadUrl}' from beat saver!"); downloads.Add(dl); continue; } } catch (Exception ex) { Log.LogErr($"Exception on song '{song.SongName ?? "(null)"}' in playlist '{bplist.PlaylistTitle}' getting info from beatsaver!", ex); var dl = new Download("http://localhost", false); dl.SetStatus(DownloadStatus.Failed, $"Failed to get information on '{song.SongName ?? "(null)"}' in playlist '{bplist.PlaylistTitle}' from beatsaver!"); downloads.Add(dl); } } if (downloads.Count < 1) { Log.LogErr($"Every single song in playlist '{bplist.PlaylistTitle}' failed to start downloading!"); _showToast("Playlist Failed", $"Every song in the playlist '{bplist.PlaylistTitle}' failed to start downloading.", ToastType.Error); return; } downloads.WaitForFinish(); ops.WaitForFinish(); var badDl = downloads.Count(x => x.Status == DownloadStatus.Failed); var goodCount = ops.Count(x => x.Status == OpStatus.Complete); var existCount = ops.Count(x => x.Status == OpStatus.Failed && x.Exception as AddSongException != null && ((AddSongException)x.Exception).FailType == AddSongFailType.SongExists); var badSong = ops.Count(x => x.Status == OpStatus.Failed) - existCount; _getConfig().Config = _getEngine().GetCurrentConfig(); _showToast("Playlist Import Complete", $"Playlist '{bplist.PlaylistTitle}' has completed. {goodCount} of {bplist.Songs.Count} songs succeeded.", ToastType.Success, 8); // successfully downloaded, {existCount} already existed in other playlists, {badDl} failed to download, {badSong} failed to import." //save playlist file for later use on QAE commit var plFileSave = _qaeConfig.PlaylistsPath.CombineFwdSlash(Path.GetFileNameWithoutExtension(filename) + ".json"); QueuedFileOp qfo = new QueuedFileOp() { Type = QueuedFileOperationType.WriteFile, TargetPath = plFileSave, SourceData = fileData }; _getEngine().OpManager.QueueOp(qfo); } catch (Exception ex) { Log.LogErr("Exception in ImportFile handling a bplist playlist!", ex); _showToast("Playlist Failed", $"Failed to read playlist from {filename}!", ToastType.Error); } } else { Log.LogErr($"Unsupported file type uploaded as {filename}"); _showToast("Unsupported File", $"The file {filename} is not a supported type.", ToastType.Error); } }
private void StatusChangeHandler(object sender, DownloadStatusChangeArgs args) { //this method is the main "loop" of the download manager. When a download is queued, its status is set to "not started", which triggers // and event that lands it here. This function handles all of the state changes, starting of downloads, clearing of succeeded or failed downloads // and the triggering of processing downloads that have completed lock (_downloads) { var dl = sender as Download; switch (args.Status) { case DownloadStatus.Aborted: case DownloadStatus.Failed: _downloads.Remove(dl); break; case DownloadStatus.Processed: _downloads.Remove(dl); break; case DownloadStatus.Downloaded: if (dl.ProcessAfterDownload) { dl.SetStatus(DownloadStatus.Processing); Task.Run(() => { try { //TODO: duplicate code on determining file type with what's in file upload... need another method in importmanager to determine file if (dl.DownloadedFilename.ToLower().EndsWith("json") || dl.DownloadedFilename.ToLower().EndsWith("bplist")) { _importManager.ImportFile(dl.DownloadedFilename, "application/json", dl.DownloadedData); } else { //load the downloaded data into a zip file provider and have the import manager try importing it MemoryStream ms = new MemoryStream(dl.DownloadedData); try { var provider = new ZipFileProvider(ms, dl.DownloadedFilename, FileCacheMode.None, true, QuestomAssets.Utils.FileUtils.GetTempDirectory()); try { _importManager.ImportFromFileProvider(provider, () => { provider.Dispose(); ms.Dispose(); }, dl.TargetPlaylistID, dl.SuppressToast); } catch { provider.Dispose(); throw; } } catch { ms.Dispose(); throw; } } //if the import manager succeeds, mark the download status as processed. // The status change events will land it in this parent event handler again, and it'll be cleared from the download list. dl.SetStatus(DownloadStatus.Processed); } catch (ImportException iex) { Log.LogErr($"Exception processing downloaded file from {dl.DownloadUrl}", iex); dl.SetStatus(DownloadStatus.Failed, $"Failed to process {dl.DownloadUrl}: {iex.FriendlyMessage}"); } catch (Exception ex) { Log.LogErr($"Exception processing downloaded file from {dl.DownloadUrl}", ex); dl.SetStatus(DownloadStatus.Failed, $"Unable to process downloaded file from {dl.DownloadUrl}"); } }); } else { dl.SetStatus(DownloadStatus.Processed); } break; } if (_downloads.Count(x => x.Status == DownloadStatus.Downloading) < _maxConcurrentDownloads) { var next = _downloads.FirstOrDefault(x => x.Status == DownloadStatus.NotStarted); if (next != null) { next.Start(); } } } StatusChanged?.Invoke(sender, args); }