package com.fsck.k9.helper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
import timber.log.Timber;
import com.fsck.k9.K9;
import org.apache.commons.io.IOUtils;
public class FileHelper {
/**
* Regular expression that represents characters we won't allow in file names.
*
* <p>
* Allowed are:
* <ul>
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
* <li>spaces: {@code " "}</li>
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>{</code>,
* <code>}</code>, {@code ~}, {@code .}, {@code ,}</li>
* </ul></p>
*
* @see #sanitizeFilename(String)
*/
private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]";
/**
* Invalid characters in a file name are replaced by this character.
*
* @see #sanitizeFilename(String)
*/
private static final String REPLACEMENT_CHARACTER = "_";
/**
* Creates a unique file in the given directory by appending a hyphen
* and a number to the given filename.
*/
public static File createUniqueFile(File directory, String filename) {
File file = new File(directory, filename);
if (!file.exists()) {
return file;
}
// Get the extension of the file, if any.
int index = filename.lastIndexOf('.');
String name;
String extension;
if (index != -1) {
name = filename.substring(0, index);
extension = filename.substring(index);
} else {
name = filename;
extension = "";
}
for (int i = 2; i < Integer.MAX_VALUE; i++) {
file = new File(directory, String.format(Locale.US, "%s-%d%s", name, i, extension));
if (!file.exists()) {
return file;
}
}
return null;
}
public static void touchFile(final File parentDir, final String name) {
final File file = new File(parentDir, name);
try {
if (!file.exists()) {
if (!file.createNewFile()) {
Timber.d("Unable to create file: %s", file.getAbsolutePath());
}
} else {
if (!file.setLastModified(System.currentTimeMillis())) {
Timber.d("Unable to change last modification date: %s", file.getAbsolutePath());
}
}
} catch (Exception e) {
Timber.d(e, "Unable to touch file: %s", file.getAbsolutePath());
}
}
private static void copyFile(File from, File to) throws IOException {
FileInputStream in = new FileInputStream(from);
FileOutputStream out = new FileOutputStream(to);
try {
byte[] buffer = new byte[1024];
int count;
while ((count = in.read(buffer)) > 0) {
out.write(buffer, 0, count);
}
out.close();
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
public static void renameOrMoveByCopying(File from, File to) throws IOException {
deleteFileIfExists(to);
boolean renameFailed = !from.renameTo(to);
if (renameFailed) {
copyFile(from, to);
boolean deleteFromFailed = !from.delete();
if (deleteFromFailed) {
Timber.e("Unable to delete source file after copying to destination!");
}
}
}
private static void deleteFileIfExists(File to) throws IOException {
boolean fileDoesNotExist = !to.exists();
if (fileDoesNotExist) {
return;
}
boolean deleteOk = to.delete();
if (deleteOk) {
return;
}
throw new IOException("Unable to delete file: " + to.getAbsolutePath());
}
public static boolean move(final File from, final File to) {
if (to.exists()) {
if (!to.delete()) {
Timber.d("Unable to delete file: %s", to.getAbsolutePath());
}
}
if (!to.getParentFile().mkdirs()) {
Timber.d("Unable to make directories: %s", to.getParentFile().getAbsolutePath());
}
try {
copyFile(from, to);
boolean deleteFromFailed = !from.delete();
if (deleteFromFailed) {
Timber.e("Unable to delete source file after copying to destination!");
}
return true;
} catch (Exception e) {
Timber.w(e, "cannot move %s to %s", from.getAbsolutePath(), to.getAbsolutePath());
return false;
}
}
public static void moveRecursive(final File fromDir, final File toDir) {
if (!fromDir.exists()) {
return;
}
if (!fromDir.isDirectory()) {
if (toDir.exists()) {
if (!toDir.delete()) {
Timber.w("cannot delete already existing file/directory %s", toDir.getAbsolutePath());
}
}
if (!fromDir.renameTo(toDir)) {
Timber.w("cannot rename %s to %s - moving instead", fromDir.getAbsolutePath(), toDir.getAbsolutePath());
move(fromDir, toDir);
}
return;
}
if (!toDir.exists() || !toDir.isDirectory()) {
if (toDir.exists()) {
if (!toDir.delete()) {
Timber.d("Unable to delete file: %s", toDir.getAbsolutePath());
}
}
if (!toDir.mkdirs()) {
Timber.w("cannot create directory %s", toDir.getAbsolutePath());
}
}
File[] files = fromDir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
moveRecursive(file, new File(toDir, file.getName()));
if (!file.delete()) {
Timber.d("Unable to delete file: %s", toDir.getAbsolutePath());
}
} else {
File target = new File(toDir, file.getName());
if (!file.renameTo(target)) {
Timber.w("cannot rename %s to %s - moving instead",
file.getAbsolutePath(), target.getAbsolutePath());
move(file, target);
}
}
}
if (!fromDir.delete()) {
Timber.w("cannot delete %s", fromDir.getAbsolutePath());
}
}
/**
* Replace characters we don't allow in file names with a replacement character.
*
* @param filename
* The original file name.
*
* @return The sanitized file name containing only allowed characters.
*/
public static String sanitizeFilename(String filename) {
return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
}
}