jenkins-bot has submitted this change and it was merged.
Change subject: Implement background saved page syncing
......................................................................
Implement background saved page syncing
Kicks off a service on app startup to check with the new Reading List
pages DAO for added or deleted entries. Handles adding or deleting
accordingly.
Bug: T126753
Change-Id: Iae8e9e19fa4d774f590a85f238b1510bb5642878
---
M app/src/main/AndroidManifest.xml
M app/src/main/java/org/wikipedia/WikipediaApp.java
M app/src/main/java/org/wikipedia/database/AppContentProvider.java
M app/src/main/java/org/wikipedia/database/DatabaseClient.java
M app/src/main/java/org/wikipedia/page/Namespace.java
M app/src/main/java/org/wikipedia/page/PageProperties.java
A app/src/main/java/org/wikipedia/savedpages/ReadingListPageObserver.java
A app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
M app/src/main/java/org/wikipedia/server/PageService.java
M app/src/main/java/org/wikipedia/server/mwapi/MwPageService.java
M app/src/main/java/org/wikipedia/server/restbase/RbPageService.java
A app/src/main/java/org/wikipedia/util/DateUtil.java
M app/src/main/java/org/wikipedia/util/FileUtil.java
13 files changed, 343 insertions(+), 19 deletions(-)
Approvals:
Niedzielski: Looks good to me, approved
jenkins-bot: Verified
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7f80339..d6c2bcc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -235,5 +235,7 @@
<service
android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService" />
+ <service android:name=".savedpages.SavedPageSyncService" />
+
</application>
</manifest>
diff --git a/app/src/main/java/org/wikipedia/WikipediaApp.java
b/app/src/main/java/org/wikipedia/WikipediaApp.java
index 59e6390..ee61928 100644
--- a/app/src/main/java/org/wikipedia/WikipediaApp.java
+++ b/app/src/main/java/org/wikipedia/WikipediaApp.java
@@ -3,6 +3,8 @@
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.IntRange;
@@ -22,6 +24,7 @@
import org.wikipedia.crash.hockeyapp.HockeyAppCrashReporter;
import org.wikipedia.database.Database;
import org.wikipedia.database.DatabaseClient;
+import org.wikipedia.database.contract.ReadingListPageContract;
import org.wikipedia.editing.EditTokenStorage;
import org.wikipedia.editing.summaries.EditSummary;
import org.wikipedia.events.ChangeTextSizeEvent;
@@ -39,6 +42,7 @@
import org.wikipedia.readinglist.page.database.ReadingListPageHttpRow;
import org.wikipedia.readinglist.page.database.disk.ReadingListPageDiskRow;
import org.wikipedia.savedpages.SavedPage;
+import org.wikipedia.savedpages.ReadingListPageObserver;
import org.wikipedia.search.RecentSearch;
import org.wikipedia.settings.Prefs;
import org.wikipedia.theme.Theme;
@@ -51,14 +55,11 @@
import org.wikipedia.util.log.L;
import org.wikipedia.zero.WikipediaZeroHandler;
-import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Random;
-import java.util.TimeZone;
import java.util.UUID;
import retrofit.RequestInterceptor;
@@ -76,6 +77,8 @@
public static final int PREFERRED_THUMB_SIZE = 320;
+ public static final String FROM_READING_LIST_PAGE_OBSERVER =
"fromReadingListPageObserver";
+
private final RemoteConfig remoteConfig = new RemoteConfig();
private final UserInfoStorage userInfoStorage = new UserInfoStorage();
private final Map<Class<?>, DatabaseClient<?>> databaseClients =
Collections.synchronizedMap(new HashMap<Class<?>, DatabaseClient<?>>());
@@ -83,6 +86,7 @@
private AppLanguageState appLanguageState;
private FunnelManager funnelManager;
private SessionFunnel sessionFunnel;
+ private ContentObserver readingListPageObserver;
private Database database;
private EditTokenStorage editTokenStorage;
@@ -183,6 +187,7 @@
AccountUtil.createAccountForLoggedInUser();
UserOptionContentResolver.registerAppSyncObserver(this);
+ registerReadingListPageObserver();
}
public Bus getBus() {
@@ -311,6 +316,11 @@
@Nullable
public String getAppLanguageCanonicalName(String code) {
return appLanguageState.getAppLanguageCanonicalName(code);
+ }
+
+ @NonNull
+ public ContentObserver getReadingListPageObserver() {
+ return readingListPageObserver;
}
public Database getDatabase() {
@@ -521,12 +531,6 @@
return PrefsOnboardingStateMachine.getInstance();
}
- public SimpleDateFormat getSimpleDateFormat() {
- SimpleDateFormat simpleDateFormat = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT);
- simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- return simpleDateFormat;
- }
-
/** For Retrofit requests. Keep in sync with #buildCustomHeaders */
public void injectCustomHeaders(RequestInterceptor.RequestFacade request,
Site site) {
Map<String, String> headers =
buildCustomHeaders(getAcceptLanguage(site));
@@ -583,4 +587,14 @@
}
return result;
}
+
+ private void registerReadingListPageObserver() {
+ readingListPageObserver = new ReadingListPageObserver(null);
+ Uri readingListPageBaseUri = ReadingListPageContract.Disk.URI;
+ Uri uriWithQuery = readingListPageBaseUri.buildUpon()
+ .appendQueryParameter(FROM_READING_LIST_PAGE_OBSERVER,
"false").build();
+ WikipediaApp.getInstance().getContentResolver()
+ .registerContentObserver(uriWithQuery, true,
readingListPageObserver);
+ L.i("Registered reading list page observer");
+ }
}
diff --git a/app/src/main/java/org/wikipedia/database/AppContentProvider.java
b/app/src/main/java/org/wikipedia/database/AppContentProvider.java
index 240441d..85db4be 100644
--- a/app/src/main/java/org/wikipedia/database/AppContentProvider.java
+++ b/app/src/main/java/org/wikipedia/database/AppContentProvider.java
@@ -11,6 +11,7 @@
import android.support.annotation.Nullable;
import org.wikipedia.WikipediaApp;
+import org.wikipedia.database.contract.ReadingListPageContract;
import org.wikipedia.util.log.L;
import java.util.Arrays;
@@ -83,6 +84,11 @@
SQLiteDatabase db = writableDatabase();
int rows = db.delete(endpoint.tables(), selection, selectionArgs);
+ if (uri.equals(ReadingListPageContract.Page.URI)) {
+ uri = uri.buildUpon()
+
.appendQueryParameter(WikipediaApp.FROM_READING_LIST_PAGE_OBSERVER, "true")
+ .build();
+ }
notifyChange(uri);
return rows;
}
@@ -99,9 +105,11 @@
}
private void notifyChange(@NonNull Uri uri) {
- if (getContentResolver() != null) {
- getContentResolver().notifyChange(uri, null);
+ boolean notify =
uri.getBooleanQueryParameter(WikipediaApp.FROM_READING_LIST_PAGE_OBSERVER,
true);
+ if (getContentResolver() == null || !notify) {
+ return;
}
+ getContentResolver().notifyChange(uri, null);
}
@Nullable private ContentResolver getContentResolver() {
diff --git a/app/src/main/java/org/wikipedia/database/DatabaseClient.java
b/app/src/main/java/org/wikipedia/database/DatabaseClient.java
index a4e7b2b..ea9c348 100644
--- a/app/src/main/java/org/wikipedia/database/DatabaseClient.java
+++ b/app/src/main/java/org/wikipedia/database/DatabaseClient.java
@@ -9,6 +9,9 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.readinglist.page.ReadingListPage;
+
public class DatabaseClient<T> {
@NonNull private final ContentProviderClient client;
@NonNull private final DatabaseTable<T> databaseTable;
@@ -26,7 +29,13 @@
public void persist(T obj) {
try {
- client.insert(uri(), toContentValues(obj));
+ Uri uri = uri();
+ if
(ReadingListPage.DATABASE_TABLE.getBaseContentURI().equals(uri)) {
+ uri = uri.buildUpon()
+
.appendQueryParameter(WikipediaApp.FROM_READING_LIST_PAGE_OBSERVER, "true")
+ .build();
+ }
+ client.insert(uri, toContentValues(obj));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
diff --git a/app/src/main/java/org/wikipedia/page/Namespace.java
b/app/src/main/java/org/wikipedia/page/Namespace.java
index 7fe41ac..900f337 100644
--- a/app/src/main/java/org/wikipedia/page/Namespace.java
+++ b/app/src/main/java/org/wikipedia/page/Namespace.java
@@ -1,10 +1,12 @@
package org.wikipedia.page;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import org.wikipedia.model.CodeEnum;
import org.wikipedia.model.EnumCode;
import org.wikipedia.model.EnumCodeMap;
+import org.wikipedia.util.StringUtil;
// https://en.wikipedia.org/wiki/Wikipedia:Namespace
// https://www.mediawiki.org/wiki/Extension_default_namespaces
@@ -63,6 +65,15 @@
private final int code;
+ @Nullable
+ public String toLegacyString() {
+ String string = this == MAIN ? null : this.name();
+ if (string != null) {
+ StringUtil.capitalizeFirstChar(string.toLowerCase());
+ }
+ return string;
+ }
+
@NonNull
public static Namespace of(int code) {
return MAP.get(code);
diff --git a/app/src/main/java/org/wikipedia/page/PageProperties.java
b/app/src/main/java/org/wikipedia/page/PageProperties.java
index c4081ac..40bfd36 100644
--- a/app/src/main/java/org/wikipedia/page/PageProperties.java
+++ b/app/src/main/java/org/wikipedia/page/PageProperties.java
@@ -9,12 +9,13 @@
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import org.wikipedia.WikipediaApp;
import org.wikipedia.server.PageLeadProperties;
import org.wikipedia.util.StringUtil;
import java.text.ParseException;
import java.util.Date;
+
+import static org.wikipedia.util.DateUtil.getIso8601DateFormat;
/**
* Immutable class that contains metadata associated with a PageTitle.
@@ -61,8 +62,7 @@
String lastModifiedText = core.getLastModified();
if (lastModifiedText != null) {
try {
-
lastModified.setTime(WikipediaApp.getInstance().getSimpleDateFormat()
- .parse(lastModifiedText).getTime());
+
lastModified.setTime(getIso8601DateFormat().parse(lastModifiedText).getTime());
} catch (ParseException e) {
Log.d("PageProperties", "Failed to parse date: " +
lastModifiedText);
}
@@ -102,8 +102,7 @@
lastModified = new Date();
String lastModifiedText = json.optString("lastmodified");
try {
-
lastModified.setTime(WikipediaApp.getInstance().getSimpleDateFormat()
- .parse(lastModifiedText).getTime());
+
lastModified.setTime(getIso8601DateFormat().parse(lastModifiedText).getTime());
} catch (ParseException e) {
Log.d("PageProperties", "Failed to parse date: " +
lastModifiedText);
}
@@ -283,8 +282,7 @@
try {
json.put("id", pageId);
json.put("revision", revisionId);
- json.put("lastmodified",
WikipediaApp.getInstance().getSimpleDateFormat()
- .format(getLastModified()));
+ json.put("lastmodified",
getIso8601DateFormat().format(getLastModified()));
json.put("displaytitle", displayTitleText);
json.put(JSON_NAME_TITLE_PRONUNCIATION_URL, titlePronunciationUrl);
json.put(JSON_NAME_GEO, GeoMarshaller.marshal(geo));
diff --git
a/app/src/main/java/org/wikipedia/savedpages/ReadingListPageObserver.java
b/app/src/main/java/org/wikipedia/savedpages/ReadingListPageObserver.java
new file mode 100644
index 0000000..c533e8c
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/savedpages/ReadingListPageObserver.java
@@ -0,0 +1,26 @@
+package org.wikipedia.savedpages;
+
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.annotation.Nullable;
+
+import org.wikipedia.WikipediaApp;
+
+public class ReadingListPageObserver extends ContentObserver {
+ public ReadingListPageObserver(@Nullable Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ WikipediaApp.getInstance()
+ .startService(new Intent(WikipediaApp.getInstance(),
SavedPageSyncService.class));
+ }
+}
diff --git
a/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
new file mode 100644
index 0000000..4ff0b96
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/savedpages/SavedPageSyncService.java
@@ -0,0 +1,176 @@
+package org.wikipedia.savedpages;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+import com.github.kevinsawicki.http.HttpRequest;
+
+import org.wikipedia.WikipediaApp;
+import org.wikipedia.page.Page;
+import org.wikipedia.page.PageTitle;
+import org.wikipedia.readinglist.page.ReadingListPageRow;
+import org.wikipedia.readinglist.page.database.ReadingListPageDao;
+import org.wikipedia.readinglist.page.database.disk.ReadingListPageDiskRow;
+import org.wikipedia.server.PageService;
+import org.wikipedia.server.PageServiceFactory;
+import org.wikipedia.util.FileUtil;
+import org.wikipedia.util.UriUtil;
+import org.wikipedia.util.log.L;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.wikipedia.readinglist.page.database.disk.DiskStatus.DELETED;
+import static org.wikipedia.readinglist.page.database.disk.DiskStatus.ONLINE;
+import static org.wikipedia.readinglist.page.database.disk.DiskStatus.OUTDATED;
+import static org.wikipedia.readinglist.page.database.disk.DiskStatus.SAVED;
+import static org.wikipedia.readinglist.page.database.disk.DiskStatus.UNSAVED;
+import static org.wikipedia.util.FileUtil.writeFile;
+
+public class SavedPageSyncService extends IntentService {
+ @NonNull private ReadingListPageDao dao;
+
+ public SavedPageSyncService() {
+ super("SavedPageSyncService");
+ dao = ReadingListPageDao.instance();
+ }
+
+ @Override
+ protected void onHandleIntent(@NonNull Intent intent) {
+ List<ReadingListPageDiskRow> queue = new ArrayList<>();
+ Collection<ReadingListPageDiskRow> rows = dao.startDiskTransaction();
+ L.i("Syncing saved rlp pages with saved pages service");
+
+ for (final ReadingListPageDiskRow row : rows) {
+ L.v("Found pending tx with status: " + row.status().name());
+ switch (row.status()) {
+ case UNSAVED:
+ case DELETED:
+ String filename = row.filename();
+ if (filename != null) {
+ FileUtil.delete(new File(filename), true);
+ dao.completeDiskTransaction(row);
+ L.v("Deleted" + filename);
+ continue;
+ }
+ L.e("Found row with null filename; skipping");
+ continue;
+ case OUTDATED:
+ queue.add(row);
+ continue;
+ case ONLINE:
+ case SAVED:
+ L.w("Received row with unexpected status " + row.status()
+ ": "
+ + row.toString());
+ continue;
+ default:
+ throw new UnsupportedOperationException("Invalid disk row
status: "
+ + row.status().name());
+ }
+ }
+ saveNewEntries(queue);
+ }
+
+ @VisibleForTesting
+ public void saveNewEntries(List<ReadingListPageDiskRow> queue) {
+ while (!queue.isEmpty()) {
+ ReadingListPageDiskRow row = queue.get(0);
+ boolean ok = savePageFor(row);
+ if (!ok) {
+ dao.failDiskTransaction(queue);
+ break;
+ }
+ dao.completeDiskTransaction(row);
+ queue.remove(row);
+ }
+ }
+
+ @VisibleForTesting
+ public boolean savePageFor(@NonNull ReadingListPageDiskRow row) {
+ final PageTitle title = makeTitleFrom(row);
+ if (title == null) {
+ return false;
+ }
+
+ try {
+ final Page page =
getApiService(title).pageCombo(title.getPrefixedText(),
+
!WikipediaApp.getInstance().isImageDownloadEnabled()).toPage(title);
+ final SavedPage savedPage = new SavedPage(page.getTitle());
+ final ImageUrlMap imageUrlMap = new
ImageUrlMap.Builder(FileUtil.getSavedPageDirFor(title))
+ .extractUrls(page).build();
+ savedPage.writeToFileSystem(page);
+ downloadImages(imageUrlMap);
+ savedPage.writeUrlMap(imageUrlMap.toJSON());
+ L.i("Page " + title.getDisplayText() + " saved!");
+ return true;
+ } catch (Exception e) {
+ L.e("Failed to save page " + title.getDisplayText(), e);
+ return false;
+ }
+ }
+
+ @Nullable
+ private PageTitle makeTitleFrom(@NonNull ReadingListPageDiskRow row) {
+ ReadingListPageRow pageRow = row.dat();
+ if (pageRow == null) {
+ return null;
+ }
+ String namespace = pageRow.namespace().toLegacyString();
+ return new PageTitle(namespace, pageRow.title(), pageRow.site());
+ }
+
+ /**
+ * @param imageUrlMap a Map with entries {source URL, file path} of images
to be downloaded
+ */
+ private void downloadImages(@NonNull final ImageUrlMap imageUrlMap) {
+ for (Map.Entry<String, String> entry : imageUrlMap.entrySet()) {
+ final String url =
UriUtil.resolveProtocolRelativeUrl(entry.getKey());
+ final File file = new File(entry.getValue());
+ boolean success = false;
+ try {
+ success = downloadImage(url, file);
+ } catch (IOException e) {
+ L.e("Failed to download image: " + url, e);
+ }
+
+ if (!success) {
+ imageUrlMap.remove(url);
+ }
+ }
+ }
+
+ private boolean downloadImage(@NonNull String url, @NonNull File file)
throws IOException {
+ if (!url.startsWith("http")) {
+ L.e("ignoring non-HTTP URL " + url);
+ return true;
+ }
+
+ HttpRequest request =
HttpRequest.get(url).userAgent(WikipediaApp.getInstance()
+ .getUserAgent());
+ try {
+ if (request.ok()) {
+ InputStream response = request.stream();
+ writeFile(response, file);
+ response.close();
+ L.v("downloaded image " + url + " to " +
file.getAbsolutePath());
+ return true;
+ }
+ } catch (Exception e) {
+ L.e("could not download image " + url, e);
+ }
+ return false;
+ }
+
+ @NonNull
+ private PageService getApiService(@NonNull PageTitle title) {
+ return PageServiceFactory.create(title.getSite());
+ }
+}
diff --git a/app/src/main/java/org/wikipedia/server/PageService.java
b/app/src/main/java/org/wikipedia/server/PageService.java
index ff5826e..4514bc3 100644
--- a/app/src/main/java/org/wikipedia/server/PageService.java
+++ b/app/src/main/java/org/wikipedia/server/PageService.java
@@ -41,4 +41,12 @@
* @param cb a Retrofit callback which provides the populated PageCombo
object in #success
*/
void pageCombo(String title, boolean noImages, PageCombo.Callback cb);
+
+ /**
+ * Gets all page content of a given title. Used in the saved page sync
background service.
+ *
+ * @param title the page title to be used including prefix
+ * @param noImages add the noimages flag to the request if true
+ */
+ PageCombo pageCombo(String title, boolean noImages);
}
diff --git a/app/src/main/java/org/wikipedia/server/mwapi/MwPageService.java
b/app/src/main/java/org/wikipedia/server/mwapi/MwPageService.java
index 77d49e8..78e4d66 100644
--- a/app/src/main/java/org/wikipedia/server/mwapi/MwPageService.java
+++ b/app/src/main/java/org/wikipedia/server/mwapi/MwPageService.java
@@ -93,6 +93,11 @@
});
}
+ @Override
+ public MwPageCombo pageCombo(String title, boolean noImages) {
+ return webService.pageCombo(title, noImages);
+ }
+
/**
* Optional boolean Retrofit parameter.
* We don't want to send the query parameter at all when it's false since
the presence of the
@@ -179,5 +184,19 @@
+ "&noheadings=true")
void pageCombo(@Query("page") String title, @Query("noimages") Boolean
noImages,
Callback<MwPageCombo> cb);
+
+ /**
+ * Gets all page content of a given title -- for refreshing a saved
page
+ * Note: the only difference in the URL from #pageLead is the
sections=all instead of 0.
+ *
+ * @param title the page title to be used including prefix
+ * @param noImages add the noimages flag to the request if true
+ */
+ @GET("/w/api.php?action=mobileview&format=json&formatversion=2&prop="
+ +
"text%7Csections%7Clanguagecount%7Cthumb%7Cimage%7Cid%7Crevision%7Cdescription"
+ +
"%7Clastmodified%7Cnormalizedtitle%7Cdisplaytitle%7Cprotection%7Ceditable"
+ +
"&onlyrequestedsections=1§ions=all§ionprop=toclevel%7Cline%7Canchor"
+ + "&noheadings=true")
+ MwPageCombo pageCombo(@Query("page") String title, @Query("noimages")
Boolean noImages);
}
}
diff --git a/app/src/main/java/org/wikipedia/server/restbase/RbPageService.java
b/app/src/main/java/org/wikipedia/server/restbase/RbPageService.java
index 1a96b33..3886fb4 100644
--- a/app/src/main/java/org/wikipedia/server/restbase/RbPageService.java
+++ b/app/src/main/java/org/wikipedia/server/restbase/RbPageService.java
@@ -100,6 +100,11 @@
});
}
+ @Override
+ public RbPageCombo pageCombo(String title, boolean noImages) {
+ return webService.pageCombo(title, noImages);
+ }
+
/* Not defined in the PageService interface since the Wiktionary
definition endpoint exists only
* in the mobile content service, and does not concern the wholesale
retrieval of the contents
* of a wiki page.
@@ -181,6 +186,14 @@
void pageCombo(@Path("title") String title, @Query("noimages") Boolean
noImages,
Callback<RbPageCombo> cb);
+ /**
+ * Gets all page content of a given title. Used in the saved page
sync background service.
+ *
+ * @param title the page title to be used including prefix
+ * @param noImages add the noimages flag to the request if true
+ */
+ @GET("/page/mobile-sections/{title}")
+ RbPageCombo pageCombo(@Path("title") String title, @Query("noimages")
Boolean noImages);
/**
* Gets selected Wiktionary content for a given title derived from
user-selected text
diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.java
b/app/src/main/java/org/wikipedia/util/DateUtil.java
new file mode 100644
index 0000000..6184a82
--- /dev/null
+++ b/app/src/main/java/org/wikipedia/util/DateUtil.java
@@ -0,0 +1,31 @@
+package org.wikipedia.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public final class DateUtil {
+
+ public static SimpleDateFormat getIso8601DateFormat() {
+ SimpleDateFormat simpleDateFormat = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT);
+ simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return simpleDateFormat;
+ }
+
+ // Ex. "2015-07-18T18:11:52Z"
+ public static long fromMwApiTimestamp(String timestamp) {
+ long timeInMilliseconds = 0;
+ try {
+ Date date = getIso8601DateFormat().parse(timestamp);
+ timeInMilliseconds = date.getTime();
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return timeInMilliseconds;
+ }
+
+ private DateUtil() {
+ }
+}
diff --git a/app/src/main/java/org/wikipedia/util/FileUtil.java
b/app/src/main/java/org/wikipedia/util/FileUtil.java
index 813a53e..f6b5fa3 100644
--- a/app/src/main/java/org/wikipedia/util/FileUtil.java
+++ b/app/src/main/java/org/wikipedia/util/FileUtil.java
@@ -69,6 +69,15 @@
path.delete();
}
+ public static void writeFile(InputStream inputStream, File file) throws
IOException {
+ FileOutputStream outputStream = new FileOutputStream(file);
+ try {
+ copyStreams(inputStream, outputStream);
+ } finally {
+ outputStream.close();
+ }
+ }
+
/**
* Utility method to copy a stream into another stream.
*
--
To view, visit https://gerrit.wikimedia.org/r/271925
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Iae8e9e19fa4d774f590a85f238b1510bb5642878
Gerrit-PatchSet: 45
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Mholloway <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Brion VIBBER <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: Niedzielski <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits