/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
* Copyright (c) 2011, 2012, FrostWire(TM). All rights reserved.
*
* 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.bt.download.android.gui;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import com.bt.download.android.core.*;
import com.bt.download.android.gui.transfers.Transfers;
import org.apache.commons.io.FilenameUtils;
import org.xmlpull.v1.XmlPullParser;
import android.app.Application;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.provider.BaseColumns;
import android.provider.MediaStore.MediaColumns;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.WindowManager;
import com.bt.download.android.core.player.EphemeralPlaylist;
import com.bt.download.android.core.player.PlaylistItem;
import com.bt.download.android.core.providers.TableFetcher;
import com.bt.download.android.core.providers.TableFetchers;
import com.bt.download.android.core.providers.UniversalStore;
import com.bt.download.android.core.providers.UniversalStore.Applications;
import com.bt.download.android.core.providers.UniversalStore.Applications.ApplicationsColumns;
import com.bt.download.android.core.providers.UniversalStore.Sharing;
import com.bt.download.android.core.providers.UniversalStore.Sharing.SharingColumns;
import com.bt.download.android.gui.util.Apk;
import com.frostwire.util.StringUtils;
import com.frostwire.localpeer.Finger;
import com.frostwire.localpeer.ScreenMetrics;
import com.frostwire.util.DirectoryUtils;
/**
* The Librarian is in charge of:
* -> Keeping track of what files we're sharing or not.
* -> Indexing the files we're sharing.
* -> Searching for files we're sharing.
*
* @author gubatron
* @author aldenml
*
*/
public final class Librarian {
private static final String TAG = "FW.Librarian";
private final Application context;
private final FileCountCache[] cache; // it is an array for performance reasons
private static Librarian instance;
public synchronized static void create(Application context) {
if (instance != null) {
return;
}
instance = new Librarian(context);
}
public static Librarian instance() {
if (instance == null) {
throw new CoreRuntimeException("Librarian not created");
}
return instance;
}
private Librarian(Application context) {
this.context = context;
this.cache = new FileCountCache[] { new FileCountCache(), new FileCountCache(), new FileCountCache(), new FileCountCache(), new FileCountCache(), new FileCountCache() };
}
public List<FileDescriptor> getFiles(byte fileType, int offset, int pageSize, boolean sharedOnly) {
return getFiles(offset, pageSize, TableFetchers.getFetcher(fileType), sharedOnly);
}
public List<FileDescriptor> getFiles(byte fileType, String where, String[] whereArgs) {
return getFiles(0, Integer.MAX_VALUE, TableFetchers.getFetcher(fileType), where, whereArgs, false);
}
/**
* Returns the total number of shared files by this peer. note: each of the
* number of shared files (by type) is stored on _cacheNumFiles, this
* function returns the sum of this cache.
*
* @return
*/
public int getNumFiles() {
int result = 0;
for (byte i = 0; i < 6; i++) {
//update numbers if you have to.
if (!cache[i].cacheValid(true)) {
cache[i].updateShared(getNumFiles(i, true));
}
result += cache[i].shared;
}
return result < 0 ? 0 : result;
}
/**
*
* @param fileType
* @param onlyShared - If false, forces getting all files, shared or unshared.
* @return
*/
public int getNumFiles(byte fileType, boolean onlyShared) {
TableFetcher fetcher = TableFetchers.getFetcher(fileType);
if (cache[fileType].cacheValid(onlyShared)) {
return cache[fileType].getCount(onlyShared);
}
Cursor c = null;
int result = 0;
int numFiles = 0;
try {
ContentResolver cr = context.getContentResolver();
c = cr.query(fetcher.getContentUri(), new String[] { BaseColumns._ID }, null, null, null);
numFiles = c != null ? c.getCount() : 0;
} catch (Exception e) {
Log.e(TAG, "Failed to get num of files", e);
} finally {
if (c != null) {
c.close();
}
}
result = onlyShared ? (getSharedFiles(fileType).size()) : numFiles;
updateCacheNumFiles(fileType, result, onlyShared);
return result;
}
public FileDescriptor getFileDescriptor(byte fileType, int fileId) {
return getFileDescriptor(fileType, fileId, true);
}
public FileDescriptor getFileDescriptor(byte fileType, int fileId, boolean sharedOnly) {
List<FileDescriptor> fds = getFiles(0, 1, TableFetchers.getFetcher(fileType), BaseColumns._ID + "=?", new String[] { String.valueOf(fileId) }, sharedOnly);
if (fds.size() > 0) {
return fds.get(0);
} else {
return null;
}
}
public String renameFile(FileDescriptor fd, String newFileName) {
try {
String filePath = fd.filePath;
File oldFile = new File(filePath);
String ext = FilenameUtils.getExtension(filePath);
File newFile = new File(oldFile.getParentFile(), newFileName + '.' + ext);
ContentResolver cr = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaColumns.DATA, newFile.getAbsolutePath());
values.put(MediaColumns.DISPLAY_NAME, FilenameUtils.getBaseName(newFileName));
values.put(MediaColumns.TITLE, FilenameUtils.getBaseName(newFileName));
TableFetcher fetcher = TableFetchers.getFetcher(fd.fileType);
cr.update(fetcher.getContentUri(), values, BaseColumns._ID + "=?", new String[] { String.valueOf(fd.id) });
oldFile.renameTo(newFile);
return newFile.getAbsolutePath();
} catch (Throwable e) {
Log.e(TAG, "Failed to rename file: " + fd, e);
}
return null;
}
public void updateSharedStates(byte fileType, List<FileDescriptor> fds) {
if (fds == null || fds.size() == 0) {
return;
}
try {
ContentResolver cr = context.getContentResolver();
Set<Integer> sharedFiles = getSharedFiles(fds.get(0).fileType);
int size = fds.size();
// we know this is a slow process, we can improve it later
for (int i = 0; i < size; i++) {
FileDescriptor fileDescriptor = fds.get(i);
ContentValues contentValues = new ContentValues();
contentValues.put(SharingColumns.SHARED, fileDescriptor.shared ? 1 : 0);
// Is this a NEW Shared File?
if (!sharedFiles.contains(fileDescriptor.id) && fileDescriptor.shared) {
// insert in table as unshared.
contentValues.put(SharingColumns.FILE_ID, fileDescriptor.id);
contentValues.put(SharingColumns.FILE_TYPE, fileType);
cr.insert(Sharing.Media.CONTENT_URI, contentValues);
} else {
// everything else is an update
cr.update(Sharing.Media.CONTENT_URI, contentValues, SharingColumns.FILE_ID + "=? AND " + SharingColumns.FILE_TYPE + "=?", new String[] { String.valueOf(fileDescriptor.id), String.valueOf(fileType) });
}
}
invalidateCountCache(fileType);
} catch (Throwable e) {
Log.e(TAG, "Failed to update share states", e);
}
}
public void deleteFiles(byte fileType, Collection<FileDescriptor> fds) {
List<Integer> ids = new ArrayList<Integer>(fds.size());
for (FileDescriptor fd : fds) {
if (new File(fd.filePath).delete()) {
ids.add(fd.id);
deleteSharedState(fd.fileType, fd.id);
}
}
try {
ContentResolver cr = context.getContentResolver();
TableFetcher fetcher = TableFetchers.getFetcher(fileType);
cr.delete(fetcher.getContentUri(), MediaColumns._ID + " IN " + StringUtils.buildSet(ids), null);
} catch (Throwable e) {
Log.e(TAG, "Failed to delete files from media store", e);
}
invalidateCountCache(fileType);
}
public void scan(File file) {
scan(file, Transfers.getIgnorableFiles());
}
public Finger finger(boolean local) {
Finger finger = new Finger();
finger.uuid = ConfigurationManager.instance().getUUIDString();
finger.nickname = ConfigurationManager.instance().getNickname();
finger.frostwireVersion = Constants.FROSTWIRE_VERSION_STRING;
finger.totalShared = getNumFiles();
finger.deviceVersion = Build.VERSION.RELEASE;
finger.deviceModel = Build.MODEL;
finger.deviceProduct = Build.PRODUCT;
finger.deviceName = Build.DEVICE;
finger.deviceManufacturer = Build.MANUFACTURER;
finger.deviceBrand = Build.BRAND;
finger.deviceScreen = readScreenMetrics();
finger.numSharedAudioFiles = getNumFiles(Constants.FILE_TYPE_AUDIO, true);
finger.numSharedVideoFiles = getNumFiles(Constants.FILE_TYPE_VIDEOS, true);
finger.numSharedPictureFiles = getNumFiles(Constants.FILE_TYPE_PICTURES, true);
finger.numSharedDocumentFiles = getNumFiles(Constants.FILE_TYPE_DOCUMENTS, true);
finger.numSharedApplicationFiles = getNumFiles(Constants.FILE_TYPE_APPLICATIONS, true);
finger.numSharedRingtoneFiles = getNumFiles(Constants.FILE_TYPE_RINGTONES, true);
if (local) {
finger.numTotalAudioFiles = getNumFiles(Constants.FILE_TYPE_AUDIO, false);
finger.numTotalVideoFiles = getNumFiles(Constants.FILE_TYPE_VIDEOS, false);
finger.numTotalPictureFiles = getNumFiles(Constants.FILE_TYPE_PICTURES, false);
finger.numTotalDocumentFiles = getNumFiles(Constants.FILE_TYPE_DOCUMENTS, false);
finger.numTotalApplicationFiles = getNumFiles(Constants.FILE_TYPE_APPLICATIONS, false);
finger.numTotalRingtoneFiles = getNumFiles(Constants.FILE_TYPE_RINGTONES, false);
} else {
finger.numTotalAudioFiles = finger.numSharedAudioFiles;
finger.numTotalVideoFiles = finger.numSharedVideoFiles;
finger.numTotalPictureFiles = finger.numSharedPictureFiles;
finger.numTotalDocumentFiles = finger.numSharedDocumentFiles;
finger.numTotalApplicationFiles = finger.numSharedApplicationFiles;
finger.numTotalRingtoneFiles = finger.numSharedRingtoneFiles;
}
return finger;
}
public void syncApplicationsProvider() {
if (!isExternalStorageMounted()) {
return;
}
Thread t = new Thread(new Runnable() {
public void run() {
syncApplicationsProviderSupport();
}
});
t.setName("syncApplicationsProvider");
t.setDaemon(true);
t.start();
}
public void syncMediaStore() {
if (!isExternalStorageMounted()) {
return;
}
Thread t = new Thread(new Runnable() {
public void run() {
syncMediaStoreSupport();
}
});
t.setName("syncMediaStore");
t.setDaemon(true);
t.start();
}
public boolean isExternalStorageMounted() {
return com.bt.download.android.util.SystemUtils.isPrimaryExternalStorageMounted();
}
public EphemeralPlaylist createEphemeralPlaylist(FileDescriptor fd) {
List<FileDescriptor> fds = Librarian.instance().getFiles(Constants.FILE_TYPE_AUDIO, FilenameUtils.getPath(fd.filePath), false);
if (fds.size() == 0) { // just in case
Log.w(TAG, "Logic error creating ephemeral playlist");
fds.add(fd);
}
EphemeralPlaylist playlist = new EphemeralPlaylist(fds);
playlist.setNextItem(new PlaylistItem(fd));
return playlist;
}
public void invalidateCountCache() {
for (FileCountCache c : cache) {
if (c != null) {
c.lastTimeCachedShared = 0;
c.lastTimeCachedOnDisk = 0;
}
}
broadcastRefreshFinger();
}
/**
* @param fileType
*/
void invalidateCountCache(byte fileType) {
cache[fileType].lastTimeCachedShared = 0;
cache[fileType].lastTimeCachedOnDisk = 0;
broadcastRefreshFinger();
}
private void broadcastRefreshFinger() {
context.sendBroadcast(new Intent(Constants.ACTION_REFRESH_FINGER));
PeerManager.instance().updateLocalPeer();
}
private void syncApplicationsProviderSupport() {
try {
List<FileDescriptor> fds = Librarian.instance().getFiles(Constants.FILE_TYPE_APPLICATIONS, 0, Integer.MAX_VALUE, false);
int packagesSize = fds.size();
String[] packages = new String[packagesSize];
for (int i = 0; i < packagesSize; i++) {
packages[i] = fds.get(i).album;
}
Arrays.sort(packages);
List<ApplicationInfo> applications = context.getPackageManager().getInstalledApplications(0);
int size = applications.size();
ArrayList<String> newPackagesList = new ArrayList<String>(size);
for (int i = 0; i < size; i++) {
ApplicationInfo appInfo = applications.get(i);
try {
if (appInfo == null) {
continue;
}
newPackagesList.add(appInfo.packageName);
File f = new File(appInfo.sourceDir);
if (!f.canRead()) {
continue;
}
int index = Arrays.binarySearch(packages, appInfo.packageName);
if (index >= 0) {
continue;
}
String data = appInfo.sourceDir;
String title = appInfo.packageName;
String packageName = appInfo.packageName;
String version = "";
Apk apk = new Apk(context, appInfo.sourceDir);
String[] result = parseApk(apk);
if (result != null) {
if (result[1] == null) {
continue;
}
title = result[1];
version = result[0];
}
ContentValues cv = new ContentValues();
cv.put(ApplicationsColumns.DATA, data);
cv.put(ApplicationsColumns.SIZE, f.length());
cv.put(ApplicationsColumns.TITLE, title);
cv.put(ApplicationsColumns.MIME_TYPE, Constants.MIME_TYPE_ANDROID_PACKAGE_ARCHIVE);
cv.put(ApplicationsColumns.VERSION, version);
cv.put(ApplicationsColumns.PACKAGE_NAME, packageName);
ContentResolver cr = context.getContentResolver();
Uri uri = cr.insert(Applications.Media.CONTENT_URI, cv);
if (appInfo.icon != 0) {
try {
InputStream is = null;
OutputStream os = null;
try {
is = apk.openRawResource(appInfo.icon);
os = cr.openOutputStream(uri);
byte[] buff = new byte[4 * 1024];
int n = 0;
while ((n = is.read(buff, 0, buff.length)) != -1) {
os.write(buff, 0, n);
}
} finally {
if (os != null) {
os.close();
}
if (is != null) {
is.close();
}
}
} catch (Throwable e) {
Log.e(TAG, "Can't retrieve icon image for application " + appInfo.packageName);
}
}
} catch (Throwable e) {
Log.e(TAG, "Error retrieving information for application " + appInfo.packageName);
}
}
// clean uninstalled applications
String[] newPackages = newPackagesList.toArray(new String[0]);
Arrays.sort(newPackages);
// simple way n * log(n)
for (int i = 0; i < packagesSize; i++) {
String packageName = packages[i];
if (Arrays.binarySearch(newPackages, packageName) < 0) {
ContentResolver cr = context.getContentResolver();
cr.delete(Applications.Media.CONTENT_URI, ApplicationsColumns.PACKAGE_NAME + " LIKE '%" + packageName + "%'", null);
}
}
} catch (Throwable e) {
Log.e(TAG, "Error performing initial applications provider synchronization with device", e);
}
}
private void syncMediaStoreSupport() {
Set<File> ignorableFiles = Transfers.getIgnorableFiles();
syncMediaStore(Constants.FILE_TYPE_AUDIO, ignorableFiles);
syncMediaStore(Constants.FILE_TYPE_PICTURES, ignorableFiles);
syncMediaStore(Constants.FILE_TYPE_VIDEOS, ignorableFiles);
syncMediaStore(Constants.FILE_TYPE_RINGTONES, ignorableFiles);
scan(SystemPaths.getSaveDirectory(Constants.FILE_TYPE_DOCUMENTS));
}
private void syncMediaStore(byte fileType, Set<File> ignorableFiles) {
TableFetcher fetcher = TableFetchers.getFetcher(fileType);
Cursor c = null;
try {
ContentResolver cr = context.getContentResolver();
String where = MediaColumns.DATA + " LIKE ?";
String[] whereArgs = new String[]{SystemPaths.getAppStorage().getAbsolutePath() + "%"};
c = cr.query(fetcher.getContentUri(), new String[]{MediaColumns._ID, MediaColumns.DATA}, where, whereArgs, null);
if (c == null) {
return;
}
int idCol = c.getColumnIndex(MediaColumns._ID);
int pathCol = c.getColumnIndex(MediaColumns.DATA);
List<Integer> ids = new ArrayList<Integer>();
while (c.moveToNext()) {
int id = Integer.valueOf(c.getString(idCol));
String path = c.getString(pathCol);
if (ignorableFiles.contains(new File(path))) {
ids.add(id);
}
}
cr.delete(fetcher.getContentUri(), MediaColumns._ID + " IN " + StringUtils.buildSet(ids), null);
} catch (Throwable e) {
Log.e(TAG, "General failure during sync of MediaStore", e);
} finally {
if (c != null) {
c.close();
}
}
}
private List<FileDescriptor> getFiles(int offset, int pageSize, TableFetcher fetcher, boolean sharedOnly) {
return getFiles(offset, pageSize, fetcher, null, null, sharedOnly);
}
/**
* Returns a list of Files.
*
* @param offset
* - from where (starting at 0)
* @param pageSize
* - how many results
* @param fetcher
* - An implementation of TableFetcher
* @param sharedOnly
* - if true, retrieves only the fine grained shared files.
*
* @return List<FileDescriptor>
*/
private List<FileDescriptor> getFiles(int offset, int pageSize, TableFetcher fetcher, String where, String[] whereArgs, boolean sharedOnly) {
List<FileDescriptor> result = new ArrayList<FileDescriptor>();
Cursor c = null;
Set<Integer> sharedIds = getSharedFiles(fetcher.getFileType());
try {
ContentResolver cr = context.getContentResolver();
String[] columns = fetcher.getColumns();
String sort = fetcher.getSortByExpression();
c = cr.query(fetcher.getContentUri(), columns, where, whereArgs, sort);
if (c == null || !c.moveToPosition(offset)) {
return result;
}
fetcher.prepare(c);
int count = 1;
do {
FileDescriptor fd = fetcher.fetch(c);
fd.shared = sharedIds.contains(fd.id);
if (sharedOnly && !fd.shared) {
continue;
}
result.add(fd);
} while (c.moveToNext() && count++ < pageSize);
} catch (Throwable e) {
Log.e(TAG, "General failure getting files", e);
} finally {
if (c != null) {
c.close();
}
}
return result;
}
public List<FileDescriptor> getFiles(String filepath, boolean exactPathMatch) {
return getFiles(getFileType(filepath, true), filepath, exactPathMatch);
}
/**
*
* @param filepath
* @param exactPathMatch - set it to false and pass an incomplete filepath prefix to get files in a folder for example.
* @return
*/
public List<FileDescriptor> getFiles(byte fileType, String filepath, boolean exactPathMatch) {
String where = MediaColumns.DATA + " LIKE ?";
String[] whereArgs = new String[] { (exactPathMatch) ? filepath : "%" + filepath + "%" };
List<FileDescriptor> fds = Librarian.instance().getFiles(fileType, where, whereArgs);
return fds;
}
private Pair<List<Integer>, List<String>> getAllFiles(byte fileType) {
Pair<List<Integer>, List<String>> result = new Pair<List<Integer>, List<String>>(new ArrayList<Integer>(), new ArrayList<String>());
Cursor c = null;
try {
TableFetcher fetcher = TableFetchers.getFetcher(fileType);
ContentResolver cr = context.getContentResolver();
c = cr.query(fetcher.getContentUri(), new String[] { BaseColumns._ID, MediaColumns.DATA }, null, null, BaseColumns._ID);
if (c != null) {
while (c.moveToNext()) {
result.first.add(c.getInt(0));
result.second.add(c.getString(1));
}
}
} catch (Throwable e) {
Log.e(TAG, "General failure getting all files", e);
} finally {
if (c != null) {
c.close();
}
}
return result;
}
private Set<Integer> getSharedFiles(byte fileType) {
TreeSet<Integer> result = new TreeSet<Integer>();
List<Integer> delete = new ArrayList<Integer>();
Cursor c = null;
try {
ContentResolver cr = context.getContentResolver();
String[] columns = new String[] { SharingColumns.FILE_ID, SharingColumns._ID };
c = cr.query(Sharing.Media.CONTENT_URI, columns, SharingColumns.SHARED + "=1 AND " + SharingColumns.FILE_TYPE + "=?", new String[] { String.valueOf(fileType) }, null);
if (c == null || !c.moveToFirst()) {
return result;
}
int fileIdCol = c.getColumnIndex(SharingColumns.FILE_ID);
int sharingIdCol = c.getColumnIndex(SharingColumns._ID);
Pair<List<Integer>, List<String>> pair = getAllFiles(fileType);
List<Integer> files = pair.first;
List<String> paths = pair.second;
do {
int fileId = c.getInt(fileIdCol);
int sharingId = c.getInt(sharingIdCol);
int index = Collections.binarySearch(files, fileId);
try {
if (index >= 0) {
File f = new File(paths.get(index));
if (f.exists() && f.isFile()) {
result.add(fileId);
} else {
delete.add(sharingId);
}
} else {
delete.add(sharingId);
}
} catch (Throwable e) {
Log.e(TAG, "Error checking fileId: " + fileId + ", fileType: " + fileId);
}
} while (c.moveToNext());
} catch (Throwable e) {
Log.e(TAG, "General failure getting shared/unshared files ids", e);
} finally {
if (c != null) {
c.close();
}
if (delete.size() > 0) {
deleteSharedStates(delete);
}
}
return result;
}
private void deleteSharedState(byte fileType, int fileId) {
try {
ContentResolver cr = context.getContentResolver();
int deleted = cr.delete(UniversalStore.Sharing.Media.CONTENT_URI, SharingColumns.FILE_ID + "= ? AND " + SharingColumns.FILE_TYPE + " = ?", new String[] { String.valueOf(fileId), String.valueOf(fileType) });
Log.d(TAG, "deleteSharedState " + deleted + " rows (fileType: " + fileType + ", fileId: " + fileId + " )");
} catch (Throwable e) {
Log.e(TAG, "Failed to delete shared state for fileType=" + fileType + ", fileId=" + fileId, e);
}
}
private void deleteSharedStates(List<Integer> sharingIds) {
try {
ContentResolver cr = context.getContentResolver();
int deleted = cr.delete(UniversalStore.Sharing.Media.CONTENT_URI, SharingColumns._ID + " IN " + StringUtils.buildSet(sharingIds), null);
Log.d(TAG, "Deleted " + deleted + " shared states");
} catch (Throwable e) {
Log.e(TAG, "Failed to delete shared states", e);
}
}
/**
* Updates the number of files for this type.
* @param fileType
*/
private void updateCacheNumFiles(byte fileType, int num, boolean sharedOnly) {
if (sharedOnly) {
cache[fileType].updateShared(num);
} else {
cache[fileType].updateOnDisk(num);
}
}
/**
* This function returns an array of string in the following order: version name, label
* @param apk
* @return
*/
private String[] parseApk(Apk apk) {
try {
String[] result = new String[3];
XmlResourceParser parser = apk.getAndroidManifest();
boolean manifestParsed = true;
boolean applicationParsed = false;
while (!manifestParsed || !applicationParsed) {
int type = parser.next();
if (type == XmlPullParser.END_DOCUMENT) {
break;
}
switch (type) {
case XmlPullParser.START_TAG:
String tagName = parser.getName();
if (tagName.equals("manifest")) {
String versionName = parser.getAttributeValue("http://schemas.android.com/apk/res/android", "versionName");
if (versionName != null && versionName.startsWith("@")) {
versionName = apk.getString(Integer.parseInt(versionName.substring(1)));
}
result[0] = versionName;
manifestParsed = true;
}
if (tagName.equals("application")) {
String label = parser.getAttributeValue("http://schemas.android.com/apk/res/android", "label");
if (label != null && label.startsWith("@")) {
label = apk.getString(Integer.parseInt(label.substring(1)));
}
result[1] = label;
applicationParsed = true;
}
break;
}
}
parser.close();
return result;
} catch (Throwable e) {
return null;
}
}
private void scan(File file, Set<File> ignorableFiles) {
//if we just have a single file, do it the old way
if (file.isFile()) {
if (ignorableFiles.contains(file)) {
return;
}
new UniversalScanner(context).scan(file.getAbsolutePath());
} else if (file.isDirectory() && file.canRead()) {
Collection<File> flattenedFiles = DirectoryUtils.getAllFolderFiles(file, null);
if (ignorableFiles != null && !ignorableFiles.isEmpty()) {
flattenedFiles.removeAll(ignorableFiles);
}
if (flattenedFiles != null && !flattenedFiles.isEmpty()) {
new UniversalScanner(context).scan(flattenedFiles);
}
}
}
public ScreenMetrics readScreenMetrics() {
ScreenMetrics sm = new ScreenMetrics();
try {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
context.getResources().getDisplayMetrics();
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
sm.densityDpi = dm.densityDpi;
sm.heightPixels = dm.heightPixels;
sm.widthPixels = dm.widthPixels;
sm.xdpi = dm.xdpi;
sm.ydpi = dm.ydpi;
} catch (Throwable e) {
Log.e(TAG, "Unable to get the device display dimensions", e);
}
return sm;
}
public double getScreenSizeInInches() {
double screenInches = 0;
try {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
context.getResources().getDisplayMetrics();
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
double x = Math.pow(dm.widthPixels / dm.xdpi, 2);
double y = Math.pow(dm.heightPixels / dm.ydpi, 2);
screenInches = Math.sqrt(x + y);
} catch (Throwable e) {
Log.e(TAG, "Unable to get the device display dimensions", e);
}
return screenInches;
}
private static class FileCountCache {
public int shared;
public int onDisk;
public long lastTimeCachedShared;
public long lastTimeCachedOnDisk;
public FileCountCache() {
shared = 0;
onDisk = 0;
lastTimeCachedShared = 0;
lastTimeCachedOnDisk = 0;
}
public void updateShared(int num) {
shared = num;
lastTimeCachedShared = System.currentTimeMillis();
}
public void updateOnDisk(int num) {
onDisk = num;
lastTimeCachedOnDisk = System.currentTimeMillis();
}
public int getCount(boolean onlyShared) {
return (onlyShared) ? shared : onDisk;
}
public boolean cacheValid(boolean onlyShared) {
long delta = System.currentTimeMillis() - ((onlyShared) ? lastTimeCachedShared : lastTimeCachedOnDisk);
return delta < Constants.LIBRARIAN_FILE_COUNT_CACHE_TIMEOUT;
}
}
private FileDescriptor getFileDescriptor(File f) {
FileDescriptor fd = null;
if (f.exists()) {
List<FileDescriptor> files = getFiles(f.getAbsolutePath(), false);
if (!files.isEmpty()) {
fd = files.get(0);
}
}
return fd;
}
public FileDescriptor getFileDescriptor(Uri uri) {
FileDescriptor fd = null;
try {
if (uri != null) {
if (uri.toString().startsWith("file://")) {
fd = getFileDescriptor(new File(uri.getPath()));
} else {
TableFetcher fetcher = TableFetchers.getFetcher(uri);
fd = new FileDescriptor();
fd.fileType = fetcher.getFileType();
fd.id = Integer.valueOf(uri.getLastPathSegment());
}
}
} catch (Throwable e) {
fd = null;
// sometimes uri.getLastPathSegment() is not an integer
e.printStackTrace();
}
return fd;
}
private byte getFileType(String filename, boolean returnTorrentsAsDocument) {
byte result = Constants.FILE_TYPE_DOCUMENTS;
MediaType mt = MediaType.getMediaTypeForExtension(FilenameUtils.getExtension(filename));
if (mt != null) {
result = (byte) mt.getId();
}
if (returnTorrentsAsDocument && result == Constants.FILE_TYPE_TORRENTS) {
result = Constants.FILE_TYPE_DOCUMENTS;
}
return result;
}
}