private async Task UploadLoop() { _uploader = new Uploader(); _analyzer = new Analyzer(); _monitor = new Monitor(); var replays = new List <ReplayFile>(_storage.Load()); var lookup = new HashSet <ReplayFile>(replays); var comparer = new ReplayFile.ReplayFileComparer(); replays.AddRange(_monitor.ScanReplays().Select(x => new ReplayFile(x)).Where(x => !lookup.Contains(x, comparer))); Files.AddRange(replays.OrderByDescending(x => x.Created)); _monitor.ReplayAdded += async(_, e) => { await EnsureFileAvailable(e.Data, 3000); Files.Insert(0, new ReplayFile(e.Data)); }; _monitor.Start(); Files.CollectionChanged += (_, __) => _collectionUpdated.Set(); _analyzer.MinimumBuild = await _uploader.GetMinimumBuild(); while (true) { try { // take files one by one, in case newer replays are added to the top of the list while we upload older ones // upload in a single thread to prevent load spikes on server var file = Files.Where(f => f.UploadStatus == UploadStatus.None).FirstOrDefault(); if (file != null) { file.UploadStatus = UploadStatus.InProgress; // test if replay is eligible for upload (not AI, PTR, Custom, etc) var replay = _analyzer.Analyze(file); if (file.UploadStatus == UploadStatus.InProgress) { // if it is, upload it await _uploader.Upload(file); } try { // save only replays with fixed status. Will retry failed ones on next launch. _storage.Save(Files.Where(x => !new[] { UploadStatus.None, UploadStatus.UploadError, UploadStatus.InProgress }.Contains(x.UploadStatus))); } catch (Exception ex) { _log.Error(ex, "Error saving replay list"); } if (ShouldDelete(file, replay)) { try { _log.Info($"Deleting replay {file}"); file.Deleted = true; File.Delete(file.Filename); } catch (Exception ex) { _log.Error(ex, "Error deleting file"); } } } else { await _collectionUpdated.WaitAsync(); } } catch (Exception ex) { _log.Error(ex, "Error in upload loop"); } } }