/****************************************************************************************
* Copyright (c) 2009 Daniel Svärd <daniel.svard@gmail.com> *
* Copyright (c) 2009 Edu Zamora <edu.zasu@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Date;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.Deflater;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mindprod.common11.BigDate;
/**
* TODO comments
*/
public class Utils {
public static Logger log = LoggerFactory.getLogger(Utils.class);
// Used to format doubles with English's decimal separator system
public static final Locale ENGLISH_LOCALE = new Locale("en_US");
public static final int CHUNK_SIZE = 32768;
private static final int DAYS_BEFORE_1970 = 719163;
private static NumberFormat mCurrentNumberFormat;
private static NumberFormat mCurrentPercentageFormat;
private static TreeSet<Long> sIdTree;
private static long sIdTime;
private static final int TIME_SECONDS = 0;
private static final int TIME_MINUTES = 1;
private static final int TIME_HOURS = 2;
private static final int TIME_DAYS = 3;
private static final int TIME_MONTHS = 4;
private static final int TIME_YEARS = 5;
public static final int TIME_FORMAT_DEFAULT = 0;
public static final int TIME_FORMAT_IN = 1;
public static final int TIME_FORMAT_BEFORE = 2;
/* Prevent class from being instantiated */
private Utils() { }
// Regex pattern used in removing tags from text before diff
private static final Pattern imgPattern = Pattern.compile("<img src=[\"']?([^\"'>]+)[\"']? ?/?>");
private static final Pattern stylePattern = Pattern.compile("(?s)<style.*?>.*?</style>");
private static final Pattern scriptPattern = Pattern.compile("(?s)<script.*?>.*?</script>");
private static final Pattern tagPattern = Pattern.compile("<.*?>");
private static final Pattern htmlEntitiesPattern = Pattern.compile("?\\w+;");
/**
* Return a string representing a time span (eg '2 days').
* @param inFormat: if true, return eg 'in 2 days'
*/
/*
public static String fmtTimeSpan(double time, int format) {
return fmtTimeSpan(time, format, false);
}
public static String fmtTimeSpan(double time, int format, boolean boldNumber) {
int type;
int unit = 99;
int point = 0;
if (Math.abs(time) < 60 || unit < 1) {
type = TIME_SECONDS;
} else if (Math.abs(time) < 3599 || unit < 2) {
type = TIME_MINUTES;
} else if (Math.abs(time) < 60 * 60 * 24 || unit < 3) {
type = TIME_HOURS;
} else if (Math.abs(time) < 60 * 60 * 24 * 29.5 || unit < 4) {
type = TIME_DAYS;
} else if (Math.abs(time) < 60 * 60 * 24 * 30 * 11.95 || unit < 5) {
type = TIME_MONTHS;
point = 1;
} else {
type = TIME_YEARS;
point = 1;
}
time = convertSecondsTo(time, type);
int formatId;
switch (format) {
case TIME_FORMAT_IN:
if (Math.round(time * 10) == 10) {
formatId = R.array.next_review_in_s;
} else {
formatId = R.array.next_review_in_p;
}
break;
case TIME_FORMAT_BEFORE:
if (Math.round(time * 10) == 10) {
formatId = R.array.next_review_before_s;
} else {
formatId = R.array.next_review_before_p;
}
break;
case TIME_FORMAT_DEFAULT:
default:
if (Math.round(time * 10) == 10) {
formatId = R.array.next_review_s;
} else {
formatId = R.array.next_review_p;
}
break;
}
String timeString = String.format(AnkiDroidApp.getAppResources().getStringArray(formatId)[type],
boldNumber ? "<b>" + fmtDouble(time, point) + "</b>" : fmtDouble(time, point));
if (boldNumber && time == 1) {
timeString = timeString.replace("1", "<b>1</b>");
}
return timeString;
}*/
public static double convertSecondsTo(double seconds, int type) {
switch (type) {
case TIME_SECONDS:
return seconds;
case TIME_MINUTES:
return seconds / 60.0;
case TIME_HOURS:
return seconds / 3600.0;
case TIME_DAYS:
return seconds / 86400.0;
case TIME_MONTHS:
return seconds / 2592000.0;
case TIME_YEARS:
return seconds / 31536000.0;
default:
return 0;
}
}
/**
* Locale
* ***********************************************************************************************
*/
/**
* @return double with percentage sign
*/
public static String fmtPercentage(Double value) {
return fmtPercentage(value, 0);
}
public static String fmtPercentage(Double value, int point) {
// only retrieve the percentage format the first time
if (mCurrentPercentageFormat == null) {
mCurrentPercentageFormat = NumberFormat.getPercentInstance(Locale.getDefault());
}
mCurrentNumberFormat.setMaximumFractionDigits(point);
return mCurrentPercentageFormat.format(value);
}
/**
* @return double with percentage sign
*/
/*
public static boolean isNewDay(long millies) {
Calendar cal = Calendar.getInstance();
if (cal.get(Calendar.HOUR_OF_DAY) < StudyOptions.mNewDayStartsAt) {
cal.add(Calendar.HOUR_OF_DAY, -cal.get(Calendar.HOUR_OF_DAY) - 24 + StudyOptions.mNewDayStartsAt);
} else {
cal.add(Calendar.HOUR_OF_DAY, -cal.get(Calendar.HOUR_OF_DAY) + StudyOptions.mNewDayStartsAt);
}
cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
cal.add(Calendar.SECOND, -cal.get(Calendar.SECOND));
if (cal.getTimeInMillis() > millies) {
return true;
} else {
return false;
}
} */
/**
* @return a string with decimal separator according to current locale
*/
public static String fmtDouble(Double value) {
return fmtDouble(value, 1);
}
public static String fmtDouble(Double value, int point) {
// only retrieve the number format the first time
if (mCurrentNumberFormat == null) {
mCurrentNumberFormat = NumberFormat.getInstance(Locale.getDefault());
}
mCurrentNumberFormat.setMaximumFractionDigits(point);
return mCurrentNumberFormat.format(value);
}
public static long genID() {
long time = System.currentTimeMillis();
long id;
long rand;
if (sIdTree == null) {
sIdTree = new TreeSet<Long>();
sIdTime = time;
} else if (sIdTime != time) {
sIdTime = time;
sIdTree.clear();
}
while (true) {
rand = UUID.randomUUID().getMostSignificantBits();
if (!sIdTree.contains(new Long(rand))) {
sIdTree.add(new Long(rand));
break;
}
}
id = rand << 41 | time;
return id;
}
private static final BigInteger shiftID = new BigInteger("18446744073709551616");
private static final BigInteger maxID = new BigInteger("9223372036854775808");
public static String hexifyID(long id) {
if (id < 0) {
BigInteger bid = BigInteger.valueOf(id);
return bid.add(shiftID).toString(16);
}
return Long.toHexString(id);
}
public static long dehexifyID(String id) {
BigInteger bid = new BigInteger(id, 16);
if (bid.compareTo(maxID) >= 0) {
bid.subtract(shiftID);
}
return bid.longValue();
}
/**
* Returns a SQL string from an array of integers.
* @param ids The array of integers to include in the list.
* @return An SQL compatible string in the format (ids[0],ids[1],..).
*/
public static String ids2str(long[] ids) {
String str = "()";
if (ids != null) {
str = Arrays.toString(ids);
str = "(" + str.substring(1, str.length()-1) + ")";
}
return str;
}
/**
* Returns a SQL string from an array of integers.
* @param ids The array of integers to include in the list.
* @return An SQL compatible string in the format (ids[0],ids[1],..).
*/
public static String ids2str(JSONArray ids) {
StringBuilder str = new StringBuilder(512);
str.append("(");
if (ids != null) {
int len = ids.length();
for (int i = 0; i < len; i++) {
try {
if (i == (len - 1)) {
str.append(ids.get(i));
} else {
str.append(ids.get(i)).append(",");
}
} catch (JSONException e) {
log.error("JSONException = " + e.getMessage());
}
}
}
str.append(")");
return str.toString();
}
/**
* Returns a SQL string from an array of integers.
* @param ids The array of integers to include in the list.
* @return An SQL compatible string in the format (ids[0],ids[1],..).
*/
public static String ids2str(List<String> ids) {
StringBuilder str = new StringBuilder(512);
str.append("(");
if (ids != null) {
int len = ids.size();
for (int i = 0; i < len; i++) {
if (i == (len - 1)) {
str.append(ids.get(i));
} else {
str.append(ids.get(i)).append(",");
}
}
}
str.append(")");
return str.toString();
}
public static JSONArray listToJSONArray(List<Object> list) {
JSONArray jsonArray = new JSONArray();
for (Object o : list) {
jsonArray.put(o);
}
return jsonArray;
}
public static List<String> jsonArrayToListString(JSONArray jsonArray) throws JSONException {
ArrayList<String> list = new ArrayList<String>();
int len = jsonArray.length();
for (int i = 0; i < len; i++) {
list.add(jsonArray.getString(i));
}
return list;
}
/**
* Strip HTML but keep media filenames
*/
public static String stripHTMLMedia(String s) {
Matcher imgMatcher = imgPattern.matcher(s);
return stripHTML(imgMatcher.replaceAll(" $1 "));
}
public static String stripHTML(String s) {
Matcher styleMatcher = stylePattern.matcher(s);
s = styleMatcher.replaceAll("");
Matcher scriptMatcher = scriptPattern.matcher(s);
s = scriptMatcher.replaceAll("");
Matcher tagMatcher = tagPattern.matcher(s);
s = tagMatcher.replaceAll("");
return entsToTxt(s);
}
private static String entsToTxt(String s) {
Matcher htmlEntities = htmlEntitiesPattern.matcher(s);
StringBuilder s2 = new StringBuilder(s);
while (htmlEntities.find()) {
String text = htmlEntities.group();
text = Jsoup.parse(text).text();
// TODO: inefficiency below, can get rid of multiple regex searches
s2.replace(htmlEntities.start(), htmlEntities.end(), text);
htmlEntities = htmlEntitiesPattern.matcher(s2);
}
return s2.toString();
}
/**
* Converts an InputStream to a String.
* @param is InputStream to convert
* @return String version of the InputStream
*/
public static String convertStreamToString(InputStream is) {
String contentOfMyInputStream = "";
try {
BufferedReader rd = new BufferedReader(new InputStreamReader(is), 4096);
String line;
StringBuilder sb = new StringBuilder();
while ((line = rd.readLine()) != null) {
sb.append(line);
}
rd.close();
contentOfMyInputStream = sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return contentOfMyInputStream;
}
/**
* Compress data.
* @param bytesToCompress is the byte array to compress.
* @return a compressed byte array.
* @throws java.io.IOException
*/
public static byte[] compress(byte[] bytesToCompress) throws IOException {
// Compressor with highest level of compression.
Deflater compressor = new Deflater(Deflater.BEST_COMPRESSION);
// Give the compressor the data to compress.
compressor.setInput(bytesToCompress);
compressor.finish();
// Create an expandable byte array to hold the compressed data.
// It is not necessary that the compressed data will be smaller than
// the uncompressed data.
ByteArrayOutputStream bos = new ByteArrayOutputStream(bytesToCompress.length);
// Compress the data
byte[] buf = new byte[bytesToCompress.length + 100];
while (!compressor.finished()) {
bos.write(buf, 0, compressor.deflate(buf));
}
bos.close();
// Get the compressed data
return bos.toByteArray();
}
/**
* Utility method to write to a file.
* Throws the exception, so we can report it in syncing log
* @throws IOException
*/
public static void writeToFile(InputStream source, String destination) throws IOException {
log.info("Creating new file... = " + destination);
new File(destination).createNewFile();
long startTimeMillis = System.currentTimeMillis();
OutputStream output = new BufferedOutputStream(new FileOutputStream(destination));
// Transfer bytes, from source to destination.
byte[] buf = new byte[CHUNK_SIZE];
long sizeBytes = 0;
int len;
if (source == null) {
log.info("source is null!");
}
while ((len = source.read(buf)) > 0) {
output.write(buf, 0, len);
sizeBytes += len;
// log.info("Write...");
}
long endTimeMillis = System.currentTimeMillis();
log.info("Finished writing!");
long durationSeconds = (endTimeMillis - startTimeMillis) / 1000;
long sizeKb = sizeBytes / 1024;
long speedKbSec = 0;
if (endTimeMillis != startTimeMillis) {
speedKbSec = sizeKb * 1000 / (endTimeMillis - startTimeMillis);
}
log.debug("Utils.writeToFile: " + "Size: " + sizeKb + "Kb, " + "Duration: " + durationSeconds + "s, " + "Speed: " + speedKbSec + "Kb/s");
output.close();
}
// Print methods
public static void printJSONObject(JSONObject jsonObject) {
printJSONObject(jsonObject, "-", null);
}
public static void printJSONObject(JSONObject jsonObject, boolean writeToFile) {
BufferedWriter buff;
try {
buff = writeToFile ?
new BufferedWriter(new FileWriter("/sdcard/payloadAndroid.txt"), 8192) : null;
try {
printJSONObject(jsonObject, "-", buff);
} finally {
if (buff != null)
buff.close();
}
} catch (IOException ioe) {
log.error("IOException = " + ioe.getMessage());
}
}
private static void printJSONObject(JSONObject jsonObject, String indentation, BufferedWriter buff) {
try {
@SuppressWarnings("unchecked") Iterator<String> keys = (Iterator<String>) jsonObject.keys();
TreeSet<String> orderedKeysSet = new TreeSet<String>();
while (keys.hasNext()) {
orderedKeysSet.add(keys.next());
}
Iterator<String> orderedKeys = orderedKeysSet.iterator();
while (orderedKeys.hasNext()) {
String key = orderedKeys.next();
try {
Object value = jsonObject.get(key);
if (value instanceof JSONObject) {
if (buff != null) {
buff.write(indentation + " " + key + " : ");
buff.newLine();
}
log.info(" " + indentation + key + " : ");
printJSONObject((JSONObject) value, indentation + "-", buff);
} else {
if (buff != null) {
buff.write(indentation + " " + key + " = " + jsonObject.get(key).toString());
buff.newLine();
}
log.info(" " + indentation + key + " = " + jsonObject.get(key).toString());
}
} catch (JSONException e) {
log.error("JSONException = " + e.getMessage());
}
}
} catch (IOException e1) {
log.error("IOException = " + e1.getMessage());
}
}
/*
public static void saveJSONObject(JSONObject jsonObject) throws IOException {
log.info("saveJSONObject");
BufferedWriter buff = new BufferedWriter(new FileWriter("/sdcard/jsonObjectAndroid.txt", true));
buff.write(jsonObject.toString());
buff.close();
}
*/
/**
* Returns 1 if true, 0 if false
*
* @param b The boolean to convert to integer
* @return 1 if b is true, 0 otherwise
*/
public static int booleanToInt(boolean b) {
return (b) ? 1 : 0;
}
/**
* Get the current time in seconds since January 1, 1970 UTC.
* @return the local system time in seconds
*/
public static double now() {
return (System.currentTimeMillis() / 1000.0);
}
/**
* Returns the effective date of the present moment.
* If the time is prior the cut-off time (9:00am by default as of 11/02/10) return yesterday,
* otherwise today
* Note that the Date class is java.sql.Date whose constructor sets hours, minutes etc to zero
*
* @param utcOffset The UTC offset in seconds we are going to use to determine today or yesterday.
* @return The date (with time set to 00:00:00) that corresponds to today in Anki terms
*/
public static Date genToday(double utcOffset) {
// The result is not adjusted for timezone anymore, following libanki model
// Timezone adjustment happens explicitly in Deck.updateCutoff(), but not in Deck.checkDailyStats()
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(System.currentTimeMillis() - (long) utcOffset * 1000l);
Date today = Date.valueOf(df.format(cal.getTime()));
return today;
}
public static Date genDate(long dateInMillis, double utcOffset) {
// The result is not adjusted for timezone anymore, following libanki model
// Timezone adjustment happens explicitly in Deck.updateCutoff(), but not in Deck.checkDailyStats()
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(dateInMillis - (long) utcOffset * 1000l);
Date today = Date.valueOf(df.format(cal.getTime()));
return today;
}
public static void printDate(String name, double date) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis((long)date * 1000);
log.debug("Value of " + name + ": " + cal.getTime().toGMTString());
}
public static String doubleToTime(double value) {
int time = (int) Math.round(value);
int seconds = time % 60;
int minutes = (time - seconds) / 60;
String formattedTime;
if (seconds < 10) {
formattedTime = Integer.toString(minutes) + ":0" + Integer.toString(seconds);
} else {
formattedTime = Integer.toString(minutes) + ":" + Integer.toString(seconds);
}
return formattedTime;
}
/**
* Returns the proleptic Gregorian ordinal of the date, where January 1 of year 1 has ordinal 1.
* @param date Date to convert to ordinal, since 01/01/01
* @return The ordinal representing the date
*/
public static int dateToOrdinal(Date date) {
// BigDate.toOrdinal returns the ordinal since 1970, so we add up the days from 01/01/01 to 1970
return BigDate.toOrdinal(date.getYear() + 1900, date.getMonth() + 1, date.getDate()) + DAYS_BEFORE_1970;
}
/**
* Return the date corresponding to the proleptic Gregorian ordinal, where January 1 of year 1 has ordinal 1.
* @param ordinal representing the days since 01/01/01
* @return Date converted from the ordinal
*/
public static Date ordinalToDate(int ordinal) {
return new Date((new BigDate(ordinal - DAYS_BEFORE_1970)).getLocalDate().getTime());
}
/**
* Indicates whether the specified action can be used as an intent. This method queries the package manager for
* installed packages that can respond to an intent with the specified action. If no suitable package is found, this
* method returns false.
* @param context The application's environment.
* @param action The Intent action to check for availability.
* @return True if an Intent with the specified action can be sent and responded to, false otherwise.
*/
/*
public static boolean isIntentAvailable(Context context, String action) {
return isIntentAvailable(context, action, null);
}
public static boolean isIntentAvailable(Context context, String action, ComponentName componentName) {
final PackageManager packageManager = context.getPackageManager();
final Intent intent = new Intent(action);
intent.setComponent(componentName);
List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
*/
/*
public static String getBaseUrl(String mediaDir, Model model, Deck deck) {
String base = null;// = model.getFeatures().trim();
// if (deck.getBool("remoteImages") && base.length() != 0 && !base.equalsIgnoreCase("null")) {
// return base;
// } else {
// Anki desktop calls deck.mediaDir() here, but for efficiency reasons we only call it once in
// Reviewer.onCreate() and use the value from there
if (mediaDir != null) {
base = urlEncodeMediaDir(mediaDir);
}
// }
return base;
}*/
/**
* @param mediaDir media directory path on SD card
* @return path converted to file URL, properly UTF-8 URL encoded
*/
/*
public static String urlEncodeMediaDir(String mediaDir) {
String base;
// Use android.net.Uri class to ensure whole path is properly encoded
// File.toURL() does not work here, and URLEncoder class is not directly usable
// with existing slashes
Uri mediaDirUri = Uri.fromFile(new File(mediaDir));
// Build complete URL
base = mediaDirUri.toString() +"/";
return base;
} */
/**
* Take an array of Long and return an array of long
*
* @param array The input with type Long[]
* @return The output with type long[]
*/
public static long[] toPrimitive(Long[] array) {
long[] results = new long[array.length];
if (array != null) {
for (int i = 0; i < array.length; i++) {
results[i] = array[i].longValue();
}
}
return results;
}
public static long[] toPrimitive(Collection<Long> array) {
long[] results = new long[array.size()];
if (array != null) {
int i = 0;
for (Long item : array) {
results[i++] = item.longValue();
}
}
return results;
}
/*
* Tags
**************************************/
/**
* Parse a string and return a list of tags.
*
* @param tags A string containing tags separated by space or comma (optionally followed by space)
* @return An array of Strings containing the individual tags
*/
public static String[] parseTags(String tags) {
if (tags != null && tags.length() != 0) {
return tags.trim().split(" +|, *");
} else {
return new String[] {};
}
}
/**
* Join a list of tags to a string, using spaces as separators
*
* @param tags The list of tags to join
* @return The joined tags in a single string
*/
public static String joinTags(Collection<String> tags) {
StringBuilder result = new StringBuilder(128);
for (String tag : tags) {
result.append(tag).append(" ");
}
return result.toString().trim();
}
/**
* Strip leading/trailing/superfluous spaces/commas from a tags string. Remove duplicates and sort.
*
* @param tags The string containing the tags, separated by spaces or commas
* @return The canonified string, as described above
*/
public static String canonifyTags(String tags) {
List<String> taglist = Arrays.asList(parseTags(tags));
for (int i = 0; i < taglist.size(); i++) {
String t = taglist.get(i);
if (t.startsWith(":")) {
taglist.set(i, t.replace("^:+", ""));
}
}
return joinTags(new TreeSet<String>(taglist));
}
/**
* Find if tag exists in a set of tags. The search is not case-sensitive
*
* @param tag The tag to look for
* @param tags The set of tags
* @return True is the tag is found in the set, false otherwise
*/
public static boolean findTag(String tag, List<String> tags) {
String lowercase = tag.toLowerCase();
for (String t : tags) {
if (t.toLowerCase().compareTo(lowercase) == 0) {
return true;
}
}
return false;
}
/**
* Add tags if they don't exist.
* Both parameters are in string format, the tags being separated by space or comma, as in parseTags
*
* @param tagStr The new tag(s) that are to be added
* @param tags The set of tags where the new ones will be added
* @return A string containing the union of tags of the input parameters
*/
public static String addTags(String tagStr, String tags) {
ArrayList<String> currentTags = new ArrayList<String>(Arrays.asList(parseTags(tags)));
for (String tag : parseTags(tagStr)) {
if (!findTag(tag, currentTags)) {
currentTags.add(tag);
}
}
return joinTags(currentTags);
}
// Misc
// *************
/**
* MD5 checksum.
* Equivalent to python md5.hexdigest()
*
* @param data the string to generate hash from
* @return A string of length 32 containing the hexadecimal representation of the MD5 checksum of data.
*/
public static String checksum(String data) {
String result = "";
if (data != null) {
MessageDigest md = null;
byte[] digest = null;
try {
md = MessageDigest.getInstance("MD5");
digest = md.digest(data.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
log.error("Utils.checksum: No such algorithm. " + e.getMessage());
throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) {
log.error("Utils.checksum: " + e.getMessage());
e.printStackTrace();
}
BigInteger biginteger = new BigInteger(1, digest);
result = biginteger.toString(16);
// pad with zeros to length of 32
if (result.length() < 32) {
result = "00000000000000000000000000000000".substring(0, 32 - result.length()) + result;
}
}
return result;
}
/*
public static void updateProgressBars(Context context, View view, double progress, int maxX, int y, boolean singleBar) {
updateProgressBars(context, view, progress, maxX, y, singleBar, true);
}
public static void updateProgressBars(Context context, View view, double progress, int maxX, int y, boolean singleBar, boolean changeColor) {
if (view == null) {
return;
}
if (singleBar) {
if (changeColor) {
if (progress < 0.5) {
view.setBackgroundColor(context.getResources().getColor(R.color.progressbar_1));
} else if (progress < 0.65) {
view.setBackgroundColor(context.getResources().getColor(R.color.progressbar_2));
} else if (progress < 0.75) {
view.setBackgroundColor(context.getResources().getColor(R.color.progressbar_3));
} else {
view.setBackgroundColor(context.getResources().getColor(R.color.progressbar_4));
}
}
FrameLayout.LayoutParams lparam = new FrameLayout.LayoutParams(0, 0);
lparam.height = y;
lparam.width = (int) (maxX * progress);
view.setLayoutParams(lparam);
} else {
LinearLayout.LayoutParams lparam = new LinearLayout.LayoutParams(0, 0);
lparam.height = y;
lparam.width = (int) (maxX * progress);
view.setLayoutParams(lparam);
}
} */
/**
* MD5 sum of file.
* Equivalent to checksum(open(os.path.join(mdir, file), "rb").read()))
*
* @param path The full path to the file
* @return A string of length 32 containing the hexadecimal representation of the MD5 checksum of the contents
* of the file
*/
public static String fileChecksum(String path) {
byte[] bytes = null;
try {
File file = new File(path);
if (file != null && file.isFile()) {
bytes = new byte[(int)file.length()];
FileInputStream fin = new FileInputStream(file);
fin.read(bytes);
}
} catch (FileNotFoundException e) {
log.error("Can't find file " + path + " to calculate its checksum");
} catch (IOException e) {
log.error("Can't read file " + path + " to calculate its checksum");
}
if (bytes == null) {
log.warn("File " + path + " appears to be empty");
return "";
}
MessageDigest md = null;
byte[] digest = null;
try {
md = MessageDigest.getInstance("MD5");
digest = md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
log.error("Utils.checksum: No such algorithm. " + e.getMessage());
throw new RuntimeException(e);
}
BigInteger biginteger = new BigInteger(1, digest);
String result = biginteger.toString(16);
// pad with zeros to length of 32
if (result.length() < 32) {
result = "00000000000000000000000000000000".substring(0, 32 - result.length()) + result;
}
return result;
}
/**
* Calculate the UTC offset
*/
public static double utcOffset() {
Calendar cal = Calendar.getInstance();
// 4am
return 4 * 60 * 60 - (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000;
}
/**
* Adds a menu item to the given menu.
*/
/*
public static MenuItem addMenuItem(Menu menu, int groupId, int itemId, int order, int titleRes,
int iconRes) {
MenuItem item = menu.add(groupId, itemId, order, titleRes);
item.setIcon(iconRes);
return item;
} */
/**
* Adds a menu item to the given menu and marks it as a candidate to be in the action bar.
*/
/*
public static MenuItem addMenuItemInActionBar(Menu menu, int groupId, int itemId, int order,
int titleRes, int iconRes) {
MenuItem item = addMenuItem(menu, groupId, itemId, order, titleRes, iconRes);
setShowAsActionIfRoom(item);
return item;
} */
/**
* Sets the menu item to appear in the action bar via reflection.
* <p>
* This method uses reflection so that it works on all platforms. It any error occurs, assume
* the action bar is not available and just proceed.
*/
/*
private static void setShowAsActionIfRoom(MenuItem item) {
try {
Field showAsActionIfRoom = item.getClass().getField("SHOW_AS_ACTION_IF_ROOM");
Method setShowAsAction = item.getClass().getMethod("setShowAsAction", int.class);
setShowAsAction.invoke(item, showAsActionIfRoom.get(null));
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
} catch (NoSuchFieldException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
} catch (NullPointerException e) {
}
} */
/** Returns the filename without the extension. */
public static String removeExtension(String filename) {
int dotPosition = filename.lastIndexOf('.');
if (dotPosition == -1) {
return filename;
}
return filename.substring(0, dotPosition);
}
/** Returns a list of files for the installed custom fonts. */
/*
public static String[] getCustomFonts(Context context) {
// FIXME #URGENT !!
String deckPath = "";
// String deckPath = preferences.getString("deckPath",
// AnkiDroidApp.getStorageDirectory() + "/AnkiDroid");
// FIXME #URGENT !!
String fontsPath = deckPath + "/fonts/";
File fontsDir = new File(fontsPath);
int fontsCount = 0;
File[] fontsList = null;
if (fontsDir.exists() && fontsDir.isDirectory()) {
fontsCount = fontsDir.listFiles().length;
fontsList = fontsDir.listFiles();
}
String[] ankiDroidFonts = null;
String assetPath = "/android_asset/fonts/";
int adFontsCount = 0;
try {
ankiDroidFonts = context.getAssets().list("fonts");
adFontsCount = ankiDroidFonts.length;
} catch (IOException e) {
log.error("Error on retrieving ankidroid fonts: " + e);
}
String[] fonts = new String[fontsCount + adFontsCount];
for (int i = 0; i < fontsCount; i++) {
fonts[i] = fontsList[i].getAbsolutePath();
}
for (int i = fontsCount; i < fonts.length; i++) {
fonts[i] = assetPath + ankiDroidFonts[i - fontsCount];
}
if (fonts.length > 0) {
return fonts;
} else {
return new String[0];
}
} */
}