package org.redcross.openmapkit;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Environment;
import android.util.Log;
import com.google.common.io.Files;
import org.apache.commons.io.FilenameUtils;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* For encapsulating tasks such as checking if external storage is available for read, for write, and fetching files from storage
*/
public class ExternalStorage {
/**
* Directories used by the app.
* * *
*/
public static final String APP_DIR = "openmapkit";
public static final String MBTILES_DIR = "mbtiles";
public static final String OSM_DIR = "osm";
public static final String DEPLOYMENTS_DIR = "deployments";
public static final String CONSTRAINTS_DIR = "constraints";
public static final String DEFAULT_CONSTRAINT = "default.json";
/**
* The name of the form specific constraints file if it were to be delivered as an ODK media file
*/
public static final String CONSTRAINTS_FILE_NAME_ON_ODK = "omk-constraints.json";
/**
* Creating the application directory structure.
*/
public static void checkOrCreateAppDirs() {
// NOTE: Unable to create directories in SD Card dir. Investigate further. #135
// File storageDir = getSDCardDirWithExternalFallback();
File storageDir = Environment.getExternalStorageDirectory();
File appDir = new File(storageDir, APP_DIR);
if(!appDir.exists()) {
appDir.mkdirs(); // mkdirs is mkdir -p
}
File mbtilesDir = new File(appDir, MBTILES_DIR);
if(!mbtilesDir.exists()) {
mbtilesDir.mkdirs();
}
File osmDir = new File(appDir, OSM_DIR);
if (!osmDir.exists()) {
osmDir.mkdirs();
}
File deploymentsDir = new File(appDir, DEPLOYMENTS_DIR);
if (!deploymentsDir.exists()) {
deploymentsDir.mkdirs();
}
File constraintsDir = new File(appDir, CONSTRAINTS_DIR);
if (!constraintsDir.exists()) {
constraintsDir.mkdirs();
}
}
public static String getMBTilesDir() {
return Environment.getExternalStorageDirectory() + "/"
+ APP_DIR + "/"
+ MBTILES_DIR + "/";
}
public static String getMBTilesDirRelativeToExternalDir() {
return "/" + APP_DIR + "/" + MBTILES_DIR + "/";
}
public static String getOSMDir() {
return Environment.getExternalStorageDirectory() + "/"
+ APP_DIR + "/"
+ OSM_DIR + "/";
}
public static String getOSMDirRelativeToExternalDir() {
return "/" + APP_DIR + "/" + OSM_DIR + "/";
}
public static File[] fetchOSMXmlFiles() {
List<File> osms = allDeploymentOSMXmlFiles();
String dirPath = getOSMDir();
File dir = new File(dirPath);
File[] otherOsms = dir.listFiles();
Collections.addAll(osms, otherOsms);
return osms.toArray(new File[osms.size()]);
}
public static String[] fetchOSMXmlFileNames() {
File[] files = fetchOSMXmlFiles();
int len = files.length;
String [] names = new String[len];
for (int i=0; i < len; ++i) {
names[i] = files[i].getName();
}
return names;
}
public static File[] fetchMBTilesFiles() {
List<File> mbtiles = allDeploymentMBTilesFiles();
String dirPath = getMBTilesDir();
File dir = new File(dirPath);
File[] otherMBTiles = dir.listFiles();
Collections.addAll(mbtiles, otherMBTiles);
return mbtiles.toArray(new File[mbtiles.size()]);
}
/**
* Checking if external storage is available for read and write
*/
public static boolean isWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/**
* Checking if external storage is available to read.
*/
public static boolean isReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
public static File deploymentDir(String deploymentName) {
File storageDir = Environment.getExternalStorageDirectory();
File deploymentsDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR);
File deploymentDir = new File(deploymentsDir, deploymentName);
if (!deploymentDir.exists()) {
deploymentDir.mkdirs();
}
return deploymentDir;
}
public static String deploymentDirRelativeToExternalDir(String deploymentName) {
// make sure deployment dir is created
deploymentDir(deploymentName);
return "/" + APP_DIR + "/" + DEPLOYMENTS_DIR + "/" + deploymentName + "/";
}
/**
* Fetches all deployment.json files in ExternalStorage
*
* @return - list of deployment.json files
*/
public static List<File> allDeploymentJSONFiles() {
List<File> jsonFiles = new ArrayList<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentsDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR);
File[] deployments = deploymentsDir.listFiles();
for (File deploymentDir : deployments) {
File[] files = deploymentDir.listFiles();
for (File f : files) {
String fileName = f.getName();
if (fileName.equals("deployment.json")) {
jsonFiles.add(f);
}
}
}
return jsonFiles;
}
public static List<File> allDeploymentOSMXmlFiles() {
List<File> deploymentOSMFiles = new ArrayList<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentsDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR);
File[] deployments = deploymentsDir.listFiles();
for (File deploymentDir : deployments) {
File[] files = deploymentDir.listFiles();
for (File f : files) {
String ext = FilenameUtils.getExtension(f.getPath());
if (ext.equals("osm")) {
deploymentOSMFiles.add(f);
}
}
}
return deploymentOSMFiles;
}
public static List<File> allDeploymentMBTilesFiles() {
List<File> deploymentMBTilesFiles = new ArrayList<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentsDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR);
File[] deployments = deploymentsDir.listFiles();
for (File deploymentDir : deployments) {
File[] files = deploymentDir.listFiles();
for (File f : files) {
String ext = FilenameUtils.getExtension(f.getPath());
if (ext.equals("mbtiles")) {
deploymentMBTilesFiles.add(f);
}
}
}
return deploymentMBTilesFiles;
}
public static Set<File> deploymentOSMXmlFiles(String deploymentName) {
Set<File> osmXmlFiles = new HashSet<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR + "/" + deploymentName);
File[] files = deploymentDir.listFiles();
for (File f : files) {
String ext = FilenameUtils.getExtension(f.getPath());
if (ext.equals("osm")) {
osmXmlFiles.add(f);
}
}
return osmXmlFiles;
}
public static File deploymentFPFile(String deploymentName) {
Set<File> osmXmlFiles = new HashSet<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR + "/" + deploymentName);
File[] files = deploymentDir.listFiles();
for (File f : files) {
if (f.getName().equals("fp.geojson")) {
return f;
}
}
return null;
}
public static Set<File> deploymentMBTilesFiles(String deploymentName) {
Set<File> mbtilesFiles = new HashSet<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR + "/" + deploymentName);
File[] files = deploymentDir.listFiles();
for (File f : files) {
String ext = FilenameUtils.getExtension(f.getPath());
if (ext.equals("mbtiles")) {
mbtilesFiles.add(f);
}
}
return mbtilesFiles;
}
public static Map<String, File> deploymentDownloadedFiles(String deploymentName) {
Map<String, File> deploymentFiles = new HashMap<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR + "/" + deploymentName);
File[] files = deploymentDir.listFiles();
if (files == null || files.length == 0) return deploymentFiles;
for (File f : files) {
String ext = FilenameUtils.getExtension(f.getPath());
if ( ext.equals("mbtiles") || ext.equals("osm") || ext.equals("geojson") ) {
deploymentFiles.put(f.getName(), f);
}
}
return deploymentFiles;
}
public static List<String> deploymentMBTilesFilePaths(String deploymentName) {
List<String> mbtilesFiles = new ArrayList<>();
File storageDir = Environment.getExternalStorageDirectory();
File deploymentDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR + "/" + deploymentName);
File[] files = deploymentDir.listFiles();
for (File f : files) {
String ext = FilenameUtils.getExtension(f.getPath());
if (ext.equals("mbtiles")) {
mbtilesFiles.add(f.getAbsolutePath());
}
}
return mbtilesFiles;
}
public static void deleteDeployment(String deploymentName) {
File storageDir = Environment.getExternalStorageDirectory();
File deploymentsDir = new File(storageDir, APP_DIR + "/" + DEPLOYMENTS_DIR);
File deploymentDir = new File(deploymentsDir, deploymentName);
deleteRecursive(deploymentDir);
}
private static void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
}
}
fileOrDirectory.delete();
}
public static void copyConstraintsToExternalStorageIfNeeded(Context context) {
File storageDir = Environment.getExternalStorageDirectory();
File appDir = new File(storageDir, APP_DIR);
File constraintsDir = new File(appDir, CONSTRAINTS_DIR);
File defaultConstraint = new File(constraintsDir, DEFAULT_CONSTRAINT);
// We want to always copy over JSON while developing.
// In production, we only want to copy over once.
if (!defaultConstraint.exists() || BuildConfig.DEBUG) {
copyAssetsFileOrDirToExternalStorage(context, CONSTRAINTS_DIR);
}
}
private static void copyAssetsFileOrDirToExternalStorage(Context context, String path) {
AssetManager assetManager = context.getAssets();
String assets[] = null;
try {
assets = assetManager.list(path);
if (assets.length == 0) {
copyAssetsFileToExternalStorage(context, path);
} else {
String fullPath = Environment.getExternalStorageDirectory() + "/" + APP_DIR + "/" + path;
File dir = new File(fullPath);
if (!dir.exists())
dir.mkdir();
for (int i = 0; i < assets.length; ++i) {
copyAssetsFileOrDirToExternalStorage(context, path + "/" + assets[i]);
}
}
} catch (IOException ex) {
Log.e("tag", "I/O Exception", ex);
}
}
private static void copyAssetsFileToExternalStorage(Context context, String filename) {
AssetManager assetManager = context.getAssets();
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(filename);
String newFileName = Environment.getExternalStorageDirectory() + "/" + APP_DIR + "/" + filename;
out = new FileOutputStream(newFileName);
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch (Exception e) {
Log.e("tag", e.getMessage());
}
}
public static File fetchConstraintsFile(String formName) {
File storageDir = Environment.getExternalStorageDirectory();
File appDir = new File(storageDir, APP_DIR);
File constraintsDir = new File(appDir, CONSTRAINTS_DIR);
return new File(constraintsDir, formName + ".json");
}
/**
* This method attempts to fetch the form's constraints file from ODK's media directory for the
* form. If the constraints file is found on ODK's media directory for the form, its contents
* will overwrite whatever is in OMK's constraints file for the form
*
* @param formFileName The name of the ODK form
* @return TRUE if there was a successful copy
*/
public static boolean copyFormConstraintsFromOdk(String formFileName) {
String sdCardPath = Environment.getExternalStorageDirectory().getAbsolutePath();
if(formFileName != null) {
String mediaDirPath = sdCardPath + "/odk/forms/" + formFileName + "-media";
File mediaDirectory = new File(mediaDirPath);
if(mediaDirectory.exists() && mediaDirectory.isDirectory()) {
String constraintsFilePath = mediaDirPath + "/" + CONSTRAINTS_FILE_NAME_ON_ODK;
File odkConstraintsFile = new File(constraintsFilePath);
if(odkConstraintsFile.exists() && !odkConstraintsFile.isDirectory()) {
File omkConstraintsFile = fetchConstraintsFile(formFileName);
try {
Files.copy(odkConstraintsFile, omkConstraintsFile);
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
/**
* From
* https://github.com/mstorsjo/vlc-android/blob/master/vlc-android/src/org/videolan/vlc/util/AndroidDevices.java
*
* @return storage directories, including external SD card
*/
public static String[] getStorageDirectories() {
String[] dirs = null;
BufferedReader bufReader = null;
ArrayList<String> list = new ArrayList<String>();
list.add(Environment.getExternalStorageDirectory().getPath());
List<String> typeWL = Arrays.asList("vfat", "exfat", "sdcardfs", "fuse");
List<String> typeBL = Arrays.asList("tmpfs");
String[] mountWL = { "/mnt", "/Removable" };
String[] mountBL = {
"/mnt/secure",
"/mnt/shell",
"/mnt/asec",
"/mnt/obb",
"/mnt/media_rw/extSdCard",
"/mnt/media_rw/sdcard",
"/storage/emulated" };
String[] deviceWL = {
"/dev/block/vold",
"/dev/fuse",
"/mnt/media_rw/extSdCard" };
try {
bufReader = new BufferedReader(new FileReader("/proc/mounts"));
String line;
while((line = bufReader.readLine()) != null) {
StringTokenizer tokens = new StringTokenizer(line, " ");
String device = tokens.nextToken();
String mountpoint = tokens.nextToken();
String type = tokens.nextToken();
// skip if already in list or if type/mountpoint is blacklisted
if (list.contains(mountpoint) || typeBL.contains(type) || Strings.StartsWith(mountBL, mountpoint))
continue;
// check that device is in whitelist, and either type or mountpoint is in a whitelist
if (Strings.StartsWith(deviceWL, device) && (typeWL.contains(type) || Strings.StartsWith(mountWL, mountpoint)))
list.add(mountpoint);
}
dirs = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
dirs[i] = list.get(i);
}
}
catch (FileNotFoundException e) {}
catch (IOException e) {}
finally {
if (bufReader != null) {
try {
bufReader.close();
}
catch (IOException e) {}
}
}
return dirs;
}
/**
* Use last storage dir, which is ext SD card. If there is no SD card,
* the last in the list will be the standard ext storage.
*
* @return storage dir
*/
public static File getSDCardDirWithExternalFallback() {
String[] dirs = getStorageDirectories();
return new File(dirs[dirs.length-1]);
}
}