async Task RemoveAsyncImpl(string href) { var uri = Ao3SiteDataLookup.ReadingListlUri(href); if (uri == null) { return; } await App.Database.ReadingListCached.DeleteAsync(uri.AbsoluteUri); var viewmodel = readingListBacking.FindInAll((m) => m.Uri == uri); if (viewmodel is null) { return; } viewmodel.PropertyChanged -= Viewmodel_PropertyChanged; await wvp.DoOnMainThreadAsync(() => { if (viewmodel == selectedItem) { UpdateSelectedItem(null); } if (Ao3SiteDataLookup.ReadingListlUri(wvp.CurrentUri.AbsoluteUri) == viewmodel.Uri) { wvp.AddRemoveReadingListToolBarItem_IsActive = AddPageButton.IsActive = false; } readingListBacking.Remove(viewmodel); viewmodel.Dispose(); }); }
public string ShouldFilterWork(long workId, IEnumerable <string> workauthors, IEnumerable <string> worktags, IEnumerable <long> workserieses) { using (rwlock.ReadLock()) { if (works.TryGetValue(workId, out var workname)) { return($"Work {workId} {workname ?? ""}".TrimEnd()); } foreach (var author in workauthors) { if (authors.Contains(author)) { return($"Author {author}"); } } foreach (var tagstr in worktags) { var tag = Ao3SiteDataLookup.LookupTagQuick(tagstr, true); if (tags.Contains(tag?.actual)) { return($"Tag {tag.actual}"); } } foreach (var series in workserieses) { if (serieses.TryGetValue(series, out var seriesname)) { return($"Series {series} {seriesname ?? ""}".TrimEnd()); } } } return(""); }
private void WebView_NewWindowRequested(WebView sender, WebViewNewWindowRequestedEventArgs args) { args.Handled = true; var uri = Ao3SiteDataLookup.CheckUri(args.Uri); if (uri != null) { webView.Navigate(uri); } else { OpenExternal(args.Uri); } }
public string GetFilterFromUrl(string url, string extra) { if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) { return(null); } Match match = null; if ((match = Ao3SiteDataLookup.regexWork.Match(uri.LocalPath)).Success) { var sWORKID = match.Groups["WORKID"].Value; if (long.TryParse(sWORKID, out var id)) { return($"Work {id} {extra ?? ""}".TrimEnd()); } } else if ((match = Ao3SiteDataLookup.regexSeries.Match(uri.LocalPath)).Success) { var sSERIESID = match.Groups["SERIESID"].Value; if (long.TryParse(sSERIESID, out var id)) { return($"Series {id} {extra ?? ""}".TrimEnd()); } } else if ((match = Ao3SiteDataLookup.regexAuthor.Match(uri.LocalPath)).Success) { var sUSERNAME = match.Groups["USERNAME"].Value; return($"Author {sUSERNAME}".TrimEnd()); } else if ((match = Ao3SiteDataLookup.regexTag.Match(uri.LocalPath)).Success) { var sTAGNAME = match.Groups["TAGNAME"].Value; var tag = Ao3SiteDataLookup.UnescapeTag(sTAGNAME); return($"Tag {tag}".TrimEnd()); } return(null); }
public async Task <IDictionary <string, bool> > AreUrlsInListAsync(string[] urls) { if (!restored.Task.IsCompleted) { await restored.Task; } return(await Task.Run(() => { var urlmap = new List <KeyValuePair <string, Uri> >(); IDictionary <string, bool> result = new Dictionary <string, bool>(); foreach (var url in urls) { var uri = Ao3SiteDataLookup.ReadingListlUri(url); if (!(uri is null)) { urlmap.Add(new KeyValuePair <string, Uri>(url, uri)); } result[url] = false; } foreach (var m in readingListBacking.AllSafe) { for (var i = 0; i < urlmap.Count; i++) { var kvp = urlmap[i]; if (m.HasUri(kvp.Value)) { result[kvp.Key] = true; urlmap.RemoveAt(i); i--; } } if (urlmap.Count == 0) { break; } } return result; })); }
public async Task <IDictionary <long, bool> > AreWorksInListAsync(long[] workids) { if (!restored.Task.IsCompleted) { await restored.Task; } return(await Task.Run(() => { var workmap = new List <KeyValuePair <long, Uri> >(); IDictionary <long, bool> result = new Dictionary <long, bool>(); foreach (var workid in workids) { var uri = Ao3SiteDataLookup.ReadingListlUri("http://archiveofourown.org/works/" + workid); if (!(uri is null)) { workmap.Add(new KeyValuePair <long, Uri>(workid, uri)); } } foreach (var m in readingListBacking.AllSafe) { for (var i = 0; i < workmap.Count; i++) { var kvp = workmap[i]; if (m.HasUri(kvp.Value)) { result[kvp.Key] = true; workmap.RemoveAt(i); i--; } } if (workmap.Count == 0) { break; } } return result; })); }
async Task <Ao3PageViewModel> AddAsyncImpl(string href, long timestamp) { if (!(readingListBacking.FindInAll((m) => m.Uri.AbsoluteUri == href) is null)) { return(null); } var model = Ao3SiteDataLookup.LookupQuick(href); if (model is null) { return(null); } if (!(readingListBacking.FindInAll((m) => m.Uri.AbsoluteUri == model.Uri.AbsoluteUri) is null)) { return(null); } return(await wvp.DoOnMainThreadAsync(async() => { var viewmodel = new Ao3PageViewModel(model.Uri, model.HasChapters ? 0 : (int?)null, model.Type == Ao3PageType.Work ? tagTypeVisible : null) // Set unread to 0. this is to prevents UI locks when importing huge reading lists during syncs { TagsVisible = tags_visible }; viewmodel.PropertyChanged += Viewmodel_PropertyChanged; await viewmodel.SetBaseDataAsync(model, false); readingListBacking.Add(viewmodel); await App.Database.ReadingListCached.InsertOrUpdateAsync(new ReadingList(model, timestamp, viewmodel.ChaptersRead)); var uri = Ao3SiteDataLookup.ReadingListlUri(wvp.CurrentUri.AbsoluteUri); if (uri == viewmodel.Uri) { wvp.AddRemoveReadingListToolBarItem_IsActive = AddPageButton.IsActive = true; } return viewmodel; })); }
public void Navigate(Uri uri, bool allowext = true) { if (uri == null) { return; } var newuri = Ao3SiteDataLookup.CheckUri(uri); if (newuri == null) { if (allowext) { OpenExternal(uri); } return; } helper?.Reset(); webView.Navigate(newuri); }
public async void PageChange(Uri uri) { if (!(uri is null)) { uri = Ao3SiteDataLookup.ReadingListlUri(uri.AbsoluteUri); } await wvp.DoOnMainThreadAsync(() => { if (uri is null) { UpdateSelectedItem(null); wvp.AddRemoveReadingListToolBarItem_IsActive = AddPageButton.IsActive = false; } else { var item = readingListBacking.FindInAll((m) => m.HasUri(uri)); UpdateSelectedItem(item); wvp.AddRemoveReadingListToolBarItem_IsActive = AddPageButton.IsActive = !(item is null); } }).ConfigureAwait(false); }
public async Task RefreshAsync(Ao3PageViewModel viewmodel) { var model = await Ao3SiteDataLookup.LookupAsync(viewmodel.Uri.AbsoluteUri); if (!(model is null)) { if (viewmodel.Uri.AbsoluteUri != model.Uri.AbsoluteUri) { await App.Database.ReadingListCached.DeleteAsync(viewmodel.Uri.AbsoluteUri); var pvm = readingListBacking.FindInAll((m) => m.Uri.AbsoluteUri == model.Uri.AbsoluteUri); if (!(pvm is null)) { await wvp.DoOnMainThreadAsync(() => { readingListBacking.Remove(pvm); pvm.Dispose(); }); } } // If missing title in update, get it from old if (string.IsNullOrEmpty(model.Title)) { model.Title = viewmodel.BaseData?.Title; } await wvp.DoOnMainThreadAsync(async() => { await viewmodel.SetBaseDataAsync(model, true); viewmodel.Loaded = true; readingListBacking.Remove(viewmodel); readingListBacking.Add(viewmodel); }); await WriteViewModelToDbAsync(viewmodel, new ReadingList(model, 0, viewmodel.Unread)); } }
public void Navigate(Uri uri, bool allowext = true) { if (uri == null) { return; } var newuri = Ao3SiteDataLookup.CheckUri(uri); if (newuri == null) { if (allowext) { OpenExternal(uri); } return; } helper?.Reset(); if (OnNavigationStarting(newuri) == false) { webView.LoadUrl(newuri.AbsoluteUri); } }
public Ao3TrackDatabase() { SQLitePCL.Batteries_V2.Init(); SQLitePCL.raw.FreezeProvider(); SQLitePCL.raw.sqlite3_shutdown(); int result = SQLitePCL.raw.sqlite3_config(SQLitePCL.raw.SQLITE_CONFIG_SERIALIZED); database = new SQLiteConnection(DatabasePath); // create the tables database.CreateTable <Variable>(); database.CreateTable <Work>(); database.CreateTable <TagCache>(); database.CreateTable <LanguageCache>(); database.CreateTable <ReadingList>(); database.CreateTable <SortColumn>(); database.CreateTable <ListFilter>(); // Do any database upgrades if necessaey TryGetVariable("DatabaseVersion", int.TryParse, out var DatabaseVersion, 0); olddatabaseversion = DatabaseVersion; if (DatabaseVersion < Version.Version.AsInteger(1, 0, 5, -1)) { database.Execute("UPDATE Work SET seq=0 WHERE seq IS NULL"); database.Execute("DELETE FROM TagCache"); // Delete the entire cache cause it's behaviour has slightly changed } if (DatabaseVersion < Version.Version.AsInteger(1, 0, 7, -1)) { TryParseDelegate <bool?> tryparse = (string s, out bool?res) => { return(TryParseNullable(s, out res, bool.TryParse)); }; var map = new NullKeyDictionary <bool?, UnitConvSetting> { { null, UnitConvSetting.None }, { true, UnitConvSetting.USToMetric }, { false, UnitConvSetting.MetricToUS } }; ConvertVariable("UnitConvOptions.tempToC", map, tryparse, "UnitConvOptions.temp"); ConvertVariable("UnitConvOptions.distToM", map, tryparse, "UnitConvOptions.dist"); ConvertVariable("UnitConvOptions.volumeToM", map, tryparse, "UnitConvOptions.volume"); ConvertVariable("UnitConvOptions.weightToM", map, tryparse, "UnitConvOptions.weight"); } if (DatabaseVersion < Version.Version.AsInteger(1, 1, 0, 4)) { var columns = database.GetTableInfo("ReadingList"); if (columns.Exists((col) => col.Name == "Uri") && columns.Exists((col) => col.Name == "Timestamp") && columns.Exists((col) => col.Name == "PrimaryTag") && columns.Exists((col) => col.Name == "Title") && columns.Exists((col) => col.Name == "Summary") && columns.Exists((col) => col.Name == "Unread")) { try { var oldValues = database.Query <ReadingListV1>("SELECT Uri, Timestamp, PrimaryTag, Title, Summary, Unread FROM ReadingList"); var newValues = new List <ReadingList>(oldValues.Count); if (oldValues.Count > 0) { var models = Ao3SiteDataLookup.LookupQuick(oldValues.Select((row) => row.Uri)); foreach (var item in oldValues) { if (models.TryGetValue(item.Uri, out var model)) { if (string.IsNullOrWhiteSpace(model.Title) || model.Type == Models.Ao3PageType.Work) { model.Title = item.Title; } if (string.IsNullOrWhiteSpace(model.PrimaryTag) || model.PrimaryTag.StartsWith("<")) { model.PrimaryTag = item.PrimaryTag; var tagdata = Ao3SiteDataLookup.LookupTagQuick(item.PrimaryTag); if (tagdata is null) { model.PrimaryTagType = Models.Ao3TagType.Other; } else { model.PrimaryTagType = Ao3SiteDataLookup.GetTypeForCategory(tagdata.category); } } if (!(model.Details is null) && (model.Details.Summary is null) && !string.IsNullOrEmpty(item.Summary)) { model.Details.Summary = item.Summary; } newValues.Add(new ReadingList(model, item.Timestamp, item.Unread)); } else { newValues.Add(new ReadingList { Uri = item.Uri, Timestamp = item.Timestamp, Unread = item.Unread }); } } } database.BeginTransaction(); database.DropTable <ReadingList>(); database.CreateTable <ReadingList>(); database.InsertAll(newValues, false); database.Commit(); } catch (Exception) { } } } if (DatabaseVersion != Version.Version.Integer) { SaveVariable("DatabaseVersion", Version.Version.Integer); } ReadingListCached = new CachedTimestampedTable <ReadingList, string, Ao3TrackDatabase>(this); ListFiltersCached = new CachedTimestampedTable <ListFilter, string, Ao3TrackDatabase>(this); // Work should be cached table too? }
public Task SetFilterStringsAsync(string tags, string authors, string works, string serieses) { return(Task.Run(async() => { string[] tagarray; using (var tasklimit = new SemaphoreSlim(MaxRefreshTasks)) { var tasks = new Queue <Task <string> >(); // Do this parallel cause it's a pain in the ass foreach (var tagstr in tags.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { if (string.IsNullOrWhiteSpace(tagstr)) { continue; } await tasklimit.WaitAsync(); tasks.Enqueue(Task.Run(async() => { var res = await Ao3SiteDataLookup.LookupTagAsync(tagstr.Trim()); await Task.Yield(); await Task.Delay(RefreshDelay); tasklimit.Release(); return res.actual; })); while (tasks.Count > 0 && tasks.Peek().IsCompleted) { await tasks.Dequeue(); } } ; tagarray = await Task.WhenAll(tasks); } rwlock.WriteLock().TaskRun(async() => { await App.Database.ListFiltersCached.BeginDeferralAsync(); try { var existingfilters = new HashSet <string>((await App.Database.ListFiltersCached.SelectAsync()).Select( (item) => item.data )); this.tags.Clear(); this.authors.Clear(); this.works.Clear(); this.serieses.Clear(); var newfilters = new List <ListFilter>(); var now = DateTime.UtcNow.ToUnixTime(); foreach (var tag in tagarray) { if (string.IsNullOrWhiteSpace(tag)) { continue; } if (!this.tags.Contains(tag)) { this.tags.Add(tag); string key = "Tag " + tag; if (existingfilters.Contains(key)) { existingfilters.Remove(key); } else { newfilters.Add(new ListFilter { data = key, timestamp = now }); } } } foreach (var author in authors.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { if (string.IsNullOrWhiteSpace(author)) { continue; } var trimmed = author.Trim(); if (!this.authors.Contains(trimmed)) { this.authors.Add(trimmed); string key = "Author " + trimmed; if (existingfilters.Contains(key)) { existingfilters.Remove(key); } else { newfilters.Add(new ListFilter { data = key, timestamp = now }); } } } foreach (var work in works.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { if (string.IsNullOrWhiteSpace(work)) { continue; } var split = work.Trim().Split(new[] { ' ' }, 2); if (split.Length > 1 && long.TryParse(split[0], out var id) && !this.works.ContainsKey(id)) { this.works.Add(id, split.Length == 2 ? split[1] : null); string key = "Work " + id.ToString() + (split.Length == 2 ? " " + split[1] : ""); if (existingfilters.Contains(key)) { existingfilters.Remove(key); } else { newfilters.Add(new ListFilter { data = key, timestamp = now }); } } } foreach (var series in serieses.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { if (string.IsNullOrWhiteSpace(series)) { continue; } var split = series.Trim().Split(new[] { ' ' }, 2); if (split.Length > 1 && long.TryParse(split[0], out var id) && !this.works.ContainsKey(id)) { this.serieses.Add(id, split.Length == 2 ? split[1] : null); string key = "Series " + id.ToString() + (split.Length == 2 ? " " + split[1] : ""); if (existingfilters.Contains(key)) { existingfilters.Remove(key); } else { newfilters.Add(new ListFilter { data = key, timestamp = now }); } } } var tasks = new Queue <Task>(); using (var tasklimit = new SemaphoreSlim(MaxRefreshTasks)) { foreach (var filter in existingfilters) { await App.Database.ListFiltersCached.DeleteAsync(filter); } foreach (var filter in newfilters) { await tasklimit.WaitAsync(); tasks.Enqueue(Task.Run(async() => { await App.Database.ListFiltersCached.InsertOrUpdateAsync(filter); await Task.Yield(); await Task.Delay(RefreshDelay); tasklimit.Release(); })); while (tasks.Count > 0 && tasks.Peek().IsCompleted) { await tasks.Dequeue(); } } } await Task.WhenAll(tasks); if (App.Current.HaveNetwork) { await Task.Run(() => SyncWithServerAsync(false).ConfigureAwait(false)); } } finally { await App.Database.ListFiltersCached.EndDeferralAsync().ConfigureAwait(false); } }); })); }
private string IsInLookup(string data) { // Not possibly valid if (string.IsNullOrWhiteSpace(data)) { return(null); } var split = data.Split(new[] { ' ' }, 2); // Not supported if (split.Length != 2) { return(null); } switch (split[0]) { case "Tag": { if (!string.IsNullOrWhiteSpace(split[1])) { var tag = Ao3SiteDataLookup.LookupTagQuick(split[1], true); using (rwlock.ReadLock()) { if (tags.Contains(tag?.actual)) { return($"Tag {tag.actual}"); } } } return(null); } case "Author": { if (!string.IsNullOrWhiteSpace(split[1])) { using (rwlock.ReadLock()) { if (authors.Contains(split[1])) { return($"Author {split[1]}"); } } } return(null); } case "Work": { var s2 = split[1].Split(new[] { ' ' }, 2); if (s2.Length != 0 && long.TryParse(s2[0], out var id)) { using (rwlock.ReadLock()) { if (works.TryGetValue(id, out var value)) { return($"Work {id} {value ?? ""}".TrimEnd()); } } } return(null); } case "Series": { var s2 = split[1].Split(new[] { ' ' }, 2); if (s2.Length != 0 && long.TryParse(s2[0], out var id)) { using (rwlock.ReadLock()) { if (serieses.TryGetValue(id, out var value)) { return($"Series {id} {value ?? ""}".TrimEnd()); } } } return(null); } } return(null); }
private async Task <string> RemoveFromLookupAsync(string data) { // Not possibly valid if (string.IsNullOrWhiteSpace(data)) { return(null); } var split = data.Split(new[] { ' ' }, 2); // Not supported if (split.Length != 2) { return(null); } switch (split[0]) { case "Tag": { if (!string.IsNullOrWhiteSpace(split[1])) { var tag = await Ao3SiteDataLookup.LookupTagAsync(split[1]); lock (tags) { tags.Remove(tag.actual); return($"Tag {tag.actual}"); } } } break; case "Author": { if (!string.IsNullOrWhiteSpace(split[1])) { lock (authors) { authors.Remove(split[1]); return($"Author {split[1]}"); } } } break; case "Work": { var s2 = split[1].Split(new[] { ' ' }, 2); if (s2.Length != 0 && long.TryParse(s2[0], out var id)) { lock (works) { if (works.TryGetValue(id, out var value)) { works.Remove(id); return($"Work {id} {value ?? ""}".TrimEnd()); } } } } break; case "Series": { var s2 = split[1].Split(new[] { ' ' }, 2); if (s2.Length != 0 && long.TryParse(s2[0], out var id)) { lock (serieses) { if (serieses.TryGetValue(id, out var value)) { serieses.Remove(id); return($"Series {id} {value ?? ""}".TrimEnd()); } } } } break; } return(null); }
void RestoreReadingList() { Task.Factory.StartNew(async() => { await App.Database.ReadingListCached.BeginDeferralAsync(); try { // Restore the reading list contents! var items = new Dictionary <string, ReadingList>(); foreach (var i in await App.Database.ReadingListCached.SelectAsync()) { items[i.Uri] = i; } var tasks = new Queue <Task>(); using (var tasklimit = new SemaphoreSlim(MaxRefreshTasks)) { await wvp.DoOnMainThreadAsync(() => ListView.ItemsSource = readingListBacking); if (items.Count == 0) { tasks.Enqueue(AddAsyncImpl("http://archiveofourown.org/", DateTime.UtcNow.ToUnixTime())); } else { var timestamp = DateTime.UtcNow.ToUnixTime(); foreach (var item in items.Values) { var model = Ao3PageModel.Deserialize(item.Model) ?? Ao3SiteDataLookup.LookupQuick(item.Uri); if (model == null) { continue; } if (model.Uri.AbsoluteUri != item.Uri) { await App.Database.ReadingListCached.DeleteAsync(item.Uri); await App.Database.ReadingListCached.InsertOrUpdateAsync(new ReadingList(model, timestamp, item.Unread)); } if (readingListBacking.FindInAll((m) => m.Uri.AbsoluteUri == model.Uri.AbsoluteUri) is null) { var viewmodel = new Ao3PageViewModel(model.Uri, model.HasChapters ? item.Unread : (int?)null, model.Type == Ao3PageType.Work ? tagTypeVisible : null) { TagsVisible = tags_visible, Favourite = item.Favourite }; await tasklimit.WaitAsync(); tasks.Enqueue(Task.Run(async() => { await viewmodel.SetBaseDataAsync(model, false); if (!readingListBacking.Contains(viewmodel)) { await wvp.DoOnMainThreadAsync(() => { viewmodel.PropertyChanged += Viewmodel_PropertyChanged; readingListBacking.Add(viewmodel); }); } await RefreshAsync(viewmodel); await Task.Delay(RefreshDelay); tasklimit.Release(); })); } #pragma warning disable 4014 while (tasks.Count > 0 && tasks.Peek().IsCompleted) { tasks.Dequeue(); } #pragma warning restore 4014 } } } await wvp.DoOnMainThreadAsync(() => { restored.SetResult(true); SyncIndicator.Content = new ActivityIndicator() { IsVisible = IsOnScreen, IsRunning = IsOnScreen, IsEnabled = IsOnScreen }; App.Database.GetVariableEvents("LogFontSizeUI").Updated += LogFontSizeUI_Updated; }); GC.Collect(); // If we don't have network access, we wait till it's available if (!App.Current.HaveNetwork) { tcsNetworkAvailable = new TaskCompletionSource <bool>(); App.Current.HaveNetworkChanged += App_HaveNetworkChanged; await tcsNetworkAvailable.Task; } #pragma warning disable 4014 while (tasks.Count > 0 && tasks.Peek().IsCompleted) { tasks.Dequeue(); } #pragma warning restore 4014 await Task.WhenAll(tasks); await SyncToServerAsync(false, true); } finally { await App.Database.ReadingListCached.EndDeferralAsync().ConfigureAwait(false); } await wvp.DoOnMainThreadAsync(() => { RefreshButton.IsEnabled = true; SyncIndicator.Content = null; }).ConfigureAwait(false); }, TaskCreationOptions.PreferFairness | TaskCreationOptions.LongRunning); }