/*
* Copyright 2004 - 2008 Christian Sprajc. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder 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.
*
* PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.disk.problem;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.PreferencesEntry;
import de.dal33t.powerfolder.util.FileUtils;
import de.dal33t.powerfolder.disk.Folder;
import de.dal33t.powerfolder.light.FileInfo;
import de.dal33t.powerfolder.light.FileInfoFactory;
/**
* Identifies problems with filenames. Note the directory names mostly have the
* same restrictions!<BR>
* Ref: <A HREF="http://en.wikipedia.org/wiki/Filename">Wikepedia/Filename</A>
* <p/>
*
* @author <A HREF="mailto:schaatser@powerfolder.com">Jan van Oosterom</A>
*/
public class FilenameProblemHelper {
/**
* All names the are not allowed on windows
*/
private static final String[] RESERVED_WORDS = {"CON", "PRN", "AUX",
"CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5",
"COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
private static final int MAX_FILENAME_LENGTH = 255;
public static final String[] ILLEGAL_LINUX_CHARS = {"/"};
public static final String[] ILLEGAL_WINDOWS_CHARS = {"|", "\\", "?", "\"",
"*", "<", ":", ">", "/"};
public static final String[] ILLEGAL_MACOSX_CHARS = {"/", ":"};
/**
* See if there are any problems.
*
* @param fInfo
* @return
*/
public static boolean hasProblems(FileInfo fInfo) {
return hasProblems(fInfo.getFilenameOnly());
}
/**
* See if there are any problems.
*
* @param filename
* @return
*/
public static boolean hasProblems(String filename) {
return containsIllegalLinuxChar(filename)
|| containsIllegalMacOSXChar(filename)
|| containsIllegalWindowsChars(filename)
|| endsWithIllegalWindowsChar(filename)
|| isReservedWindowsFilename(filename) || isTooLong(filename);
}
/**
* Create problems for the file.
*
* @param controller
* @param fileInfo
* @return
*/
public static List<Problem> getProblems(Controller controller,
FileInfo fileInfo)
{
String filename = fileInfo.getFilenameOnly();
List<Problem> returnValue = new ArrayList<Problem>();
if (PreferencesEntry.FILE_NAME_CHECK.getValueBoolean(controller)) {
if (containsIllegalLinuxChar(filename)) {
returnValue.add(new IllegalLinuxCharsFilenameProblem(fileInfo));
}
if (containsIllegalMacOSXChar(filename)) {
returnValue
.add(new IllegalMacosxCharsFilenameProblem(fileInfo));
}
if (containsIllegalWindowsChars(filename)) {
returnValue
.add(new IllegalWindowsCharsFilenameProblem(fileInfo));
}
if (endsWithIllegalWindowsChar(filename)) {
returnValue.add(new EndIllegalCharsFilenameProblem(fileInfo));
}
if (isReservedWindowsFilename(filename)) {
returnValue.add(new ReservedWordFilenameProblem(fileInfo));
}
if (isTooLong(filename)) {
returnValue.add(new TooLongFilenameProblem(fileInfo));
}
}
return returnValue;
}
/**
* Will also return true if file is called AUX.txt or aux
*/
public static boolean isReservedWindowsFilename(String filename) {
String filePart = stripExtension(filename).toUpperCase();
for (String reservedWord : RESERVED_WORDS) {
if (reservedWord.equals(filePart)) {
return true;
}
}
return false;
}
/**
* must be a filename only, path elements must be removed
*/
private static String stripExtension(String filename) {
int lastPoint = filename.lastIndexOf('.');
if (lastPoint >= 0) {
return filename.substring(0, lastPoint);
}
return filename;
}
/**
* |\?*<":>/ illegal in windows
*/
public static boolean containsIllegalWindowsChars(String filename) {
for (String illegalLinuxChar : ILLEGAL_WINDOWS_CHARS) {
if (filename.contains(illegalLinuxChar)) {
return true;
}
}
return false;
}
/**
* windows filename may not end with . or space ( )
*/
public static boolean endsWithIllegalWindowsChar(String filename) {
return filename.endsWith(".") || filename.endsWith(" ");
}
/**
* : and / are illegal on Mac OSX
*/
private static boolean containsIllegalMacOSXChar(String filename) {
for (String illegalLinuxChar : ILLEGAL_MACOSX_CHARS) {
if (filename.contains(illegalLinuxChar)) {
return true;
}
}
return false;
}
/**
* / is illegal on Unix
*/
private static boolean containsIllegalLinuxChar(String filename) {
for (String illegalLinuxChar : ILLEGAL_LINUX_CHARS) {
if (filename.contains(illegalLinuxChar)) {
return true;
}
}
return false;
}
public static boolean isTooLong(String filename) {
return filename.length() > MAX_FILENAME_LENGTH;
}
/**
* This method tries to rename the file to a better filename without
* problems.
*/
public static void resolve(Controller controller, FileInfo fileInfo,
String newFilename, Problem problem)
{
Folder folder = controller.getFolderRepository().getFolder(
fileInfo.getFolderInfo());
File file = folder.getDiskFile(fileInfo);
if (!file.exists()) {
return;
}
File newFile = FileUtils.buildFileFromRelativeName(folder
.getLocalBase(), newFilename);
if (file.renameTo(newFile)) {
FileInfo renamedFileInfo = FileInfoFactory.newFile(folder, newFile,
controller.getMySelf().getInfo(), false);
if (folder.isKnown(fileInfo)) {
folder.removeFilesLocal(fileInfo);
}
folder.scanChangedFile(renamedFileInfo);
fileInfo.getFolder(controller.getFolderRepository()).removeProblem(
problem);
}
}
public static String removeChars(String filenameArg, String[] charsToRemove)
{
String filename = filenameArg;
for (String c : charsToRemove) {
while (filename.contains(c)) {
int index = filename.indexOf(c);
filename = filename.substring(0, index)
+ filename.substring(index + 1, filename.length());
}
}
return filename;
}
/**
* Unique if a file with that name does not exist
*
* @param controller
* @param newName
* the new name WITHOUT path!
* @param fileInfo
* the FileInfo
* @return if the file with the new name (same path as FileInfo) does not
* exists yet.
*/
public static boolean isUnique(Controller controller, String newName,
FileInfo fileInfo)
{
Folder folder = controller.getFolderRepository().getFolder(
fileInfo.getFolderInfo());
String path;
int i = fileInfo.getRelativeName().lastIndexOf('/');
if (i > 0) {
path = fileInfo.getRelativeName().substring(i,
fileInfo.getRelativeName().length());
} else {
path = "/";
}
File newFile = FileUtils.buildFileFromRelativeName(folder
.getLocalBase(), path + newName);
return !newFile.exists();
}
/**
* Tries to find a shorter, unique filename in a folder for a file that has
* a really long name.
*
* @see #isTooLong(String)
* @see TooLongFilenameProblem
* @param controller
* @param fileInfo
* @return
*/
public static String getShorterFilename(Controller controller,
FileInfo fileInfo)
{
int length = Math.min(MAX_FILENAME_LENGTH, fileInfo.getFilenameOnly()
.length());
while (!isUnique(controller, fileInfo.getFilenameOnly().substring(0,
length), fileInfo))
{
length--;
if (length < 1) {
throw new IllegalStateException(
"Length too small when shortening "
+ fileInfo.getFilenameOnly());
}
}
return fileInfo.getFilenameOnly().substring(0, length);
}
/**
* Makes a unique filename in a folder.
*
* @param controller
* @param fileInfo
* @return
*/
public static String makeUnique(Controller controller, FileInfo fileInfo) {
String filename = fileInfo.getFilenameOnly();
String extension = "";
if (filename.contains(".")) {
extension = filename.substring(filename.lastIndexOf('.'));
filename = filename.substring(0, filename.lastIndexOf('.'));
}
String extra = "-1";
int count = 1;
while (!isUnique(controller, filename + extra + extension, fileInfo)) {
extra = "-" + count++;
if (count >= 1000) {
throw new IllegalStateException(
"Unable to fina a unique filename. Ended at: " + filename
+ extra + extension);
}
}
return filename + extra + extension;
}
}