Пример #1
0
        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();
            });
        }
Пример #2
0
        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("");
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        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);
        }
Пример #5
0
        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;
            }));
        }
Пример #6
0
        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;
            }));
        }
Пример #7
0
        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;
            }));
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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));
            }
        }
Пример #11
0
        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);
            }
        }
Пример #12
0
        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?
        }
Пример #13
0
        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);
                    }
                });
            }));
        }
Пример #14
0
        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);
        }
Пример #15
0
        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);
        }
Пример #16
0
        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);
        }