Moved single-use code out of Utils over to where it's actually used.

This commit is contained in:
Paul Hawke 2017-08-25 23:08:50 -05:00
parent 388f588301
commit d0a85dbd82
12 changed files with 184 additions and 217 deletions

View file

@ -144,12 +144,6 @@ public class CommonsApplication extends DaggerApplication {
}
}
public boolean deviceHasCamera() {
PackageManager pm = getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
public void clearApplicationData(Context context) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());

View file

@ -5,7 +5,9 @@ import android.content.res.Resources;
import android.support.annotation.Nullable;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
@ -21,7 +23,7 @@ public class LicenseList {
public LicenseList(Activity activity) {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
while (Utils.xmlFastForward(parser, XMLNS_LICENSE, "license")) {
while (xmlFastForward(parser, XMLNS_LICENSE, "license")) {
String id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url");
@ -76,4 +78,34 @@ public class LicenseList {
}
return template;
}
/**
* Fast-forward an XmlPullParser to the next instance of the given element
* in the input stream (namespaced).
*
* @param parser
* @param namespace
* @param element
* @return true on match, false on failure
*/
private boolean xmlFastForward(XmlPullParser parser, String namespace, String element) {
try {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() == XmlPullParser.START_TAG &&
parser.getNamespace().equals(namespace) &&
parser.getName().equals(element)) {
// We found it!
return true;
}
}
return false;
} catch (XmlPullParserException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}

View file

@ -1,26 +1,10 @@
package fr.free.nrw.commons;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.Spanned;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.w3c.dom.Node;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -29,70 +13,10 @@ import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class Utils {
// Get SHA1 of file from input stream
public static String getSHA1(InputStream is) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "Exception while getting Digest");
return "";
}
byte[] buffer = new byte[8192];
int read;
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
// Fill to 40 chars
output = String.format("%40s", output).replace(' ', '0');
Timber.i("File SHA1: %s", output);
return output;
} catch (IOException e) {
Timber.e(e, "IO Exception");
return "";
} finally {
try {
is.close();
} catch (IOException e) {
Timber.e(e, "Exception on closing MD5 input stream");
}
}
}
/** Fix Html.fromHtml is deprecated problem
* @param source provided Html string
* @return returned Spanned of appropriate method according to version check
* */
public static Spanned fromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
return Html.fromHtml(source);
}
}
/**
* Strips localization symbols from a string.
* Removes the suffix after "@" and quotes.
@ -109,49 +33,12 @@ public class Utils {
}
}
public static Date parseMWDate(String mwDate) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
try {
return isoFormat.parse(mwDate);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
public static String toMWDate(Date date) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return isoFormat.format(date);
}
public static String makeThumbBaseUrl(String filename) {
String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name)));
return String.format("%s/%s/%s/%s", CommonsApplication.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
}
public static String getStringFromDOM(Node dom) {
Transformer transformer = null;
try {
transformer = TransformerFactory.newInstance().newTransformer();
} catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
StringWriter outputStream = new StringWriter();
DOMSource domSource = new DOMSource(dom);
StreamResult strResult = new StreamResult(outputStream);
try {
transformer.transform(domSource, strResult);
} catch (TransformerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return outputStream.toString();
}
public static String urlEncode(String url) {
try {
return URLEncoder.encode(url, "utf-8");
@ -160,39 +47,10 @@ public class Utils {
}
}
public static long countBytes(InputStream stream) throws IOException {
long count = 0;
BufferedInputStream bis = new BufferedInputStream(stream);
while (bis.read() != -1) {
count++;
}
return count;
}
public static String capitalize(String string) {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
}
public static String licenseTemplateFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "{{self|cc-by-3.0}}";
case Prefs.Licenses.CC_BY_4:
return "{{self|cc-by-4.0}}";
case Prefs.Licenses.CC_BY_SA_3:
return "{{self|cc-by-sa-3.0}}";
case Prefs.Licenses.CC_BY_SA_4:
return "{{self|cc-by-sa-4.0}}";
case Prefs.Licenses.CC0:
return "{{self|cc-zero}}";
case Prefs.Licenses.CC_BY:
return "{{self|cc-by-3.0}}";
case Prefs.Licenses.CC_BY_SA:
return "{{self|cc-by-sa-3.0}}";
}
throw new RuntimeException("Unrecognized license value: " + license);
}
public static int licenseNameFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
@ -213,51 +71,6 @@ public class Utils {
throw new RuntimeException("Unrecognized license value: " + license);
}
public static String licenseUrlFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "https://creativecommons.org/licenses/by/3.0/";
case Prefs.Licenses.CC_BY_4:
return "https://creativecommons.org/licenses/by/4.0/";
case Prefs.Licenses.CC_BY_SA_3:
return "https://creativecommons.org/licenses/by-sa/3.0/";
case Prefs.Licenses.CC_BY_SA_4:
return "https://creativecommons.org/licenses/by-sa/4.0/";
case Prefs.Licenses.CC0:
return "https://creativecommons.org/publicdomain/zero/1.0/";
}
throw new RuntimeException("Unrecognized license value: " + license);
}
/**
* Fast-forward an XmlPullParser to the next instance of the given element
* in the input stream (namespaced).
*
* @param parser
* @param namespace
* @param element
* @return true on match, false on failure
*/
public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) {
try {
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getEventType() == XmlPullParser.START_TAG &&
parser.getNamespace().equals(namespace) &&
parser.getName().equals(element)) {
// We found it!
return true;
}
}
return false;
} catch (XmlPullParserException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
@ -272,8 +85,4 @@ public class Utils {
return title;
}
public static boolean isNullOrWhiteSpace(String value) {
return value == null || value.trim().isEmpty();
}
}

View file

@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import java.text.SimpleDateFormat;
@ -16,7 +17,6 @@ import java.util.Locale;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
public class Contribution extends Media {
@ -149,7 +149,7 @@ public class Contribution extends Media {
}
buffer.append("== {{int:license-header}} ==\n")
.append(Utils.licenseTemplateFor(getLicense())).append("\n\n")
.append(licenseTemplateFor(getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
.append(getTrackingTemplates());
return buffer.toString();
@ -377,4 +377,25 @@ public class Contribution extends Media {
}
}
}
@NonNull
private String licenseTemplateFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "{{self|cc-by-3.0}}";
case Prefs.Licenses.CC_BY_4:
return "{{self|cc-by-4.0}}";
case Prefs.Licenses.CC_BY_SA_3:
return "{{self|cc-by-sa-3.0}}";
case Prefs.Licenses.CC_BY_SA_4:
return "{{self|cc-by-sa-4.0}}";
case Prefs.Licenses.CC0:
return "{{self|cc-zero}}";
case Prefs.Licenses.CC_BY:
return "{{self|cc-by-3.0}}";
case Prefs.Licenses.CC_BY_SA:
return "{{self|cc-by-sa-3.0}}";
}
throw new RuntimeException("Unrecognized license value: " + license);
}
}

View file

@ -196,11 +196,17 @@ public class ContributionsListFragment extends DaggerFragment {
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu);
if (!application.deviceHasCamera()) {
if (!deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false);
}
}
public boolean deviceHasCamera() {
PackageManager pm = getContext().getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View file

@ -13,9 +13,12 @@ import android.os.RemoteException;
import android.text.TextUtils;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.inject.Inject;
@ -130,7 +133,13 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
done = true;
}
}
prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
prefs.edit().putString("lastSyncTimestamp", toMWDate(curTime)).apply();
Timber.d("Oh hai, everyone! Look, a kitty!");
}
private String toMWDate(Date date) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return isoFormat.format(date);
}
}

View file

@ -17,7 +17,6 @@ import java.io.IOException;
import javax.inject.Inject;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@ -59,7 +58,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
if (Utils.isNullOrWhiteSpace(authCookie)) {
if (isNullOrWhiteSpace(authCookie)) {
Timber.d("Could not authenticate :(");
return;
}
@ -133,4 +132,8 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
}
}
}
private boolean isNullOrWhiteSpace(String value) {
return value == null || value.trim().isEmpty();
}
}

View file

@ -25,6 +25,8 @@ import org.mediawiki.api.MWApi;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -33,7 +35,6 @@ import java.util.Locale;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.Utils;
import in.yuvi.http.fluent.Http;
import io.reactivex.Observable;
import io.reactivex.Single;
@ -337,7 +338,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
logEvents.add(new LogEventResult.LogEvent(
image.getString("@pageid"),
image.getString("@title"),
Utils.parseMWDate(image.getString("@timestamp")))
parseMWDate(image.getString("@timestamp")))
);
}
return logEvents;
@ -399,7 +400,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String errorCode = result.getString("/api/error/@code");
return new UploadResult(resultStatus, errorCode);
} else {
Date dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
String imageUrl = result.getString("/api/upload/imageinfo/@url");
return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl);
@ -425,4 +426,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return Integer.parseInt(uploadCount);
});
}
private Date parseMWDate(String mwDate) {
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
try {
return isoFormat.parse(mwDate);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -1,12 +1,13 @@
package fr.free.nrw.commons.ui.widget;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatTextView;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import fr.free.nrw.commons.Utils;
/**
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
* links clickable.
@ -17,10 +18,25 @@ public class HtmlTextView extends AppCompatTextView {
super(context, attrs);
setMovementMethod(LinkMovementMethod.getInstance());
setText(Utils.fromHtml(getText().toString()));
setText(fromHtml(getText().toString()));
}
public void setHtmlText(String newText) {
setText(Utils.fromHtml(newText));
setText(fromHtml(newText));
}
/**
* Fix Html.fromHtml is deprecated problem
*
* @param source provided Html string
* @return returned Spanned of appropriate method according to version check
*/
private static Spanned fromHtml(String source) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
} else {
//noinspection deprecation
return Html.fromHtml(source);
}
}
}

View file

@ -27,6 +27,9 @@ import com.facebook.drawee.view.SimpleDraweeView;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
@ -35,7 +38,6 @@ import javax.inject.Inject;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.category.CategorizationFragment;
@ -380,7 +382,7 @@ public class ShareActivity
try {
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
Timber.d("Input stream created from %s", mediaUri.toString());
String fileSHA1 = Utils.getSHA1(inputStream);
String fileSHA1 = getSHA1(inputStream);
Timber.d("File SHA1 is: %s", fileSHA1);
ExistingFileAsync fileAsyncTask =
@ -536,4 +538,41 @@ public class ShareActivity
}
return super.onOptionsItemSelected(item);
}
// Get SHA1 of file from input stream
private String getSHA1(InputStream is) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
Timber.e(e, "Exception while getting Digest");
return "";
}
byte[] buffer = new byte[8192];
int read;
try {
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String output = bigInt.toString(16);
// Fill to 40 chars
output = String.format("%40s", output).replace(' ', '0');
Timber.i("File SHA1: %s", output);
return output;
} catch (IOException e) {
Timber.e(e, "IO Exception");
return "";
} finally {
try {
is.close();
} catch (IOException e) {
Timber.e(e, "Exception on closing MD5 input stream");
}
}
}
}

View file

@ -8,7 +8,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
@ -184,7 +184,7 @@ public class SingleUploadFragment extends DaggerFragment {
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(Utils.licenseUrlFor(license)));
intent.setData(Uri.parse(licenseUrlFor(license)));
startActivity(intent);
return true;
} else {
@ -280,6 +280,23 @@ public class SingleUploadFragment extends DaggerFragment {
}
}
@NonNull
private String licenseUrlFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "https://creativecommons.org/licenses/by/3.0/";
case Prefs.Licenses.CC_BY_4:
return "https://creativecommons.org/licenses/by/4.0/";
case Prefs.Licenses.CC_BY_SA_3:
return "https://creativecommons.org/licenses/by-sa/3.0/";
case Prefs.Licenses.CC_BY_SA_4:
return "https://creativecommons.org/licenses/by-sa/4.0/";
case Prefs.Licenses.CC0:
return "https://creativecommons.org/publicdomain/zero/1.0/";
}
throw new RuntimeException("Unrecognized license value: " + license);
}
private class TitleTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { }

View file

@ -13,13 +13,14 @@ import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.TextUtils;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.concurrent.Executors;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
@ -109,7 +110,7 @@ public class UploadController {
.getLength();
if(length == -1) {
// Let us find out the long way!
length = Utils.countBytes(application.getContentResolver()
length = countBytes(application.getContentResolver()
.openInputStream(contribution.getLocalUri()));
}
contribution.setDataLength(length);
@ -165,4 +166,14 @@ public class UploadController {
}
}.executeOnExecutor(Executors.newFixedThreadPool(1)); // TODO remove this by using a sensible thread handling strategy
}
private long countBytes(InputStream stream) throws IOException {
long count = 0;
BufferedInputStream bis = new BufferedInputStream(stream);
while (bis.read() != -1) {
count++;
}
return count;
}
}