protected override Storyboard GetStoryboard() { Storyboard storyboard; try { using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) { var decoder = Decoder.GetDecoder <Storyboard>(stream); string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; // todo: support loading from both set-wide storyboard *and* beatmap specific. if (string.IsNullOrEmpty(storyboardFilename)) { storyboard = decoder.Decode(stream); } else { using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename)))) storyboard = decoder.Decode(stream, secondaryStream); } } } catch (Exception e) { Logger.Error(e, "Storyboard failed to load"); storyboard = new Storyboard(); } storyboard.BeatmapInfo = BeatmapInfo; return(storyboard); }
// todo: expose this when we need to do individual difficulty lookups. protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) => Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler);
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { LogForModel(beatmapSet, "Performing online lookups..."); return(Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray())); }
// todo: expose this when we need to do individual difficulty lookups. protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) => Task.Factory.StartNew(() => lookup(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
private void logForModel(BeatmapSetInfo set, string message) => ArchiveModelManager <BeatmapSetInfo, BeatmapSetFileInfo> .LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}");
public WorkingBeatmap(BeatmapInfo beatmapInfo, BeatmapSetInfo beatmapSetInfo, BeatmapDatabase database) { this.BeatmapInfo = beatmapInfo; this.BeatmapSetInfo = beatmapSetInfo; this.database = database; }
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { return(Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray())); }
/// <summary> /// Refresh an existing instance of a <see cref="BeatmapSetInfo"/> from the store. /// </summary> /// <param name="beatmapSet">A stale instance.</param> /// <returns>A fresh instance.</returns> public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
protected override Storyboard GetStoryboard() { Storyboard storyboard; try { using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) { var decoder = Decoder.GetDecoder <Storyboard>(stream); // todo: support loading from both set-wide storyboard *and* beatmap specific. if (BeatmapSetInfo?.StoryboardFile == null) { storyboard = decoder.Decode(stream); } else { using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile)))) storyboard = decoder.Decode(stream, secondaryStream); } } } catch (Exception e) { Logger.Error(e, "Storyboard failed to load"); storyboard = new Storyboard(); } storyboard.BeatmapInfo = BeatmapInfo; return(storyboard); }
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmap) { // download is in progress (or was, and failed). if (cacheDownloadRequest != null) { return(false); } // database is unavailable. if (!storage.Exists(cache_database_name)) { return(false); } try { using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) { db.Open(); using (var cmd = db.CreateCommand()) { cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmap.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID ?? (object)DBNull.Value)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmap.Path)); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var status = (BeatmapSetOnlineStatus)reader.GetByte(2); beatmap.Status = status; beatmap.BeatmapSet.Status = status; beatmap.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); beatmap.OnlineBeatmapID = reader.GetInt32(1); if (beatmap.Metadata != null) { beatmap.Metadata.AuthorID = reader.GetInt32(3); } if (beatmap.BeatmapSet.Metadata != null) { beatmap.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3); } LogForModel(set, $"Cached local retrieval for {beatmap}."); return(true); } } } } } catch (Exception ex) { LogForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}."); } return(false); }
protected WorkingBeatmap(BeatmapInfo beatmapInfo, BeatmapSetInfo beatmapSetInfo, bool withStoryboard = false) { BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapSetInfo; WithStoryboard = withStoryboard; }
private void logForModel(BeatmapSetInfo set, string message) => RealmArchiveModelImporter <BeatmapSetInfo> .LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}");
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo) { // download is in progress (or was, and failed). if (cacheDownloadRequest != null) { return(false); } // database is unavailable. if (!storage.Exists(cache_database_name)) { return(false); } if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) && string.IsNullOrEmpty(beatmapInfo.Path) && beatmapInfo.OnlineID <= 0) { return(false); } try { using (var db = new SqliteConnection(DatabaseContextFactory.CreateDatabaseConnectionString("online.db", storage))) { db.Open(); using (var cmd = db.CreateCommand()) { cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path)); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { var status = (BeatmapOnlineStatus)reader.GetByte(2); beatmapInfo.Status = status; Debug.Assert(beatmapInfo.BeatmapSet != null); beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return(true); } } } } } catch (Exception ex) { logForModel(set, $"Cached local retrieval for {beatmapInfo} failed with {ex}."); } return(false); }
/// <summary> /// Downloads a beatmap. /// </summary> /// <param name="beatmapSetInfo">The <see cref="BeatmapSetInfo"/> to be downloaded.</param> /// <returns>A new <see cref="DownloadBeatmapSetRequest"/>, or an existing one if a download is already in progress.</returns> public DownloadBeatmapSetRequest Download(BeatmapSetInfo beatmapSetInfo) { var existing = GetExistingDownload(beatmapSetInfo); if (existing != null) { return(existing); } if (api == null) { return(null); } ProgressNotification downloadNotification = new ProgressNotification { Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", }; var request = new DownloadBeatmapSetRequest(beatmapSetInfo); request.DownloadProgressed += progress => { downloadNotification.State = ProgressNotificationState.Active; downloadNotification.Progress = progress; }; request.Success += data => { downloadNotification.State = ProgressNotificationState.Completed; using (var stream = new MemoryStream(data)) using (var archive = new OszArchiveReader(stream)) Import(archive); currentDownloads.Remove(request); }; request.Failure += data => { downloadNotification.State = ProgressNotificationState.Completed; Logger.Error(data, "Failed to get beatmap download information"); currentDownloads.Remove(request); }; downloadNotification.CancelRequested += () => { request.Cancel(); currentDownloads.Remove(request); downloadNotification.State = ProgressNotificationState.Cancelled; return(true); }; currentDownloads.Add(request); PostNotification?.Invoke(downloadNotification); // don't run in the main api queue as this is a long-running task. Task.Run(() => request.Perform(api)); return(request); }
/// <summary> /// Import a beamap into our local <see cref="FileStore"/> storage. /// If the beatmap is already imported, the existing instance will be returned. /// </summary> /// <param name="files">The store to import beatmap files to.</param> /// <param name="beatmaps">The store to import beatmaps to.</param> /// <param name="reader">The beatmap archive to be read.</param> /// <returns>The imported beatmap, or an existing instance if it is already present.</returns> private BeatmapSetInfo importToStorage(FileStore files, BeatmapStore beatmaps, ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); if (string.IsNullOrEmpty(mapName)) { throw new InvalidOperationException("No beatmap files found in the map folder."); } // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) { using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); } var hash = hashable.ComputeSHA2Hash(); // check if this beatmap has already been imported and exit early if so. var beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash); if (beatmapSet != null) { undelete(beatmaps, files, beatmapSet); // ensure all files are present and accessible foreach (var f in beatmapSet.Files) { if (!storage.Exists(f.FileInfo.StoragePath)) { using (Stream s = reader.GetStream(f.Filename)) files.Add(s, false); } } // todo: delete any files which shouldn't exist any more. return(beatmapSet); } List <BeatmapSetFileInfo> fileInfos = new List <BeatmapSetFileInfo>(); // import files to manager foreach (string file in reader.Filenames) { using (Stream s = reader.GetStream(file)) fileInfos.Add(new BeatmapSetFileInfo { Filename = file, FileInfo = files.Add(s) }); } BeatmapMetadata metadata; using (var stream = new StreamReader(reader.GetStream(mapName))) metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata; // check if a set already exists with the same online id. if (metadata.OnlineBeatmapSetID != null) { beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID); } if (beatmapSet == null) { beatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, Beatmaps = new List <BeatmapInfo>(), Hash = hash, Files = fileInfos, Metadata = metadata } } ; var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu")); foreach (var name in mapNames) { using (var raw = reader.GetStream(name)) using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit using (var sr = new StreamReader(ms)) { raw.CopyTo(ms); ms.Position = 0; var decoder = Decoder.GetDecoder(sr); Beatmap beatmap = decoder.DecodeBeatmap(sr); beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID); if (existing == null) { // Exclude beatmap-metadata if it's equal to beatmapset-metadata if (metadata.Equals(beatmap.Metadata)) { beatmap.BeatmapInfo.Metadata = null; } RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.Ruleset = ruleset; beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); } } } return(beatmapSet); }
/// <summary> /// Get an existing download request if it exists. /// </summary> /// <param name="beatmap">The <see cref="BeatmapSetInfo"/> whose download request is wanted.</param> /// <returns>The <see cref="DownloadBeatmapSetRequest"/> object if it exists, or null.</returns> public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID);
/// <summary> /// Downloads a beatmap. /// </summary> /// <param name="beatmapSetInfo">The <see cref="BeatmapSetInfo"/> to be downloaded.</param> /// <param name="noVideo">Whether the beatmap should be downloaded without video. Defaults to false.</param> public void Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) { var existing = GetExistingDownload(beatmapSetInfo); if (existing != null || api == null) { return; } if (!api.LocalUser.Value.IsSupporter) { PostNotification?.Invoke(new SimpleNotification { Icon = FontAwesome.fa_superpowers, Text = "You gotta be a supporter to download for now 'yo" }); return; } ProgressNotification downloadNotification = new ProgressNotification { Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", }; var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); request.DownloadProgressed += progress => { downloadNotification.State = ProgressNotificationState.Active; downloadNotification.Progress = progress; }; request.Success += data => { downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}"; Task.Factory.StartNew(() => { // This gets scheduled back to the update thread, but we want the import to run in the background. using (var stream = new MemoryStream(data)) using (var archive = new OszArchiveReader(stream)) Import(archive); downloadNotification.State = ProgressNotificationState.Completed; }, TaskCreationOptions.LongRunning); currentDownloads.Remove(request); }; request.Failure += data => { downloadNotification.State = ProgressNotificationState.Completed; Logger.Error(data, "Failed to get beatmap download information"); currentDownloads.Remove(request); }; downloadNotification.CancelRequested += () => { request.Cancel(); currentDownloads.Remove(request); downloadNotification.State = ProgressNotificationState.Cancelled; return(true); }; currentDownloads.Add(request); PostNotification?.Invoke(downloadNotification); // don't run in the main api queue as this is a long-running task. Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); BeatmapDownloadBegan?.Invoke(request); }
/// <summary> /// Import a beamap into our local <see cref="FileStore"/> storage. /// If the beatmap is already imported, the existing instance will be returned. /// </summary> /// <param name="reader">The beatmap archive to be read.</param> /// <returns>The imported beatmap, or an existing instance if it is already present.</returns> private BeatmapSetInfo importToStorage(ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); if (string.IsNullOrEmpty(mapName)) { throw new InvalidOperationException("No beatmap files found in the map folder."); } // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) { using (Stream s = reader.GetStream(file)) s.CopyTo(hashable); } var hash = hashable.ComputeSHA2Hash(); // check if this beatmap has already been imported and exit early if so. BeatmapSetInfo beatmapSet; lock (beatmaps) beatmapSet = beatmaps.QueryAndPopulate <BeatmapSetInfo>(b => b.Hash == hash).FirstOrDefault(); if (beatmapSet != null) { Undelete(beatmapSet); return(beatmapSet); } List <BeatmapSetFileInfo> fileInfos = new List <BeatmapSetFileInfo>(); // import files to manager foreach (string file in reader.Filenames) { using (Stream s = reader.GetStream(file)) fileInfos.Add(new BeatmapSetFileInfo { Filename = file, FileInfo = files.Add(s) }); } BeatmapMetadata metadata; using (var stream = new StreamReader(reader.GetStream(mapName))) metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; beatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, Beatmaps = new List <BeatmapInfo>(), Hash = hash, Files = fileInfos, Metadata = metadata }; var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu")); foreach (var name in mapNames) { using (var raw = reader.GetStream(name)) using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit using (var sr = new StreamReader(ms)) { raw.CopyTo(ms); ms.Position = 0; var decoder = BeatmapDecoder.GetDecoder(sr); Beatmap beatmap = decoder.Decode(sr); beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); // TODO: Diff beatmap metadata with set metadata and leave it here if necessary beatmap.BeatmapInfo.Metadata = null; // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.Ruleset = rulesets.Query <RulesetInfo>().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID); beatmap.BeatmapInfo.StarDifficulty = rulesets.Query <RulesetInfo>().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap) .Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); } } return(beatmapSet); }
public WorkingBeatmap(Beatmap beatmap) { this.beatmap = beatmap; BeatmapInfo = beatmap.BeatmapInfo; BeatmapSetInfo = beatmap.BeatmapInfo.BeatmapSet; }