use of org.wikipedia.dataclient.WikiSite 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.");
}
}
use of org.wikipedia.dataclient.WikiSite in project apps-android-wikipedia by wikimedia.
the class LinkHandler method onUrlClick.
@Override
public void onUrlClick(@NonNull String href, @Nullable String titleString) {
if (href.startsWith("//")) {
// for URLs without an explicit scheme, add our default scheme explicitly.
href = getWikiSite().scheme() + ":" + href;
}
Uri uri = Uri.parse(href);
boolean knownScheme = false;
for (String scheme : KNOWN_SCHEMES) {
if (href.startsWith(scheme + ":")) {
knownScheme = true;
}
}
if (!knownScheme) {
// for URLs without a known scheme, add our default scheme explicitly.
uri = uri.buildUpon().scheme(getWikiSite().scheme()).authority(getWikiSite().authority()).path(href).build();
}
Log.d("Wikipedia", "Link clicked was " + uri.toString());
if (!TextUtils.isEmpty(uri.getPath()) && WikiSite.supportedAuthority(uri.getAuthority()) && (uri.getPath().startsWith("/wiki/") || uri.getPath().startsWith("/zh-"))) {
WikiSite site = new WikiSite(uri);
if (site.subdomain().equals(getWikiSite().subdomain()) && !site.languageCode().equals(getWikiSite().languageCode())) {
// override the languageCode from the parent WikiSite, in case it's a variant.
site = new WikiSite(uri.getAuthority(), getWikiSite().languageCode());
}
PageTitle title = TextUtils.isEmpty(titleString) ? site.titleForInternalLink(uri.getPath()) : PageTitle.withSeparateFragment(titleString, uri.getFragment(), site);
onInternalLinkClicked(title);
} else if (!TextUtils.isEmpty(titleString) && UriUtil.isValidOfflinePageLink(uri)) {
WikiSite site = new WikiSite(uri);
PageTitle title = PageTitle.withSeparateFragment(titleString, uri.getFragment(), site);
onInternalLinkClicked(title);
} else if (!TextUtils.isEmpty(uri.getAuthority()) && WikiSite.supportedAuthority(uri.getAuthority()) && !TextUtils.isEmpty(uri.getFragment())) {
onPageLinkClicked(uri.getFragment());
} else {
onExternalLinkClicked(uri);
}
}
use of org.wikipedia.dataclient.WikiSite in project apps-android-wikipedia by wikimedia.
the class PageActivity method handleIntent.
private void handleIntent(@NonNull Intent intent) {
if (Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() != null) {
WikiSite wiki = new WikiSite(intent.getData());
PageTitle title = wiki.titleForUri(intent.getData());
HistoryEntry historyEntry = new HistoryEntry(title, HistoryEntry.SOURCE_EXTERNAL_LINK);
loadPageInForegroundTab(title, historyEntry);
} else if (ACTION_LOAD_IN_NEW_TAB.equals(intent.getAction()) || ACTION_LOAD_IN_CURRENT_TAB.equals(intent.getAction())) {
PageTitle title = intent.getParcelableExtra(EXTRA_PAGETITLE);
HistoryEntry historyEntry = intent.getParcelableExtra(EXTRA_HISTORYENTRY);
if (ACTION_LOAD_IN_NEW_TAB.equals(intent.getAction())) {
loadPageInForegroundTab(title, historyEntry);
} else if (ACTION_LOAD_IN_CURRENT_TAB.equals(intent.getAction())) {
loadPage(title, historyEntry, TabPosition.CURRENT_TAB);
}
if (intent.hasExtra(Constants.INTENT_EXTRA_REVERT_QNUMBER)) {
showDescriptionEditRevertDialog(intent.getStringExtra(Constants.INTENT_EXTRA_REVERT_QNUMBER));
}
} else if (ACTION_LOAD_FROM_EXISTING_TAB.equals(intent.getAction())) {
PageTitle title = intent.getParcelableExtra(EXTRA_PAGETITLE);
HistoryEntry historyEntry = intent.getParcelableExtra(EXTRA_HISTORYENTRY);
loadPage(title, historyEntry, TabPosition.EXISTING_TAB);
} else if (ACTION_SHOW_TAB_LIST.equals(intent.getAction()) || ACTION_RESUME_READING.equals(intent.getAction()) || intent.hasExtra(Constants.INTENT_APP_SHORTCUT_CONTINUE_READING)) {
// do nothing, since this will be handled indirectly by PageFragment.
} else if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
PageTitle title = new PageTitle(query, app.getWikiSite());
HistoryEntry historyEntry = new HistoryEntry(title, HistoryEntry.SOURCE_SEARCH);
loadPageInForegroundTab(title, historyEntry);
} else if (intent.hasExtra(Constants.INTENT_FEATURED_ARTICLE_FROM_WIDGET)) {
new IntentFunnel(app).logFeaturedArticleWidgetTap();
loadMainPageInForegroundTab();
} else {
loadMainPageInCurrentTab();
}
}
use of org.wikipedia.dataclient.WikiSite in project apps-android-wikipedia by wikimedia.
the class PageFragment method setupMessageHandlers.
private void setupMessageHandlers() {
linkHandler = new LinkHandler(getActivity()) {
@Override
public void onPageLinkClicked(@NonNull String anchor) {
dismissBottomSheet();
JSONObject payload = new JSONObject();
try {
payload.put("anchor", anchor);
} catch (JSONException e) {
throw new RuntimeException(e);
}
bridge.sendMessage("handleReference", payload);
}
@Override
public void onInternalLinkClicked(@NonNull PageTitle title) {
handleInternalLink(title);
}
@Override
public WikiSite getWikiSite() {
return model.getTitle().getWikiSite();
}
};
bridge.addListener("linkClicked", linkHandler);
bridge.addListener("referenceClicked", new ReferenceHandler() {
@Override
protected void onReferenceClicked(@NonNull String refHtml, @Nullable String refLinkText) {
if (!isAdded()) {
Log.d("PageFragment", "Detached from activity, so stopping reference click.");
return;
}
showBottomSheet(new ReferenceDialog(getActivity(), linkHandler, refHtml, StringUtils.defaultString(refLinkText)));
}
});
bridge.addListener("imageClicked", (String messageType, JSONObject messagePayload) -> {
try {
String href = decodeURL(messagePayload.getString("href"));
if (href.startsWith("/wiki/")) {
String filename = UriUtil.removeInternalLinkPrefix(href);
WikiSite wiki = model.getTitle().getWikiSite();
getActivity().startActivityForResult(GalleryActivity.newIntent(getActivity(), model.getTitleOriginal(), filename, wiki, GalleryFunnel.SOURCE_NON_LEAD_IMAGE), Constants.ACTIVITY_REQUEST_GALLERY);
} else {
linkHandler.onUrlClick(href, messagePayload.optString("title"));
}
} catch (JSONException e) {
L.logRemoteErrorIfProd(e);
}
});
bridge.addListener("mediaClicked", (String messageType, JSONObject messagePayload) -> {
try {
String href = decodeURL(messagePayload.getString("href"));
String filename = StringUtil.removeUnderscores(UriUtil.removeInternalLinkPrefix(href));
WikiSite wiki = model.getTitle().getWikiSite();
getActivity().startActivityForResult(GalleryActivity.newIntent(getActivity(), model.getTitleOriginal(), filename, wiki, GalleryFunnel.SOURCE_NON_LEAD_IMAGE), Constants.ACTIVITY_REQUEST_GALLERY);
} catch (JSONException e) {
L.logRemoteErrorIfProd(e);
}
});
}
use of org.wikipedia.dataclient.WikiSite in project apps-android-wikipedia by wikimedia.
the class PageImageDatabaseTable method fromCursor.
@Override
public PageImage fromCursor(Cursor cursor) {
WikiSite wiki = new WikiSite(Col.SITE.val(cursor), Col.LANG.val(cursor));
PageTitle title = new PageTitle(Col.NAMESPACE.val(cursor), Col.TITLE.val(cursor), wiki);
String imageName = Col.IMAGE_NAME.val(cursor);
return new PageImage(title, imageName);
}
Aggregations