Beispiel #1
0
        /// <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;
            }
        }
Beispiel #2
0
        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;
            }
        }
Beispiel #3
0
        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);
        }