public void DeleteAll() { Collections.Clear(); PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); }
public void DeleteAll() { var maps = GetAllUsableBeatmapSets(); if (maps.Count == 0) { return; } var notification = new ProgressNotification { Progress = 0, State = ProgressNotificationState.Active, }; PostNotification?.Invoke(notification); int i = 0; foreach (var b in maps) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return; } notification.Text = $"Deleting ({i} of {maps.Count})"; notification.Progress = (float)++i / maps.Count; Delete(b); } notification.State = ProgressNotificationState.Completed; }
public void UndeleteAll() { var deleteMaps = QueryBeatmapSets(bs => bs.DeletePending).ToList(); if (!deleteMaps.Any()) { return; } var notification = new ProgressNotification { CompletionText = "Restored all deleted beatmaps!", Progress = 0, State = ProgressNotificationState.Active, }; PostNotification?.Invoke(notification); int i = 0; foreach (var bs in deleteMaps) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return; } notification.Text = $"Restoring ({i} of {deleteMaps.Count})"; notification.Progress = (float)++i / deleteMaps.Count; Undelete(bs); } notification.State = ProgressNotificationState.Completed; }
public async Task Import(Stream stream) { var notification = new ProgressNotification { State = ProgressNotificationState.Active, Text = "匯入圖譜收藏準備中..." }; PostNotification?.Invoke(notification); var collection = readCollections(stream, notification); bool importCompleted = false; Schedule(() => { importCollections(collection); importCompleted = true; }); while (!IsDisposed && !importCompleted) { await Task.Delay(10); } notification.CompletionText = $"已匯入 {collection.Count} 個圖譜收藏"; notification.State = ProgressNotificationState.Completed; }
public async Task OnPostActionAsync(PostNotification info) { _postActionProducer.Produce(TOPIC, new Message <Null, PostNotification>() { Value = info }); }
/// <summary> /// Import one or more <see cref="BeatmapSetInfo"/> from filesystem <paramref name="paths"/>. /// This will post notifications tracking progress. /// </summary> /// <param name="paths">One or more beatmap locations on disk.</param> public List <BeatmapSetInfo> Import(params string[] paths) { var notification = new ProgressNotification { Text = "Beatmap import is initialising...", CompletionText = "Import successful!", Progress = 0, State = ProgressNotificationState.Active, }; PostNotification?.Invoke(notification); List <BeatmapSetInfo> imported = new List <BeatmapSetInfo>(); int i = 0; foreach (string path in paths) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return(imported); } try { notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}"; using (ArchiveReader reader = getReaderFrom(path)) imported.Add(Import(reader)); notification.Progress = (float)++i / paths.Length; // We may or may not want to delete the file depending on where it is stored. // e.g. reconstructing/repairing database with beatmaps from default storage. // Also, not always a single file, i.e. for LegacyFilesystemReader // TODO: Add a check to prevent files from storage to be deleted. try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception e) { Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); } } catch (Exception e) { e = e.InnerException ?? e; Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})"); } } notification.State = ProgressNotificationState.Completed; return(imported); }
public ScoreManager(RulesetStore rulesets, Func <BeatmapManager> beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) : base(storage, realm) { this.scheduler = scheduler; this.difficultyCache = difficultyCache; this.configManager = configManager; scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm) { PostNotification = obj => PostNotification?.Invoke(obj) }; }
public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore <byte[]> resources, AudioManager audio, Scheduler scheduler) : base(storage, realm) { this.audio = audio; this.scheduler = scheduler; this.host = host; this.resources = resources; userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files")); skinImporter = new SkinImporter(storage, realm, this) { PostNotification = obj => PostNotification?.Invoke(obj), }; var defaultSkins = new[] { DefaultLegacySkin = new DefaultLegacySkin(this), DefaultSkin = new DefaultSkin(this), }; // Ensure the default entries are present. realm.Write(r => { foreach (var skin in defaultSkins) { if (r.Find <SkinInfo>(skin.SkinInfo.ID) == null) { r.Add(skin.SkinInfo.Value); } } }); CurrentSkinInfo.ValueChanged += skin => { CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin); }; CurrentSkin.Value = DefaultSkin; CurrentSkin.ValueChanged += skin => { if (!skin.NewValue.SkinInfo.Equals(CurrentSkinInfo.Value)) { throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); } SourceChanged?.Invoke(); }; }
public async Task OnPostActionAsync(PostNotification info) { var currentDayStatistic = await _dbAccess.GetBlogDayStatisticAsync(DateTime.UtcNow.Date); var countDelta = info.Action switch { PostAction.CREATED => 1, PostAction.DELETED => - 1, _ => throw new NotSupportedException() }; currentDayStatistic.PostsCount += countDelta; await _db.SaveChangesAsync(); }
public void DeleteVideos(List <BeatmapSetInfo> items, bool silent = false) { if (items.Count == 0) { return; } var notification = new ProgressNotification { Progress = 0, Text = $"Preparing to delete all {HumanisedModelName} videos...", CompletionText = "No videos found to delete!", State = ProgressNotificationState.Active, }; if (!silent) { PostNotification?.Invoke(notification); } int i = 0; int deleted = 0; foreach (var b in items) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return; } var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal))); if (video != null) { DeleteFile(b, video); deleted++; notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!"; } notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)"; notification.Progress = (float)++i / items.Count; } notification.State = ProgressNotificationState.Completed; }
public async Task Import(Stream stream) { var notification = new ProgressNotification { State = ProgressNotificationState.Active, Text = "Collections import is initialising..." }; PostNotification?.Invoke(notification); var collection = readCollections(stream, notification); await importCollections(collection); notification.CompletionText = $"Imported {collection.Count} collections"; notification.State = ProgressNotificationState.Completed; }
async Task dispatcherLoopAsync() { await ThreadingUtils.ContinueAtDedicatedThread(); while (true) { try { var message = _consumer.Consume(); var scope = _scopeBuilder .CreateScope(); var controller = scope.ServiceProvider .GetRequiredService <IStatisticServiceAPI>(); var parameter = message.Value; var handler = getHandler(); executeAsync(); ///////////////////////////////////////////// Func <Task> getHandler() { return(message.Value switch { CommentaryNotification cn => () => controller.OnCommentaryActionAsync(cn), PostNotification pn => () => controller.OnPostActionAsync(pn), SeenNotification sn => () => controller.OnSeenAsync(sn), UserNotification un => () => controller.OnUserActionAsync(un), _ => throw new NotSupportedException() }); } async void executeAsync() { using (scope) { await ThreadingUtils.ContinueAtThreadPull(); await handler(); _logger.LogInformation($"Consumed message '{message.Value}' at: '{message.TopicPartitionOffset}'."); } } }
/// <summary> /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. /// </summary> public void Undelete(List <TModel> items, bool silent = false) { if (!items.Any()) { return; } var notification = new ProgressNotification { CompletionText = "Restored all deleted items!", Progress = 0, State = ProgressNotificationState.Active, }; if (!silent) { PostNotification?.Invoke(notification); } int i = 0; using (ContextFactory.GetForWrite()) { foreach (var item in items) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return; } notification.Text = $"Restoring ({++i} of {items.Count})"; Undelete(item); notification.Progress = (float)i / items.Count; } } notification.State = ProgressNotificationState.Completed; }
/// <summary> /// Delete multiple items. /// This will post notifications tracking progress. /// </summary> public void Delete(List <TModel> items, bool silent = false) { if (items.Count == 0) { return; } var notification = new ProgressNotification { Progress = 0, CompletionText = $"Deleted all {typeof(TModel).Name.Replace("Info", "").ToLower()}s!", State = ProgressNotificationState.Active, }; if (!silent) { PostNotification?.Invoke(notification); } int i = 0; using (ContextFactory.GetForWrite()) { foreach (var b in items) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return; } notification.Text = $"Deleting ({++i} of {items.Count})"; Delete(b); notification.Progress = (float)i / items.Count; } } notification.State = ProgressNotificationState.Completed; }
public void Delete(List <TModel> items, bool silent = false) { if (items.Count == 0) { return; } var notification = new ProgressNotification { Progress = 0, Text = $"Preparing to delete all {HumanisedModelName}s...", CompletionText = $"Deleted all {HumanisedModelName}s!", State = ProgressNotificationState.Active, }; if (!silent) { PostNotification?.Invoke(notification); } int i = 0; foreach (var b in items) { if (notification.State == ProgressNotificationState.Cancelled) { // user requested abort return; } notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})"; Delete(b); notification.Progress = (float)i / items.Count; } notification.State = ProgressNotificationState.Completed; }
public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider?api, AudioManager audioManager, IResourceStore <byte[]> gameResources, GameHost?host = null, WorkingBeatmap?defaultBeatmap = null, bool performOnlineLookups = false) : base(storage, realm) { if (performOnlineLookups) { if (api == null) { throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required."); } onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } var userResources = new RealmFileStore(realm, storage).Store; BeatmapTrackStore = audioManager.GetTrackStore(userResources); beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); }
/// <summary> /// Begin a download for the requested <see cref="TModel"/>. /// </summary> /// <param name="model">The <see cref="TModel"/> to be downloaded.</param> /// <param name="minimiseDownloadSize">Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.</param> /// <returns>Whether the download was started.</returns> public bool Download(TModel model, bool minimiseDownloadSize = false) { if (!canDownload(model)) { return(false); } var request = CreateDownloadRequest(model, minimiseDownloadSize); DownloadNotification notification = new DownloadNotification { Text = $"Downloading {request.Model}", }; request.DownloadProgressed += progress => { notification.State = ProgressNotificationState.Active; notification.Progress = progress; }; request.Success += filename => { Task.Factory.StartNew(async() => { // This gets scheduled back to the update thread, but we want the import to run in the background. await Import(notification, filename); currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; request.Failure += error => { DownloadFailed?.Invoke(request); if (error is OperationCanceledException) { return; } notification.State = ProgressNotificationState.Cancelled; Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); currentDownloads.Remove(request); }; notification.CancelRequested += () => { request.Cancel(); currentDownloads.Remove(request); notification.State = ProgressNotificationState.Cancelled; return(true); }; currentDownloads.Add(request); PostNotification?.Invoke(notification); Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); DownloadBegan?.Invoke(request); return(true); }
/// <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> /// 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.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); return(request); }
/// <summary> /// Downloads a beatmap. /// This will post notifications tracking progress. /// </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> /// <returns>Downloading can happen</returns> public bool Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) { var existing = GetExistingDownload(beatmapSetInfo); if (existing != null || api == null) { return(false); } var downloadNotification = new DownloadNotification { CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", 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 += filename => { 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. var importedBeatmap = Import(filename); downloadNotification.CompletionClickAction = () => { PresentCompletedImport(importedBeatmap.Yield()); return(true); }; downloadNotification.State = ProgressNotificationState.Completed; currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; request.Failure += error => { BeatmapDownloadFailed?.Invoke(request); if (error is OperationCanceledException) { return; } downloadNotification.State = ProgressNotificationState.Cancelled; Logger.Error(error, "Beatmap download failed!"); 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); return(true); }
/// <summary> /// Begin a download for the requested <typeparamref name="TModel"/>. /// </summary> /// <param name="model">The <typeparamref name="TModel"/> to be downloaded.</param> /// <param name="minimiseDownloadSize">Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.</param> /// <returns>Whether the download was started.</returns> public bool Download(TModel model, bool minimiseDownloadSize = false) { if (!canDownload(model)) { return(false); } var request = CreateDownloadRequest(model, minimiseDownloadSize); DownloadNotification notification = new DownloadNotification { Text = $"Downloading {request.Model}", }; request.DownloadProgressed += progress => { notification.State = ProgressNotificationState.Active; notification.Progress = progress; }; request.Success += filename => { Task.Factory.StartNew(async() => { // This gets scheduled back to the update thread, but we want the import to run in the background. var imported = await Import(notification, filename); // for now a failed import will be marked as a failed download for simplicity. if (!imported.Any()) { downloadFailed.Value = new WeakReference <ArchiveDownloadRequest <TModel> >(request); } currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; request.Failure += triggerFailure; notification.CancelRequested += () => { request.Cancel(); return(true); }; currentDownloads.Add(request); PostNotification?.Invoke(notification); api.PerformAsync(request); downloadBegan.Value = new WeakReference <ArchiveDownloadRequest <TModel> >(request); return(true); void triggerFailure(Exception error) { currentDownloads.Remove(request); downloadFailed.Value = new WeakReference <ArchiveDownloadRequest <TModel> >(request); notification.State = ProgressNotificationState.Cancelled; if (!(error is OperationCanceledException)) { Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); } } }
/// <summary> /// Downloads a beatmap. /// This will post notifications tracking progress. /// </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> /// <returns>Downloading can happen</returns> public bool Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) { var existing = GetExistingDownload(beatmapSetInfo); if (existing != null || api == null) { return(false); } var downloadNotification = new DownloadNotification { Text = $"Downloading {beatmapSetInfo}", }; var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); request.DownloadProgressed += progress => { downloadNotification.State = ProgressNotificationState.Active; downloadNotification.Progress = progress; }; request.Success += filename => { Task.Factory.StartNew(() => { // This gets scheduled back to the update thread, but we want the import to run in the background. Import(downloadNotification, filename); currentDownloads.Remove(request); }, TaskCreationOptions.LongRunning); }; request.Failure += error => { BeatmapDownloadFailed?.Invoke(request); if (error is OperationCanceledException) { return; } downloadNotification.State = ProgressNotificationState.Cancelled; Logger.Error(error, "Beatmap download failed!"); 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(() => { try { request.Perform(api); } catch { // no need to handle here as exceptions will filter down to request.Failure above. } }, TaskCreationOptions.LongRunning); BeatmapDownloadBegan?.Invoke(request); return(true); }