package gem.kevin.innov;
import gem.kevin.util.DataUtil;
import gem.kevin.util.FileUtil;
import gem.kevin.util.StorageUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import com.sparseboolean.ifexplorer.DirectoryMonitor;
import android.util.Log;
public class FilePathUrlManager implements DirectoryMonitor.FileEventHandler {
public interface FilePathObserver {
public void notifyOnAllEvents(String path);
}
private static final String TAG = "IfExlporer-FilePathUrlManager";
private static final int BUFFER = 2048;
public static final int SORT_NONE = 0;
public static final int SORT_ALPHA = 1;
private boolean mShowHiddenFiles = false;
private int mPreSortType = SORT_ALPHA;
private long mDirSize = 0;
private ArrayList<String> mUrlHistoryList;
private int mHistoryPosition = 0;
private ArrayList<String> mCurrentUrlContent;
private String mLastMonitoringPath = null;
private DirectoryMonitor mCurrentDirMonitor = null;
private FilePathObserver mFilePathObserver;
private ArrayList<String> mAudioPatternFilePaths = new ArrayList<String>();
private ArrayList<String> mVideoPatternFilePaths = new ArrayList<String>();
private ArrayList<String> mImagePatternFilePaths = new ArrayList<String>();
private ArrayList<String> mApkPatternFilePaths = new ArrayList<String>();
private HashMap<String, ArrayList<String>> mSearchPatternFilePaths = new HashMap<String, ArrayList<String>>();
@SuppressWarnings("rawtypes")
private static final Comparator mAlphComparator = new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
return arg0.toLowerCase().compareTo(arg1.toLowerCase());
}
};
public static final String ROOT_DIR = "/";
public static String buildUrl(String dirPath, String query, String pattern) {
String field_path = (dirPath != null) ? (URL_FIELD_DIR_PATH
+ URL_KEY_VALUE_DIVIDER + dirPath + URL_FIELD_DIVIDER) : "";
String field_name = (query != null) ? (URL_FIELD_QUERY
+ URL_KEY_VALUE_DIVIDER + query + URL_FIELD_DIVIDER) : "";
String field_pattern = (pattern != null) ? pattern + URL_FIELD_DIVIDER
: "";
return field_path + field_name + field_pattern;
}
public static String getDir(String url) {
if (isExistingDirPath(url)) {
return url;
}
String fields[] = url.split(URL_FIELD_DIVIDER);
if (fields != null && fields.length > 0) {
for (String field : fields) {
if (field.contains(URL_FIELD_DIR_PATH)) {
String[] pathKeyValue = field.split(URL_KEY_VALUE_DIVIDER);
if (pathKeyValue != null && pathKeyValue.length == 2) {
String[] paths = pathKeyValue[1]
.split(URL_MULTI_VALUE_DIVIDER);
if (paths != null && paths.length > 0) {
// If multiple dirs exist, we choose the first one
Log.i(TAG, "Get dir: " + paths[0]);
return paths[0];
}
}
}
}
}
return null;
}
public static String getQueryStr(String url) {
String fields[] = url.split(URL_FIELD_DIVIDER);
if (fields != null && fields.length > 0) {
for (String field : fields) {
if (field.contains(URL_FIELD_QUERY)) {
String[] keyValue = field.split(URL_KEY_VALUE_DIVIDER);
if (keyValue != null && keyValue.length == 2) {
return keyValue[1];
}
}
}
}
return null;
}
public static String getSmartPattern(String url) {
String fields[] = url.split(URL_FIELD_DIVIDER);
if (fields != null && fields.length > 0) {
for (String field : fields) {
if (field.startsWith(SMART_PREFIX)) {
Log.i(TAG, "Smart patter is: " + field);
return field;
}
}
}
return null;
}
/** converts integer from wifi manager to an IP address.
*
* @param des
* @return */
public static String integerToIPAddress(int ip) {
String ascii_address = "";
int[] num = new int[4];
num[0] = (ip & 0xff000000) >> 24;
num[1] = (ip & 0x00ff0000) >> 16;
num[2] = (ip & 0x0000ff00) >> 8;
num[3] = ip & 0x000000ff;
ascii_address = num[0] + "." + num[1] + "." + num[2] + "." + num[3];
return ascii_address;
}
public static boolean isExistingDirPath(String path) {
if (!path.startsWith("/")) {
return false;
}
File file = new File(path);
return file.exists() && file.isDirectory();
}
public static boolean isSearchUrl(String url) {
if (url == null) {
return false;
}
if (url.contains(URL_FIELD_QUERY + URL_KEY_VALUE_DIVIDER)
|| url.contains(SMART_PREFIX)) {
return true;
} else {
return false;
}
}
// Inspired by org.apache.commons.io.FileUtils.isSymlink()
private static boolean isSymlink(File file) throws IOException {
File fileInCanonicalDir = null;
if (file.getParent() == null) {
fileInCanonicalDir = file;
} else {
File canonicalDir = file.getParentFile().getCanonicalFile();
fileInCanonicalDir = new File(canonicalDir, file.getName());
}
return !fileInCanonicalDir.getCanonicalFile().equals(
fileInCanonicalDir.getAbsoluteFile());
}
private final Comparator size = new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
String dir = getCurrentUrl();
Long first = new File(dir + "/" + arg0).length();
Long second = new File(dir + "/" + arg1).length();
return first.compareTo(second);
}
};
private final Comparator type = new Comparator<String>() {
@Override
public int compare(String arg0, String arg1) {
String ext = null;
String ext2 = null;
int ret;
try {
ext = arg0.substring(arg0.lastIndexOf(".") + 1, arg0.length())
.toLowerCase();
ext2 = arg1.substring(arg1.lastIndexOf(".") + 1, arg1.length())
.toLowerCase();
} catch (IndexOutOfBoundsException e) {
return 0;
}
ret = ext.compareTo(ext2);
if (ret == 0) {
return arg0.toLowerCase().compareTo(arg1.toLowerCase());
}
return ret;
}
};
public static final String PATTERN_ALL = "*";
/* smart folder path suffix */
public static final String SMART_APK = "type: apk";
public static final String SMART_MOVIES = "type: video";
public static final String SMART_IMAGE = "type: image";
public static final String SMART_MUSIC = "type: audio";
public static final String SMART_FILE = "type: file";
public static final String SMART_PREFIX = "type: ";
/* external storage volumes mount points parent directory */
public static final String URL_MULTI_VALUE_DIVIDER = " + ";
public static final String URL_FIELD_QUERY = "query";
public static final String URL_FIELD_DIR_PATH = "dir";
public static final String URL_KEY_VALUE_DIVIDER = ": ";
public static final String URL_FIELD_DIVIDER = " & ";
public static final String URL_PATH_APK = URL_FIELD_DIVIDER + SMART_APK
+ URL_FIELD_DIVIDER;
public static final String URL_PATH_MOVIES = URL_FIELD_DIVIDER
+ SMART_MOVIES + URL_FIELD_DIVIDER;
public static final String URL_PATH_IMAGE = URL_FIELD_DIVIDER + SMART_IMAGE
+ URL_FIELD_DIVIDER;
public static final String URL_PATH_MUSIC = URL_FIELD_DIVIDER + SMART_MUSIC
+ URL_FIELD_DIVIDER;
public static final String URL_PATH_FILE = URL_FIELD_DIVIDER + SMART_FILE
+ URL_FIELD_DIVIDER;
private static FilePathUrlManager sFilePathUrlManager;
public static String buildSearchId(String searchStr, String searchDir) {
return searchStr + URL_KEY_VALUE_DIVIDER + searchDir;
}
public static FilePathUrlManager getLocalFileManager(String initLocation) {
return new FilePathUrlManager(initLocation);
}
public static FilePathUrlManager getSingleton(String initLocation) {
if (sFilePathUrlManager == null) {
sFilePathUrlManager = new FilePathUrlManager(initLocation);
}
return sFilePathUrlManager;
}
public static String parseSearchDir(String searchId) {
String keyValue[] = searchId.split(URL_KEY_VALUE_DIVIDER);
if (keyValue != null && keyValue.length >= 2) {
return keyValue[1];
}
return null;
}
public static String parseSearchStr(String searchId) {
String keyValue[] = searchId.split(URL_KEY_VALUE_DIVIDER);
if (keyValue != null && keyValue.length >= 1) {
return keyValue[0];
}
return null;
}
/** Constructs an object of the class <br>
* this class uses a stack to handle the navigation of directories. */
private FilePathUrlManager(String initLocation) {
mCurrentUrlContent = new ArrayList<String>();
mUrlHistoryList = new ArrayList<String>();
mUrlHistoryList.add(initLocation);
}
/** @param path */
public void createZipFile(String path) {
File dir = new File(path);
String[] list = dir.list();
String name = path.substring(path.lastIndexOf("/"), path.length());
String _path;
if (!dir.canRead() || !dir.canWrite()) {
return;
}
int len = list.length;
if (path.charAt(path.length() - 1) != '/') {
_path = path + "/";
} else {
_path = path;
}
try {
ZipOutputStream zip_out = new ZipOutputStream(
new BufferedOutputStream(new FileOutputStream(_path + name
+ ".zip"), BUFFER));
for (int i = 0; i < len; i++) {
zip_folder(new File(_path + list[i]), zip_out);
}
zip_out.close();
} catch (FileNotFoundException e) {
Log.e("File not found", e.getMessage());
} catch (IOException e) {
Log.e("IOException", e.getMessage());
}
}
/** @param zip_file
* @param directory */
public void extractZipFiles(String zip_file, String directory) {
byte[] data = new byte[BUFFER];
String name, path, zipDir;
ZipEntry entry;
ZipInputStream zipstream;
if (!(directory.charAt(directory.length() - 1) == '/')) {
directory += "/";
}
if (zip_file.contains("/")) {
path = zip_file;
name = path.substring(path.lastIndexOf("/") + 1, path.length() - 4);
zipDir = directory + name + "/";
} else {
path = directory + zip_file;
name = path.substring(path.lastIndexOf("/") + 1, path.length() - 4);
zipDir = directory + name + "/";
}
new File(zipDir).mkdir();
try {
zipstream = new ZipInputStream(new FileInputStream(path));
while ((entry = zipstream.getNextEntry()) != null) {
String buildDir = zipDir;
String[] dirs = entry.getName().split("/");
if (dirs != null && dirs.length > 0) {
for (int i = 0; i < dirs.length - 1; i++) {
buildDir += dirs[i] + "/";
new File(buildDir).mkdir();
}
}
int read = 0;
FileOutputStream out = new FileOutputStream(zipDir
+ entry.getName());
while ((read = zipstream.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, read);
}
zipstream.closeEntry();
out.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/** @param zipName
* @param toDir
* @param fromDir */
public void extractZipFilesFromDir(String zipName, String toDir,
String fromDir) {
if (!(toDir.charAt(toDir.length() - 1) == '/')) {
toDir += "/";
}
if (!(fromDir.charAt(fromDir.length() - 1) == '/')) {
fromDir += "/";
}
String org_path = fromDir + zipName;
extractZipFiles(org_path, toDir);
}
public String getCurrentUrl() {
return mUrlHistoryList.get(mHistoryPosition);
}
public int getCurrentUrlPosition() {
return mHistoryPosition;
}
public int getHistoryPosition() {
return mHistoryPosition;
}
public ArrayList<String> getNextUrlContent(String url, boolean isNewUrl,
boolean forward) {
if (url != null && !url.equals(getCurrentUrl())) {
if (isNewUrl) {
int size = mUrlHistoryList.size();
for (int i = size - 1; i > mHistoryPosition; i--) {
mUrlHistoryList.remove(i);
}
mUrlHistoryList.add(url);
mHistoryPosition = mUrlHistoryList.size() - 1;
}
}
if (forward) {
mHistoryPosition++;
if (mHistoryPosition >= mUrlHistoryList.size()) {
mHistoryPosition = mUrlHistoryList.size() - 1;
}
}
return populateCurrentUrlContent();
}
public ArrayList<String> getPreviousUrlContent() {
mHistoryPosition--;
if (mHistoryPosition < 0) {
mHistoryPosition = 0;
}
return populateCurrentUrlContent();
}
public ArrayList<String> getUrlHistory() {
return mUrlHistoryList;
}
public int getUrlHistoryCount() {
return mUrlHistoryList.size();
}
// implements DirectoryMonitor.FileEventHandler::onAllEvents
@Override
public void onAllEvents(String path) {
if (mFilePathObserver != null) {
mFilePathObserver.notifyOnAllEvents(path);
}
}
public ArrayList<String> refreshUrlContent() {
return getNextUrlContent(getCurrentUrl(), false, false);
}
/*
* kevin@xmic search file of specific type in a directory and return
* sub-directories for caller to relay following searches make sure reserved
* dirs added to sub-directories.
*/
public ArrayList<String> relaySearchFiles(String topDir, String searchStr,
ArrayList<String> typeList, ArrayList<String> accumulateResult,
ArrayList<String> currentResult,
ArrayList<String> reservedSearchDirs) {
Log.i(TAG, "relaySearchFiles - topDir: " + topDir + " searchStr: "
+ searchStr);
ArrayList<String> subDirs = new ArrayList<String>();
File root_dir = new File(topDir);
String[] list = root_dir.list();
if (list != null && root_dir.canRead()) {
int len = list.length;
for (int i = 0; i < len; i++) {
File check = new File(topDir + "/" + list[i]);
// skip symbol link
try {
if (isSymlink(check)) {
continue;
}
} catch (IOException e) {
Log.e(TAG, "Failed to check symbol link!");
}
if (check.isFile()) {
if (typeList != null) {
String extension = DataUtil
.getFileExtensionWithoutDot(check.getPath());
if (typeList.contains(extension.toLowerCase())) {
if (PATTERN_ALL.equals(searchStr)) {
Log.i(TAG, "ADD result: " + check.getPath()
+ " as * match");
accumulateResult.add(check.getPath());
currentResult.add(check.getPath());
} else {
if (searchStr != null
&& check.getName().contains(searchStr)) {
accumulateResult.add(check.getPath());
currentResult.add(check.getPath());
}
}
}
} else {
if (searchStr != null
&& check.getName().contains(searchStr)) {
accumulateResult.add(check.getPath());
currentResult.add(check.getPath());
}
}
}
if (check.isDirectory()) {
if (StorageUtil.getExcludeSearchPath().contains(
check.getAbsolutePath())
|| StorageUtil
.isExcludeSearchPath(
check.getAbsolutePath(),
reservedSearchDirs)) {
continue;
} else {
if (searchStr != null
&& check.getName().contains(searchStr)) {
accumulateResult.add(check.getPath());
currentResult.add(check.getPath());
}
if (check.canRead() && !topDir.equals("/")) {
subDirs.add(check.getPath());
}
}
}
}
}
return subDirs;
}
public void removeInvalidUrls(String invalidUrlPrefix) {
@SuppressWarnings("unchecked")
ArrayList<String> clone = (ArrayList<String>) mUrlHistoryList.clone();
for (int i = 0; i < clone.size(); i++) {
String url = clone.get(i);
if (url.startsWith(invalidUrlPrefix)) {
mUrlHistoryList.remove(url);
}
}
mHistoryPosition = mUrlHistoryList.size() > 0 ? mUrlHistoryList.size() - 1
: 0;
}
/*
* @deprecated
*
* Search file of specific type in a directory
* recursively and make sure reserved dirs be searched if provided This
* method might be time-consuming, so you can use #relaySearchFilesOfType to
* get search right after one level directory scanned.
*/
public boolean searchFilesOfType(String topDir, int type,
ArrayList<String> result, ArrayList<String> reservedSearchDirs) {
ArrayList<String> typeList = null;
switch (type) {
case StorageUtil.TYPE_AUDIO:
typeList = DataUtil.getSupportedAudioFileExtensions();
break;
case StorageUtil.TYPE_VIDEO:
typeList = DataUtil.getSupportedVideoFileExtensions();
break;
case StorageUtil.TYPE_IMAGE:
typeList = DataUtil.getSupportedImageFileExtensions();
break;
case StorageUtil.TYPE_APKFILE:
typeList = DataUtil.getSupportedAppInstallerFileExtensions();
break;
default:
// do nothing
return false;
}
File root_dir = new File(topDir);
String[] list = root_dir.list();
if (list != null && root_dir.canRead()) {
int len = list.length;
for (int i = 0; i < len; i++) {
File check = new File(topDir + "/" + list[i]);
// skip symbol link and hidden files
try {
if (isSymlink(check) || check.isHidden()) {
continue;
}
} catch (IOException e) {
Log.e(TAG, "Failed to check symbol link!");
}
if (check.isFile()) {
String extension = DataUtil
.getFileExtensionWithoutDot(check.getPath());
if (typeList.contains(extension.toLowerCase())) {
result.add(check.getPath());
}
}
if (check.isDirectory()) {
if (StorageUtil.getExcludeSearchPath().contains(
check.getAbsolutePath())
|| StorageUtil
.isExcludeSearchPath(
check.getAbsolutePath(),
reservedSearchDirs)) {
continue;
} else {
if (check.canRead() && !topDir.equals("/")) {
searchFilesOfType(check.getAbsolutePath(), type,
result, reservedSearchDirs);
}
}
}
}
}
return true;
}
public void setApkPatternPath(ArrayList<String> apkPatternFilePaths) {
mApkPatternFilePaths = apkPatternFilePaths;
}
public void setAudioPatternPath(ArrayList<String> audioPatternFilePaths) {
mAudioPatternFilePaths = audioPatternFilePaths;
}
public void setFilePathObserver(FilePathObserver observer) {
mFilePathObserver = observer;
}
public void setHistoryPosition(int position) {
mHistoryPosition = position;
}
public void setImagePatternPath(ArrayList<String> imagePatternFilePaths) {
mImagePatternFilePaths = imagePatternFilePaths;
}
public void setSearchFilePatternPath(
HashMap<String, ArrayList<String>> searchPatternFilePaths) {
mSearchPatternFilePaths = searchPatternFilePaths;
}
public void setShowHiddenFiles(boolean choice) {
mShowHiddenFiles = choice;
}
public void setPreSortType(int type) {
mPreSortType = type;
}
public int getPreSortType() {
return mPreSortType;
}
public void setUrlHistory(ArrayList<String> history) {
mUrlHistoryList = history;
}
public void setVideoPatternPath(ArrayList<String> videoPatternFilePaths) {
mVideoPatternFilePaths = videoPatternFilePaths;
}
public void updateCachedFilePaths(String pattern,
HashMap<String, String> explicitChangeSets) {
// FIXME
// There might be concurrent exception risk here
if (explicitChangeSets != null && explicitChangeSets.size() > 0) {
ArrayList<String> targetCaches;
if (FilePathUrlManager.SMART_MUSIC.equals(pattern)) {
targetCaches = mAudioPatternFilePaths;
} else if (FilePathUrlManager.SMART_IMAGE.equals(pattern)) {
targetCaches = mImagePatternFilePaths;
} else if (FilePathUrlManager.SMART_MOVIES.equals(pattern)) {
targetCaches = mVideoPatternFilePaths;
} else if (FilePathUrlManager.SMART_APK.equals(pattern)) {
targetCaches = mApkPatternFilePaths;
} else {
return;
}
if (targetCaches == null) {
return;
}
for (String oldValue : explicitChangeSets.keySet()) {
if (targetCaches.contains(oldValue)) {
int index = targetCaches.indexOf(oldValue);
String newValue = explicitChangeSets.get(oldValue);
if (newValue != null) {
targetCaches.set(index,
explicitChangeSets.get(oldValue));
} else {
targetCaches.remove(oldValue);
}
} else {
if (FileUtil.NEW_GENERATED_FILE.equals(oldValue)) {
targetCaches.add(explicitChangeSets.get(oldValue));
}
}
}
}
}
@SuppressWarnings("unchecked")
private ArrayList<String> populateCurrentUrlContent() {
if (!mCurrentUrlContent.isEmpty()) {
mCurrentUrlContent.clear();
}
// Smart folder mode
// Return clone list instead the pattern file path list itself,
// because they maybe get modified by setter method when caller of this
// method is using the list
if (FilePathUrlManager.SMART_MUSIC
.equals(getSmartPattern(getCurrentUrl()))) {
return (ArrayList<String>) mAudioPatternFilePaths.clone();
} else if (FilePathUrlManager.SMART_IMAGE
.equals(getSmartPattern(getCurrentUrl()))) {
return (ArrayList<String>) mImagePatternFilePaths.clone();
} else if (FilePathUrlManager.SMART_MOVIES
.equals(getSmartPattern(getCurrentUrl()))) {
return (ArrayList<String>) mVideoPatternFilePaths.clone();
} else if (FilePathUrlManager.SMART_APK
.equals(getSmartPattern(getCurrentUrl()))) {
return (ArrayList<String>) mApkPatternFilePaths.clone();
} else if (FilePathUrlManager.SMART_FILE
.equals(getSmartPattern(getCurrentUrl()))) {
String searchId = buildSearchId(getQueryStr(getCurrentUrl()),
getDir(getCurrentUrl()));
Log.i(TAG, "search id: " + searchId);
ArrayList<String> searchRecord = mSearchPatternFilePaths
.get(searchId);
if (searchRecord != null) {
return (ArrayList<String>) searchRecord.clone();
} else {
return null;
}
}
// Common folder mode
File file = new File(getCurrentUrl());
if (file.exists() && file.canRead()) {
String currentPath = file.getPath();
if (!currentPath.equals(mLastMonitoringPath)) {
// monitoring the new current directory
if (mCurrentDirMonitor != null) {
mCurrentDirMonitor.stopWatching();
}
mCurrentDirMonitor = new DirectoryMonitor(currentPath,
FilePathUrlManager.this);
mCurrentDirMonitor.startWatching();
mLastMonitoringPath = currentPath;
}
String[] children = file.list();
if (children != null) {
int len = children.length;
/* add files/folder to ArrayList depending on hidden status */
for (int i = 0; i < len; i++) {
if (!mShowHiddenFiles) {
if (children[i].toString().charAt(0) != '.') {
if (currentPath.endsWith("/")) {
mCurrentUrlContent.add(currentPath
+ children[i]);
} else {
mCurrentUrlContent.add(currentPath + "/"
+ children[i]);
}
}
} else {
mCurrentUrlContent.add(file.getPath() + "/"
+ children[i]);
}
}
}
/* sort the ArrayList that was made from above for loop */
switch (mPreSortType) {
case SORT_NONE:
// no sorting needed
break;
case SORT_ALPHA:
Object[] tt = mCurrentUrlContent.toArray();
mCurrentUrlContent.clear();
Arrays.sort(tt, mAlphComparator);
for (Object a : tt) {
mCurrentUrlContent.add((String) a);
}
break;
}
}
return mCurrentUrlContent;
}
private void zip_folder(File file, ZipOutputStream zout) throws IOException {
byte[] data = new byte[BUFFER];
int read;
if (file.isFile()) {
ZipEntry entry = new ZipEntry(file.getName());
zout.putNextEntry(entry);
BufferedInputStream instream = new BufferedInputStream(
new FileInputStream(file));
while ((read = instream.read(data, 0, BUFFER)) != -1) {
zout.write(data, 0, read);
}
zout.closeEntry();
instream.close();
} else if (file.isDirectory()) {
String[] list = file.list();
int len = list.length;
for (int i = 0; i < len; i++) {
zip_folder(new File(file.getPath() + "/" + list[i]), zout);
}
}
}
}