/* * Geopaparazzi - Digital field mapping on Android based devices * Copyright (C) 2010 HydroloGIS (www.hydrologis.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 eu.geopaparazzi.library.util; import static eu.geopaparazzi.library.util.LibraryConstants.PREFS_KEY_BASEFOLDER; import static eu.geopaparazzi.library.util.LibraryConstants.PREFS_KEY_CUSTOM_EXTERNALSTORAGE; import static eu.geopaparazzi.library.util.Utilities.messageDialog; import java.io.File; import java.io.IOException; import java.io.Serializable; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.ApplicationInfo; import android.os.Environment; import android.preference.PreferenceManager; import android.util.Log; import eu.geopaparazzi.library.R; import eu.geopaparazzi.library.database.GPLog; /** * Singleton that takes care of resources management. * * <p>It creates a folder structure with possible database and log file names.</p> * * @author Andrea Antonello (www.hydrologis.com) */ public class ResourcesManager implements Serializable { private static final long serialVersionUID = 1L; private static final String PATH_MAPS = "maps"; //$NON-NLS-1$ private static final String PATH_MEDIA = "media"; //$NON-NLS-1$ private File applicationDir; private File databaseFile; private File mediaDir; private File mapsDir; private File exportDir; private static ResourcesManager resourcesManager; private String applicationLabel; private static boolean useInternalMemory = true; private File sdcardDir; private boolean createdApplicationDirOnInit = false; public static void setUseInternalMemory( boolean useInternalMemory ) { ResourcesManager.useInternalMemory = useInternalMemory; } /** * The getter for the {@link ResourcesManager} singleton. * * <p>This is a singletone but might require to be recreated * in every moment of the application. This is due to the fact * that when the application looses focus (for example because of * an incoming call, and therefore at a random moment, if the memory * is too low, the parent activity could have been killed by * the system in background. In which case we need to recreate it.) * * @param context the context to refer to. * @return the {@link ResourcesManager} instance. * @throws Exception */ public synchronized static ResourcesManager getInstance( Context context ) throws Exception { if (resourcesManager == null) { resourcesManager = new ResourcesManager(context); } return resourcesManager; } public static void resetManager() { resourcesManager = null; } public String getApplicationName() { return applicationLabel; } private ResourcesManager( Context context ) throws Exception { Context appContext = context.getApplicationContext(); ApplicationInfo appInfo = appContext.getApplicationInfo(); String packageName = appInfo.packageName; int lastDot = packageName.lastIndexOf('.'); applicationLabel = packageName.replace('.', '_'); if (lastDot != -1) { applicationLabel = packageName.substring(lastDot + 1, packageName.length()); } applicationLabel = applicationLabel.toLowerCase(); String databaseName = applicationLabel + ".db"; //$NON-NLS-1$ /* * take care to create all the folders needed * * The default structure is: * * sdcard * | * |-- applicationname * | | * | |--- applicationname.db * | |--- media (folder) * | |--- export (folder) * | `--- debug.log * `-- mapsdir */ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(appContext); String baseFolder = preferences.getString(PREFS_KEY_BASEFOLDER, ""); //$NON-NLS-1$ applicationDir = new File(baseFolder); File parentFile = applicationDir.getParentFile(); boolean parentExists = false; boolean parentCanWrite = false; if (parentFile != null) { parentExists = parentFile.exists(); parentCanWrite = parentFile.canWrite(); } // the folder doesn't exist for some reason, fallback on default String state = Environment.getExternalStorageState(); if (GPLog.LOG_HEAVY) { Log.i("RESOURCESMANAGER", state); } boolean mExternalStorageAvailable; boolean mExternalStorageWriteable; if (Environment.MEDIA_MOUNTED.equals(state)) { mExternalStorageAvailable = mExternalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { mExternalStorageAvailable = true; mExternalStorageWriteable = false; } else { mExternalStorageAvailable = mExternalStorageWriteable = false; } String cantCreateSdcardmsg = appContext.getResources().getString(R.string.cantcreate_sdcard); File possibleApplicationDir; if (mExternalStorageAvailable && mExternalStorageWriteable) { // and external storage exists and is usable String customFolderPath = preferences.getString(PREFS_KEY_CUSTOM_EXTERNALSTORAGE, "asdasdpoipoi"); File customFolderFile = new File(customFolderPath); if (customFolderFile.exists() && customFolderFile.isDirectory() && customFolderFile.canWrite()) { /* * the user wants a different storage path: * - use that as sdcard * - create an app folder inside it */ sdcardDir = customFolderFile; possibleApplicationDir = new File(sdcardDir, applicationLabel); } else { if (customFolderPath.equals("internal")) { /* * the user folder doesn't exist, but is "internal": * - use internal app memory * - set sdcard anyways to the external folder for maps use */ useInternalMemory = true; possibleApplicationDir = appContext.getDir(applicationLabel, Context.MODE_PRIVATE); sdcardDir = Environment.getExternalStorageDirectory(); } else { sdcardDir = Environment.getExternalStorageDirectory(); possibleApplicationDir = new File(sdcardDir, applicationLabel); } } } else if (useInternalMemory) { /* * no external storage available: * - use internal memory * - set sdcard for maps inside the space */ possibleApplicationDir = appContext.getDir(applicationLabel, Context.MODE_PRIVATE); sdcardDir = possibleApplicationDir; } else { String msgFormat = Utilities.format(cantCreateSdcardmsg, "sdcard/" + applicationLabel); throw new IOException(msgFormat); } if (baseFolder.length() == 0 || !parentExists || !parentCanWrite) { applicationDir = possibleApplicationDir; } // if (GPLog.LOG_HEAVY) { Log.i("RESOURCESMANAGER", "Possible app dir: " + applicationDir); // } String applicationDirPath = applicationDir.getAbsolutePath(); if (!applicationDir.exists()) { createdApplicationDirOnInit = true; // RandomAccessFile file = null; // try { // file = new RandomAccessFile(applicationDir, "rw"); // final FileLock fileLock = file.getChannel().tryLock(); // Log.i("RESOURCESMANAGER", "Got the lock? " + (null != fileLock)); // if (null != fileLock) { // Log.i("RESOURCESMANAGER", "Is a valid lock? " + fileLock.isValid()); // } // } finally { // file.close(); // } // Process proc = Runtime.getRuntime().exec(new String[]{"lsof", // applicationDir.getAbsolutePath()}); // StringBuilder sb = new StringBuilder("LOSF RESULT: "); // BufferedReader stdInput = new BufferedReader(new // InputStreamReader(proc.getInputStream())); // BufferedReader stdError = new BufferedReader(new // InputStreamReader(proc.getErrorStream())); // String s; // while( (s = stdInput.readLine()) != null ) { // sb.append(s).append("\n"); // } // while( (s = stdError.readLine()) != null ) { // sb.append(s).append("\n"); // } // Log.i("RESOURCESMANAGER", sb.toString()); if (!applicationDir.mkdirs()) { String msgFormat = Utilities.format(cantCreateSdcardmsg, applicationDirPath); throw new IOException(msgFormat); } } if (GPLog.LOG_HEAVY) { Log.i("RESOURCESMANAGER", "App dir exists: " + applicationDir.exists()); } databaseFile = new File(applicationDirPath, databaseName); mediaDir = new File(applicationDir, PATH_MEDIA); if (!mediaDir.exists()) if (!mediaDir.mkdir()) { String msgFormat = Utilities.format(cantCreateSdcardmsg, mediaDir.getAbsolutePath()); throw new IOException(msgFormat); } exportDir = applicationDir.getParentFile(); mapsDir = new File(sdcardDir, PATH_MAPS); if (!mapsDir.exists()) if (!mapsDir.mkdir()) { String msgFormat = Utilities.format(cantCreateSdcardmsg, mapsDir.getAbsolutePath()); messageDialog(appContext, msgFormat, null); mapsDir = sdcardDir; } } /** * Get the file to the main application folder. * * @return the {@link File} to the app folder. */ public File getApplicationDir() { return applicationDir; } /** * Get info about the application folder's pre-existence. * * @return <code>true</code> if on initialisation an * application folder had to be created, <code>false</code> * if the application folder already existed. */ public boolean hadToCreateApplicationDirOnInit() { return createdApplicationDirOnInit; } /** * Get the file to the main application's parent folder. * * @return the {@link File} to the app's parent folder. */ public File getApplicationParentDir() { return applicationDir.getParentFile(); } /** * Get the sdcard dir or <code>null</code>. * * @return the sdcard folder file. */ public File getSdcardDir() { return sdcardDir; } /** * Sets a new application folder. * * <p>Note that this will reset all the folders and resources that are bound * to it. For example there might be the need to recreate the database file.</p> * * @param context the context to use. * @param path the path to the new application. */ public void setApplicationDir( Context context, String path ) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); Editor editor = preferences.edit(); editor.putString(LibraryConstants.PREFS_KEY_BASEFOLDER, path); editor.commit(); resetManager(); } /** * Get the file to a default database location for the app. * * <p>This path is generated with default values and can be * exploited. It doesn't assure that in the location there really is a db. * * @return the {@link File} to the database. */ public File getDatabaseFile() { return databaseFile; } /** * Get the default media folder. * * @return the default media folder. */ public File getMediaDir() { return mediaDir; } /** * Get the default export folder. * * @return the default export folder. */ public File getExportDir() { return exportDir; } /** * Get the default maps folder. * * @return the default maps folder. */ public File getMapsDir() { return mapsDir; } /** * Update the description file of the project. * * @param description a new description for the project. * @throws IOException */ public void addProjectDescription( String description ) throws IOException { File applicationDir = getApplicationDir(); File descriptionFile = new File(applicationDir, "description"); //$NON-NLS-1$ FileUtilities.writefile(description, descriptionFile); } }