Search in sources :

Example 11 with ReadingListPage

use of org.wikipedia.readinglist.database.ReadingListPage in project apps-android-wikipedia by wikimedia.

the class ReadingListFragment method showDeleteItemsUndoSnackbar.

private void showDeleteItemsUndoSnackbar(final ReadingList readingList, final List<ReadingListPage> pages) {
    String message = pages.size() == 1 ? String.format(getString(R.string.reading_list_item_deleted), pages.get(0).title()) : String.format(getString(R.string.reading_list_items_deleted), pages.size());
    Snackbar snackbar = FeedbackUtil.makeSnackbar(getActivity(), message, FeedbackUtil.LENGTH_DEFAULT);
    snackbar.setAction(R.string.reading_list_item_delete_undo, v -> {
        List<ReadingListPage> newPages = new ArrayList<>();
        for (ReadingListPage page : pages) {
            newPages.add(new ReadingListPage(ReadingListPage.toPageTitle(page)));
        }
        ReadingListDbHelper.instance().addPagesToList(readingList, newPages, true);
        readingList.pages().addAll(newPages);
        updateReadingListData();
    });
    snackbar.show();
}
Also used : ArrayList(java.util.ArrayList) ReadingListPage(org.wikipedia.readinglist.database.ReadingListPage) Snackbar(android.support.design.widget.Snackbar)

Example 12 with ReadingListPage

use of org.wikipedia.readinglist.database.ReadingListPage in project apps-android-wikipedia by wikimedia.

the class ReadingListFragment method setSearchQuery.

private void setSearchQuery(@Nullable String query) {
    if (readingList == null) {
        return;
    }
    currentSearchQuery = query;
    displayedPages.clear();
    if (TextUtils.isEmpty(query)) {
        displayedPages.addAll(readingList.pages());
    } else {
        query = query.toUpperCase(Locale.getDefault());
        for (ReadingListPage page : readingList.pages()) {
            if (page.title().toUpperCase(Locale.getDefault()).contains(query.toUpperCase(Locale.getDefault()))) {
                displayedPages.add(page);
            }
        }
    }
    adapter.notifyDataSetChanged();
    updateEmptyState(query);
}
Also used : ReadingListPage(org.wikipedia.readinglist.database.ReadingListPage)

Example 13 with ReadingListPage

use of org.wikipedia.readinglist.database.ReadingListPage in project apps-android-wikipedia by wikimedia.

the class ReadingListHeaderView method updateThumbnails.

private void updateThumbnails() {
    if (readingList == null) {
        return;
    }
    clearThumbnails();
    List<String> thumbUrls = new ArrayList<>();
    for (ReadingListPage page : readingList.pages()) {
        if (!TextUtils.isEmpty(page.thumbUrl())) {
            thumbUrls.add(page.thumbUrl());
        }
        if (thumbUrls.size() > imageViews.size()) {
            break;
        }
    }
    for (int i = thumbUrls.size(); i < imageViews.size() && i < readingList.pages().size(); i++) {
        thumbUrls.add("");
    }
    for (int i = 0; i < thumbUrls.size() && i < imageViews.size(); ++i) {
        loadThumbnail(imageViews.get(i), thumbUrls.get(i));
    }
}
Also used : ArrayList(java.util.ArrayList) ReadingListPage(org.wikipedia.readinglist.database.ReadingListPage)

Example 14 with ReadingListPage

use of org.wikipedia.readinglist.database.ReadingListPage in project apps-android-wikipedia by wikimedia.

the class ReadingListSyncAdapter method createOrUpdatePage.

private void createOrUpdatePage(@NonNull ReadingList listForPage, @NonNull RemoteReadingListEntry remotePage) {
    PageTitle remoteTitle = pageTitleFromRemoteEntry(remotePage);
    ReadingListPage localPage = null;
    boolean updateOnly = false;
    for (ReadingListPage page : listForPage.pages()) {
        if (ReadingListPage.toPageTitle(page).equals(remoteTitle)) {
            localPage = page;
            updateOnly = true;
            break;
        }
    }
    if (localPage == null) {
        localPage = new ReadingListPage(pageTitleFromRemoteEntry(remotePage));
        localPage.listId(listForPage.id());
        if (ReadingListDbHelper.instance().pageExistsInList(listForPage, remoteTitle)) {
            updateOnly = true;
        }
    }
    localPage.remoteId(remotePage.id());
    if (remotePage.summary() != null) {
        localPage.description(remotePage.summary().getDescription());
        localPage.thumbUrl(remotePage.summary().getThumbnailUrl());
    }
    if (updateOnly) {
        L.d("Updating local page " + localPage.title());
        ReadingListDbHelper.instance().updatePage(localPage);
    } else {
        L.d("Creating local page " + localPage.title());
        ReadingListDbHelper.instance().addPagesToList(listForPage, Collections.singletonList(localPage), false);
    }
}
Also used : PageTitle(org.wikipedia.page.PageTitle) ReadingListPage(org.wikipedia.readinglist.database.ReadingListPage)

Example 15 with ReadingListPage

use of org.wikipedia.readinglist.database.ReadingListPage in project apps-android-wikipedia by wikimedia.

the class ReadingListSyncAdapter method onPerformSync.

@SuppressWarnings("methodlength")
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
    if (isDisabledByRemoteConfig() || !AccountUtil.isLoggedIn() || !(Prefs.isReadingListSyncEnabled() || Prefs.shouldShowReadingListSyncMergePrompt() || Prefs.isReadingListsRemoteDeletePending())) {
        L.d("Skipping sync of reading lists.");
        if (extras.containsKey(SYNC_EXTRAS_REFRESHING)) {
            SavedPageSyncService.sendSyncEvent();
        }
        return;
    }
    L.d("Begin sync of reading lists...");
    List<String> csrfToken = new ArrayList<>();
    Set<Long> listIdsDeleted = Prefs.getReadingListsDeletedIds();
    Set<String> pageIdsDeleted = Prefs.getReadingListPagesDeletedIds();
    List<ReadingList> allLocalLists = null;
    WikiSite wiki = WikipediaApp.getInstance().getWikiSite();
    ReadingListClient client = new ReadingListClient(wiki);
    ReadingListSyncNotification readingListSyncNotification = ReadingListSyncNotification.getInstance();
    String lastSyncTime = Prefs.getReadingListsLastSyncTime();
    boolean shouldSendSyncEvent = extras.containsKey(SYNC_EXTRAS_REFRESHING);
    boolean shouldRetry = false;
    boolean shouldRetryWithForce = false;
    try {
        IN_PROGRESS = true;
        boolean syncEverything = false;
        if (extras.containsKey(SYNC_EXTRAS_FORCE_FULL_SYNC) || Prefs.isReadingListsRemoteDeletePending() || Prefs.isReadingListsRemoteSetupPending()) {
            // reset the remote ID on all lists, since they will need to be recreated next time.
            L.d("Resetting all lists to un-synced.");
            syncEverything = true;
            ReadingListDbHelper.instance().markEverythingUnsynced();
            allLocalLists = ReadingListDbHelper.instance().getAllLists();
        }
        if (Prefs.isReadingListsRemoteDeletePending()) {
            // Are we scheduled for a teardown? If so, delete everything and bail.
            L.d("Tearing down remote lists...");
            client.tearDown(getCsrfToken(wiki, csrfToken));
            Prefs.setReadingListsRemoteDeletePending(false);
            return;
        } else if (Prefs.isReadingListsRemoteSetupPending()) {
            // ...Or are we scheduled for setup?
            if (client.setup(getCsrfToken(wiki, csrfToken))) {
                // Set up for the first time, which means that we don't need to ask whether
                // the user wants to merge local lists.
                Prefs.shouldShowReadingListSyncMergePrompt(false);
            }
            Prefs.setReadingListsRemoteSetupPending(false);
        }
        if (Prefs.shouldShowReadingListSyncMergePrompt()) {
            Prefs.shouldShowReadingListSyncMergePrompt(false);
            for (ReadingList list : allLocalLists) {
                for (ReadingListPage page : list.pages()) {
                    if (page.remoteId() <= 0) {
                        // At least one page in our collection is not synced.
                        // We therefore need to ask the user if we want to merge unsynced pages
                        // with the remote collection, or delete them.
                        // However, let's issue a request to the changes endpoint, so that
                        // it can throw an exception if lists are not set up for the user.
                        client.getChangesSince(DateUtil.getIso8601DateFormat().format(new Date()));
                        // Exception wasn't thrown, so post the bus event.
                        WikipediaApp.getInstance().getBus().post(new ReadingListsMergeLocalDialogEvent());
                        return;
                    }
                }
            }
        }
        // -----------------------------------------------
        // PHASE 1: Sync from remote to local.
        // -----------------------------------------------
        List<RemoteReadingList> remoteListsModified = Collections.emptyList();
        List<RemoteReadingListEntry> remoteEntriesModified = Collections.emptyList();
        if (TextUtils.isEmpty(lastSyncTime)) {
            syncEverything = true;
        }
        if (syncEverything) {
            if (allLocalLists == null) {
                allLocalLists = ReadingListDbHelper.instance().getAllLists();
            }
        } else {
            if (allLocalLists == null) {
                allLocalLists = ReadingListDbHelper.instance().getAllListsWithUnsyncedPages();
            }
            L.d("Fetching changes from server, since " + lastSyncTime);
            SyncedReadingLists allChanges = client.getChangesSince(lastSyncTime);
            if (allChanges.getLists() != null) {
                remoteListsModified = allChanges.getLists();
            }
            if (allChanges.getEntries() != null) {
                remoteEntriesModified = allChanges.getEntries();
            }
        }
        // Perform a quick check for whether we'll need to sync all lists
        for (RemoteReadingListEntry remoteEntry : remoteEntriesModified) {
            // find the list to which this entry belongs...
            ReadingList eigenLocalList = null;
            RemoteReadingList eigenRemoteList = null;
            for (ReadingList localList : allLocalLists) {
                if (localList.remoteId() == remoteEntry.listId()) {
                    eigenLocalList = localList;
                    break;
                }
            }
            for (RemoteReadingList remoteList : remoteListsModified) {
                if (remoteList.id() == remoteEntry.listId()) {
                    eigenRemoteList = remoteList;
                    break;
                }
            }
            if (eigenLocalList == null && eigenRemoteList == null) {
                L.w("Remote entry belongs to an unknown local list. Falling back to full sync.");
                syncEverything = true;
                break;
            }
        }
        if (syncEverything) {
            allLocalLists = ReadingListDbHelper.instance().getAllLists();
            L.d("Fetching all lists from server...");
            remoteListsModified = client.getAllLists();
        }
        // Notify any event consumers that reading lists are, in fact, enabled.
        WikipediaApp.getInstance().getBus().post(new ReadingListsEnabledStatusEvent());
        // setup syncing indicator for remote to local
        int remoteItemsTotal = remoteListsModified.size();
        int remoteItemsSynced = 0;
        // First, update our list hierarchy to match the remote hierarchy.
        for (RemoteReadingList remoteList : remoteListsModified) {
            readingListSyncNotification.setNotificationProgress(getContext(), remoteItemsTotal, remoteItemsSynced++);
            // Find the remote list in our local lists...
            ReadingList localList = null;
            boolean upsertNeeded = false;
            for (ReadingList list : allLocalLists) {
                if (list.isDefault() && remoteList.isDefault()) {
                    localList = list;
                    if (list.remoteId() != remoteList.id()) {
                        list.remoteId(remoteList.id());
                        upsertNeeded = true;
                    }
                    break;
                }
                if (list.remoteId() == remoteList.id()) {
                    localList = list;
                    break;
                } else if (StringUtil.normalizedEquals(list.title(), remoteList.name())) {
                    localList = list;
                    localList.remoteId(remoteList.id());
                    upsertNeeded = true;
                    break;
                }
            }
            if (remoteList.isDefault() && localList != null && !localList.isDefault()) {
                L.logRemoteError(new RuntimeException("Unexpected: remote default list corresponds to local non-default list."));
                localList = ReadingListDbHelper.instance().getDefaultList();
            }
            if (remoteList.isDeleted()) {
                if (localList != null && !localList.isDefault()) {
                    L.d("Deleting local list " + localList.title());
                    ReadingListDbHelper.instance().deleteList(localList, false);
                    ReadingListDbHelper.instance().markPagesForDeletion(localList, localList.pages(), false);
                    allLocalLists.remove(localList);
                    shouldSendSyncEvent = true;
                }
                continue;
            }
            if (localList == null) {
                // A new list needs to be created locally.
                L.d("Creating local list " + remoteList.name());
                if (remoteList.isDefault()) {
                    L.logRemoteError(new RuntimeException("Unexpected: local default list no longer matches remote."));
                    localList = ReadingListDbHelper.instance().getDefaultList();
                } else {
                    localList = ReadingListDbHelper.instance().createList(remoteList.name(), remoteList.description());
                }
                localList.remoteId(remoteList.id());
                allLocalLists.add(localList);
                upsertNeeded = true;
            } else {
                if (!localList.isDefault() && !StringUtil.normalizedEquals(localList.title(), remoteList.name())) {
                    localList.title(remoteList.name());
                    upsertNeeded = true;
                }
                if (!localList.isDefault() && !StringUtil.normalizedEquals(localList.description(), remoteList.description())) {
                    localList.description(remoteList.description());
                    upsertNeeded = true;
                }
            }
            if (upsertNeeded) {
                L.d("Updating info for local list " + localList.title());
                localList.dirty(false);
                ReadingListDbHelper.instance().updateList(localList, false);
                shouldSendSyncEvent = true;
            }
            if (syncEverything) {
                L.d("Fetching all pages in remote list " + remoteList.name());
                List<RemoteReadingListEntry> remoteEntries = client.getListEntries(remoteList.id());
                for (RemoteReadingListEntry remoteEntry : remoteEntries) {
                    // TODO: optimization opportunity -- create/update local pages in bulk.
                    createOrUpdatePage(localList, remoteEntry);
                }
                shouldSendSyncEvent = true;
            }
        }
        if (!syncEverything) {
            for (RemoteReadingListEntry remoteEntry : remoteEntriesModified) {
                // find the list to which this entry belongs...
                ReadingList eigenList = null;
                for (ReadingList localList : allLocalLists) {
                    if (localList.remoteId() == remoteEntry.listId()) {
                        eigenList = localList;
                        break;
                    }
                }
                if (eigenList == null) {
                    L.w("Remote entry belongs to an unknown local list.");
                    continue;
                }
                shouldSendSyncEvent = true;
                if (remoteEntry.isDeleted()) {
                    deletePageByTitle(eigenList, pageTitleFromRemoteEntry(remoteEntry));
                } else {
                    createOrUpdatePage(eigenList, remoteEntry);
                }
            }
        }
        // -----------------------------------------------
        // PHASE 2: Sync from local to remote.
        // -----------------------------------------------
        // Do any remote lists need to be deleted?
        List<Long> listIdsToDelete = new ArrayList<>();
        listIdsToDelete.addAll(listIdsDeleted);
        for (Long id : listIdsToDelete) {
            L.d("Deleting remote list id " + id);
            try {
                client.deleteList(getCsrfToken(wiki, csrfToken), id);
            } catch (Throwable t) {
                L.w(t);
                if (!client.isServiceError(t) && !client.isUnavailableError(t)) {
                    throw t;
                }
            }
            listIdsDeleted.remove(id);
        }
        // Do any remote pages need to be deleted?
        List<String> pageIdsToDelete = new ArrayList<>();
        pageIdsToDelete.addAll(pageIdsDeleted);
        for (String id : pageIdsToDelete) {
            L.d("Deleting remote page id " + id);
            String[] listAndPageId = id.split(":");
            try {
                // TODO: optimization opportunity once server starts supporting batch deletes.
                client.deletePageFromList(getCsrfToken(wiki, csrfToken), Long.parseLong(listAndPageId[0]), Long.parseLong(listAndPageId[1]));
            } catch (Throwable t) {
                L.w(t);
                if (!client.isServiceError(t) && !client.isUnavailableError(t)) {
                    throw t;
                }
            }
            pageIdsDeleted.remove(id);
        }
        // setup syncing indicator for local to remote
        int localItemsTotal = allLocalLists.size();
        int localItemsSynced = 0;
        // Determine whether any remote lists need to be created or updated
        for (ReadingList localList : allLocalLists) {
            readingListSyncNotification.setNotificationProgress(getContext(), localItemsTotal, localItemsSynced++);
            RemoteReadingList remoteList = new RemoteReadingList(localList.title(), localList.description());
            boolean upsertNeeded = false;
            if (localList.remoteId() > 0) {
                if (!localList.isDefault() && localList.dirty()) {
                    // Update remote metadata for this list.
                    L.d("Updating info for remote list " + remoteList.name());
                    client.updateList(getCsrfToken(wiki, csrfToken), localList.remoteId(), remoteList);
                    upsertNeeded = true;
                }
            } else if (!localList.isDefault()) {
                // This list needs to be created remotely.
                L.d("Creating remote list " + remoteList.name());
                long id = client.createList(getCsrfToken(wiki, csrfToken), remoteList);
                localList.remoteId(id);
                upsertNeeded = true;
            }
            if (upsertNeeded) {
                localList.dirty(false);
                ReadingListDbHelper.instance().updateList(localList, false);
            }
        }
        for (ReadingList localList : allLocalLists) {
            List<ReadingListPage> localPages = new ArrayList<>();
            List<RemoteReadingListEntry> newEntries = new ArrayList<>();
            for (ReadingListPage localPage : localList.pages()) {
                if (localPage.remoteId() < 1) {
                    localPages.add(localPage);
                    newEntries.add(remoteEntryFromLocalPage(localPage));
                }
            }
            if (newEntries.isEmpty()) {
                continue;
            }
            try {
                if (localPages.size() == 1) {
                    L.d("Creating new remote page " + localPages.get(0).title());
                    localPages.get(0).remoteId(client.addPageToList(getCsrfToken(wiki, csrfToken), localList.remoteId(), newEntries.get(0)));
                    ReadingListDbHelper.instance().updatePage(localPages.get(0));
                } else {
                    L.d("Creating " + newEntries.size() + " new remote pages");
                    List<Long> ids = client.addPagesToList(getCsrfToken(wiki, csrfToken), localList.remoteId(), newEntries);
                    for (int i = 0; i < ids.size(); i++) {
                        localPages.get(i).remoteId(ids.get(i));
                    }
                    ReadingListDbHelper.instance().updatePages(localPages);
                }
            } catch (Throwable t) {
                // so let's force a full sync.
                if (client.isErrorType(t, "duplicate-page")) {
                    shouldRetryWithForce = true;
                    break;
                } else if (client.isErrorType(t, "entry-limit")) {
                // TODO: handle more meaningfully than ignoring, for now.
                } else {
                    throw t;
                }
            }
        }
    } catch (Throwable t) {
        if (client.isErrorType(t, "not-set-up")) {
            Prefs.setReadingListSyncEnabled(false);
            if (TextUtils.isEmpty(lastSyncTime)) {
                // This means that it's our first time attempting to sync, and we see that
                // syncing isn't enabled on the server. So, let's prompt the user to enable it:
                WikipediaApp.getInstance().getBus().post(new ReadingListsEnableDialogEvent());
            } else {
                // This can only mean that our reading lists have been torn down (disabled) by
                // another client, so we need to notify the user of this development.
                WikipediaApp.getInstance().getBus().post(new ReadingListsNoLongerSyncedEvent());
            }
        }
        if (client.isErrorType(t, "notloggedin")) {
            try {
                L.d("Server doesn't believe we're logged in, so logging in...");
                getCsrfToken(wiki, csrfToken);
                shouldRetry = true;
            } catch (Throwable caught) {
                t = caught;
            }
        }
        L.w(t);
    } finally {
        lastSyncTime = getLastDateFromHeader(lastSyncTime, client);
        Prefs.setReadingListsLastSyncTime(lastSyncTime);
        Prefs.setReadingListsDeletedIds(listIdsDeleted);
        Prefs.setReadingListPagesDeletedIds(pageIdsDeleted);
        readingListSyncNotification.cancelNotification(getContext());
        if (shouldSendSyncEvent) {
            SavedPageSyncService.sendSyncEvent();
        }
        if ((shouldRetry || shouldRetryWithForce) && !extras.containsKey(SYNC_EXTRAS_RETRYING)) {
            Bundle b = new Bundle();
            b.putAll(extras);
            b.putBoolean(SYNC_EXTRAS_RETRYING, true);
            if (shouldRetryWithForce) {
                b.putBoolean(SYNC_EXTRAS_FORCE_FULL_SYNC, true);
            }
            manualSync(b);
        }
        IN_PROGRESS = false;
        SavedPageSyncService.enqueue();
        L.d("Finished sync of reading lists.");
    }
}
Also used : ReadingListsNoLongerSyncedEvent(org.wikipedia.events.ReadingListsNoLongerSyncedEvent) ReadingListsEnabledStatusEvent(org.wikipedia.events.ReadingListsEnabledStatusEvent) ArrayList(java.util.ArrayList) WikiSite(org.wikipedia.dataclient.WikiSite) RemoteReadingList(org.wikipedia.readinglist.sync.SyncedReadingLists.RemoteReadingList) ReadingListsEnableDialogEvent(org.wikipedia.events.ReadingListsEnableDialogEvent) ReadingListPage(org.wikipedia.readinglist.database.ReadingListPage) RemoteReadingListEntry(org.wikipedia.readinglist.sync.SyncedReadingLists.RemoteReadingListEntry) ReadingListsMergeLocalDialogEvent(org.wikipedia.events.ReadingListsMergeLocalDialogEvent) Bundle(android.os.Bundle) Date(java.util.Date) ReadingList(org.wikipedia.readinglist.database.ReadingList) RemoteReadingList(org.wikipedia.readinglist.sync.SyncedReadingLists.RemoteReadingList)

Aggregations

ReadingListPage (org.wikipedia.readinglist.database.ReadingListPage)18 ArrayList (java.util.ArrayList)8 PageTitle (org.wikipedia.page.PageTitle)5 ReadingList (org.wikipedia.readinglist.database.ReadingList)3 Snackbar (android.support.design.widget.Snackbar)2 IOException (java.io.IOException)2 Bundle (android.os.Bundle)1 NonNull (android.support.annotation.NonNull)1 Nullable (android.support.annotation.Nullable)1 OnClick (butterknife.OnClick)1 Date (java.util.Date)1 HashSet (java.util.HashSet)1 CallbackTask (org.wikipedia.concurrency.CallbackTask)1 WikiSite (org.wikipedia.dataclient.WikiSite)1 ReadingListsEnableDialogEvent (org.wikipedia.events.ReadingListsEnableDialogEvent)1 ReadingListsEnabledStatusEvent (org.wikipedia.events.ReadingListsEnabledStatusEvent)1 ReadingListsMergeLocalDialogEvent (org.wikipedia.events.ReadingListsMergeLocalDialogEvent)1 ReadingListsNoLongerSyncedEvent (org.wikipedia.events.ReadingListsNoLongerSyncedEvent)1 ReadingListBookmarkMenu (org.wikipedia.readinglist.ReadingListBookmarkMenu)1 RemoteReadingList (org.wikipedia.readinglist.sync.SyncedReadingLists.RemoteReadingList)1