/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.utils;
import android.content.Context;
import android.util.Log;
import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.common.Constants;
import org.catrobat.catroid.soundrecorder.SoundRecorder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public final class UtilFile {
private static final String TAG = UtilFile.class.getSimpleName();
// Suppress default constructor for noninstantiability
private UtilFile() {
throw new AssertionError();
}
private static long getSizeOfFileOrDirectoryInByte(File fileOrDirectory) {
if (!fileOrDirectory.exists()) {
return 0;
}
if (fileOrDirectory.isFile()) {
return fileOrDirectory.length();
}
File[] contents = fileOrDirectory.listFiles();
if (contents == null) {
return 0;
}
long size = 0;
for (File file : contents) {
size += file.isDirectory() ? getSizeOfFileOrDirectoryInByte(file) : file.length();
}
return size;
}
public static long getProgressFromBytes(String projectName, Long progress) {
long fileByteSize = getSizeOfFileOrDirectoryInByte(new File(Utils.buildProjectPath(projectName)));
if (fileByteSize == 0) {
return (long) 0;
}
return progress * 100 / fileByteSize;
}
public static String getSizeAsString(File fileOrDirectory) {
final int unit = 1024;
long bytes = UtilFile.getSizeOfFileOrDirectoryInByte(fileOrDirectory);
if (bytes < unit) {
return bytes + " Byte";
}
/*
* Logarithm of "bytes" to base "unit"
* log(a) / log(b) == logarithm of a to the base of b
*/
int exponent = (int) (Math.log(bytes) / Math.log(unit));
char prefix = "KMGTPE".charAt(exponent - 1);
return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(unit, exponent), prefix);
}
public static boolean deleteDirectory(File fileOrDirectory) {
return deleteDirectory(fileOrDirectory, 0);
}
private static boolean deleteDirectory(File fileOrDirectory, int space) {
if (fileOrDirectory == null) {
return false;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < space; i++) {
sb.append('-');
}
boolean success = true;
if (fileOrDirectory.exists() && fileOrDirectory.isDirectory()) {
// Please note: especially with MyProjectsActivityTest.testAddNewProjectMixedCase(), it happens that listFiles
// returns null (although fileOrDirectory exists and is a directory). This should definitely not happen
// and there is probably an I/O Error from the system. So we just check this manually and abort if so.
File[] files = fileOrDirectory.listFiles();
if (files == null) {
return false;
}
for (File child : files) {
success = deleteDirectory(child, space + 1);
if (!success) {
return false;
}
}
}
Log.v(TAG, sb.toString() + "delete: " + fileOrDirectory.getName());
//http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy
final File renameBeforeDelete = new File(fileOrDirectory.getAbsolutePath() + System.currentTimeMillis());
fileOrDirectory.renameTo(renameBeforeDelete);
return renameBeforeDelete.delete();
}
public static File saveFileToProject(String project, String sceneName, String name, int fileID, Context context,
FileType type) {
String filePath;
if (project == null || project.equalsIgnoreCase("")) {
filePath = Utils.buildProjectPath(name);
if (sceneName == null || sceneName.equalsIgnoreCase("")) {
filePath = Utils.buildPath(filePath, sceneName);
}
} else {
switch (type) {
case TYPE_IMAGE_FILE:
filePath = Utils.buildPath(Utils.buildProjectPath(project), sceneName, Constants.IMAGE_DIRECTORY,
name);
break;
case TYPE_SOUND_FILE:
filePath = Utils.buildPath(Utils.buildProjectPath(project), sceneName, Constants.SOUND_DIRECTORY,
name);
break;
default:
filePath = Utils.buildPath(Utils.buildProjectPath(name), sceneName);
break;
}
}
BufferedInputStream in = new BufferedInputStream(context.getResources().openRawResource(fileID),
Constants.BUFFER_8K);
try {
File file = new File(filePath);
file.getParentFile().mkdirs();
file.createNewFile();
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file), Constants.BUFFER_8K);
byte[] buffer = new byte[Constants.BUFFER_8K];
int length = 0;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.flush();
out.close();
return file;
} catch (IOException ioException) {
Log.e(TAG, Log.getStackTraceString(ioException));
return null;
}
}
public static void createStandardProjectIfRootDirectoryIsEmpty(Context context) {
File rootDirectory = new File(Constants.DEFAULT_ROOT);
if (rootDirectory == null || rootDirectory.listFiles() == null || getProjectNames(rootDirectory).size() == 0) {
ProjectManager.getInstance().initializeDefaultProject(context);
}
}
/**
* returns a list of strings of all projectnames in the catroid folder
*/
public static List<String> getProjectNames(File directory) {
List<String> projectList = new ArrayList<String>();
File[] fileList = directory.listFiles();
if (fileList != null) {
FilenameFilter filenameFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return filename.contentEquals(Constants.PROJECTCODE_NAME);
}
};
for (File file : fileList) {
if (file.isDirectory() && file.list(filenameFilter).length != 0) {
projectList.add(decodeSpecialCharsForFileSystem(file.getName()));
}
}
}
return projectList;
}
public static File copyFile(File destinationFile, File sourceFile) throws IOException {
FileInputStream inputStream = null;
FileChannel inputChannel = null;
FileOutputStream outputStream = null;
FileChannel outputChannel = null;
try {
inputStream = new FileInputStream(sourceFile);
inputChannel = inputStream.getChannel();
outputStream = new FileOutputStream(destinationFile);
outputChannel = outputStream.getChannel();
inputChannel.transferTo(0, inputChannel.size(), outputChannel);
return destinationFile;
} catch (IOException exception) {
throw exception;
} finally {
if (inputChannel != null) {
inputChannel.close();
}
if (inputStream != null) {
inputStream.close();
}
if (outputChannel != null) {
outputChannel.close();
}
if (outputStream != null) {
outputStream.close();
}
}
}
public static File copyFromResourceIntoProject(String projectName, String sceneName, String directoryInProject,
String outputFilename, int resourceId, Context context, boolean prependMd5ToFilename) throws IOException {
String directoryPath;
directoryPath = Utils.buildPath(Utils.buildProjectPath(projectName), sceneName, directoryInProject);
File copiedFile = new File(directoryPath, outputFilename);
if (!copiedFile.exists()) {
copiedFile.createNewFile();
} else {
throw new IllegalArgumentException("file " + copiedFile.getAbsolutePath() + " already exists!");
}
InputStream in = context.getResources().openRawResource(resourceId);
OutputStream out = new BufferedOutputStream(new FileOutputStream(copiedFile), Constants.BUFFER_8K);
byte[] buffer = new byte[Constants.BUFFER_8K];
int length = 0;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.flush();
out.close();
if (!prependMd5ToFilename) {
return copiedFile;
}
return prependMd5ToFilename(copiedFile);
}
public static File copySoundFromResourceIntoProject(String projectName, String sceneName, String outputFilename,
int resourceId,
Context context, boolean prependMd5ToFilename) throws IllegalArgumentException, IOException {
if (!outputFilename.toLowerCase(Locale.US).endsWith(SoundRecorder.RECORDING_EXTENSION)) {
throw new IllegalArgumentException("Only Files with extension " + SoundRecorder.RECORDING_EXTENSION
+ " allowed");
}
return copyFromResourceIntoProject(projectName, sceneName, Constants.SOUND_DIRECTORY, outputFilename,
resourceId,
context,
prependMd5ToFilename);
}
public static File copyImageFromResourceIntoProject(String projectName, String sceneName, String outputFilename,
int
resourceId,
Context context, boolean prependMd5ToFilename, double scaleFactor) throws IOException {
if (scaleFactor <= 0) {
throw new IllegalArgumentException("scale factor is smaller or equal zero");
}
outputFilename = UtilFile.encodeSpecialCharsForFileSystem(outputFilename);
if (!outputFilename.toLowerCase(Locale.US).endsWith(Constants.IMAGE_STANDARD_EXTENSION)) {
outputFilename = outputFilename + Constants.IMAGE_STANDARD_EXTENSION;
}
File copiedFile = copyFromResourceIntoProject(projectName, sceneName, Constants.IMAGE_DIRECTORY, outputFilename,
resourceId, context, false);
ImageEditing.scaleImageFile(copiedFile, scaleFactor);
if (!prependMd5ToFilename) {
return copiedFile;
}
return prependMd5ToFilename(copiedFile);
}
private static File prependMd5ToFilename(File file) throws IOException {
File fileWithMd5 = new File(file.getParent(), Utils.md5Checksum(file) + Constants.FILENAME_SEPARATOR
+ file.getName());
if (!file.renameTo(fileWithMd5)) {
throw new IOException("renaming file " + file.getAbsoluteFile() + " to " + fileWithMd5.getAbsoluteFile()
+ " failed");
}
return fileWithMd5;
}
public static String encodeSpecialCharsForFileSystem(String projectName) {
if (projectName.equals(".") || projectName.equals("..")) {
projectName = projectName.replace(".", "%2E");
} else {
projectName = projectName.replace("%", "%25");
projectName = projectName.replace("\"", "%22");
projectName = projectName.replace("/", "%2F");
projectName = projectName.replace(":", "%3A");
projectName = projectName.replace("<", "%3C");
projectName = projectName.replace(">", "%3E");
projectName = projectName.replace("?", "%3F");
projectName = projectName.replace("\\", "%5C");
projectName = projectName.replace("|", "%7C");
projectName = projectName.replace("*", "%2A");
}
return projectName;
}
public static String decodeSpecialCharsForFileSystem(String projectName) {
projectName = projectName.replace("%2E", ".");
projectName = projectName.replace("%2A", "*");
projectName = projectName.replace("%7C", "|");
projectName = projectName.replace("%5C", "\\");
projectName = projectName.replace("%3F", "?");
projectName = projectName.replace("%3E", ">");
projectName = projectName.replace("%3C", "<");
projectName = projectName.replace("%3A", ":");
projectName = projectName.replace("%2F", "/");
projectName = projectName.replace("%22", "\"");
projectName = projectName.replace("%25", "%");
return projectName;
}
public enum FileType {
TYPE_IMAGE_FILE, TYPE_SOUND_FILE
}
}