Newer
Older

Lyubomir Marinov
committed
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.impl.fileaccess;
import java.io.*;

Lyubomir Marinov
committed
import org.jitsi.service.configuration.*;
import org.jitsi.service.fileaccess.*;
import org.jitsi.service.libjitsi.*;

Lyubomir Marinov
committed
import org.jitsi.util.*;
import com.sun.jna.*;
import com.sun.jna.ptr.*;
import com.sun.jna.win32.*;

Lyubomir Marinov
committed
/**
* Default FileAccessService implementation.
*
* @author Alexander Pelov
* @author Lyubomir Marinov
*/
public class FileAccessServiceImpl implements FileAccessService
{
/**
* The <tt>Logger</tt> used by the <tt>FileAccessServiceImpl</tt> class and
* its instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(FileAccessServiceImpl.class);
/**
* The file prefix for all temp files.
*/
public static final String TEMP_FILE_PREFIX = "SIPCOMM";
/**
* The file suffix for all temp files.
*/
public static final String TEMP_FILE_SUFFIX = "TEMP";

Lyubomir Marinov
committed
private String profileDirLocation;
private String cacheDirLocation;
private String logDirLocation;
private String scHomeDirName;
/**
* The indicator which determines whether {@link #initialize()} has been
* invoked on this instance. Introduced to delay the initialization of the
* state of this instance until it is actually necessary.
*/
private boolean initialized = false;

Lyubomir Marinov
committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public FileAccessServiceImpl()
{
}
/**
* This method returns a created temporary file. After you close this file
* it is not guaranteed that you will be able to open it again nor that it
* will contain any information.
*
* Note: DO NOT store unencrypted sensitive information in this file
*
* @return The created temporary file
* @throws IOException
* If the file cannot be created
*/
public File getTemporaryFile()
throws IOException
{
File retVal = null;
try
{
logger.logEntry();
retVal = TempFileManager.createTempFile(TEMP_FILE_PREFIX,
TEMP_FILE_SUFFIX);
}
finally
{
logger.logExit();
}
return retVal;
}
/**
* Returns the temporary directory.
*
* @return the created temporary directory
* @throws IOException if the temporary directory cannot not be created
*/
public File getTemporaryDirectory() throws IOException
{
File file = getTemporaryFile();
if (!file.delete())
{
throw new IOException("Could not create temporary directory, "
+ "because: could not delete temporary file.");
}
if (!file.mkdirs())
{
throw new IOException("Could not create temporary directory");
}
return file;
}
/**
* Please use {@link #getPrivatePersistentFile(String, FileCategory)}.
*/
@Deprecated
public File getPrivatePersistentFile(String fileName)
throws Exception
{
return this.getPrivatePersistentFile(fileName, FileCategory.PROFILE);
}

Lyubomir Marinov
committed
/**
* This method returns a file specific to the current user. It may not
* exist, but it is guaranteed that you will have the sufficient rights to
* create it.
*
* This file should not be considered secure because the implementor may
* return a file accessible to everyone. Generally it will reside in current

Lyubomir Marinov
committed
* user's homedir, but it may as well reside in a shared directory.
*
* Note: DO NOT store unencrypted sensitive information in this file
*
* @param fileName
* The name of the private file you wish to access
* @param category
* The classification of the file.

Lyubomir Marinov
committed
* @return The file
* @throws Exception if we faile to create the file.
*/
public File getPrivatePersistentFile(String fileName, FileCategory category)

Lyubomir Marinov
committed
throws Exception
{

Lyubomir Marinov
committed
File file = null;
try
{
file = accessibleFile(getFullPath(category), fileName);

Lyubomir Marinov
committed
if (file == null)
{
throw new SecurityException("Insufficient rights to access "
+ "this file in current user's home directory: "
+ new File(getFullPath(category), fileName).getPath());

Lyubomir Marinov
committed
}
}
finally
{
logger.logExit();
}
return file;
}
/**
* Please use {@link #getPrivatePersistentDirectory(String, FileCategory)}
*/
@Deprecated
public File getPrivatePersistentDirectory(String dirName)
throws Exception
{
return getPrivatePersistentDirectory(dirName, FileCategory.PROFILE);
}

Lyubomir Marinov
committed
/**
* This method creates a directory specific to the current user.
*
* This directory should not be considered secure because the implementor
* may return a directory accessible to everyone. Generally it will reside
* in current user's homedir, but it may as well reside in a shared
* directory.

Lyubomir Marinov
committed
*
* It is guaranteed that you will be able to create files in it.
*
* Note: DO NOT store unencrypted sensitive information in this file
*
* @param dirName
* The name of the private directory you wish to access.
* @param category
* The classification of the directory.

Lyubomir Marinov
committed
* @return The created directory.
* @throws Exception
* Thrown if there is no suitable location for the persistent
* directory.
*/
public File getPrivatePersistentDirectory(String dirName,
FileCategory category) throws Exception

Lyubomir Marinov
committed
{
File dir = new File(getFullPath(category), dirName);

Lyubomir Marinov
committed
if (dir.exists())
{
if (!dir.isDirectory())
{
throw new RuntimeException("Could not create directory "
+ "because: A file exists with this name:"
+ dir.getAbsolutePath());
}
}
else
{
if (!dir.mkdirs())
{
throw new IOException("Could not create directory");
}
}
return dir;
}
/**
* Returns the full path corresponding to a file located in the

Lyubomir Marinov
committed
* sip-communicator config home and carrying the specified name.
* @param category The classification of the file or directory.
* @return the config home location of a a file with the specified name.

Lyubomir Marinov
committed
*/
private File getFullPath(FileCategory category)

Lyubomir Marinov
committed
{

Lyubomir Marinov
committed
initialize();

Lyubomir Marinov
committed
// bypass the configurationService here to remove the dependency

Lyubomir Marinov
committed

Lyubomir Marinov
committed
{
case CACHE:
directory = this.cacheDirLocation;
break;
case LOG:
directory = this.logDirLocation;
break;
default:
directory = this.profileDirLocation;
break;

Lyubomir Marinov
committed
}
return new File(directory, this.scHomeDirName);

Lyubomir Marinov
committed
}
/**
* Returns the value of the specified java system property. In case the
* value was a zero length String or one that only contained whitespaces,
* null is returned. This method is for internal use only. Users of the
* configuration service are to use the getProperty() or getString() methods
* which would automatically determine whether a property is system or not.
* @param propertyName the name of the property whose value we need.
* @return the value of the property with name propertyName or null if
* the value had length 0 or only contained spaces tabs or new lines.
*/
private static String getSystemProperty(String propertyName)
{
String retval = System.getProperty(propertyName);
if (retval == null){
return retval;
}
if (retval.trim().length() == 0){
return null;
}
return retval;
}

Lyubomir Marinov
committed
/**
* Checks if a file exists and if it is writable or readable. If not -
* checks if the user has a write privileges to the containing directory.
*
* If those conditions are met it returns a File in the directory with a
* fileName. If not - returns null.
*
* @param homedir the location of the sip-communicator home directory.
* @param fileName the name of the file to create.
* @return Returns null if the file does not exist and cannot be created.
* Otherwise - an object to this file
* @throws IOException
* Thrown if the home directory cannot be created
*/
private static File accessibleFile(File homedir, String fileName)

Lyubomir Marinov
committed
throws IOException
{
File file = null;
try
{
logger.logEntry();
file = new File(homedir, fileName);

Lyubomir Marinov
committed
if (file.canRead() || file.canWrite())
{
return file;
}

Lyubomir Marinov
committed
{
if (logger.isDebugEnabled())
logger.debug("Creating home directory : "
+ homedir.getAbsolutePath());
if (!homedir.mkdirs())

Lyubomir Marinov
committed
{
String message = "Could not create the home directory : "
+ homedir.getAbsolutePath();

Lyubomir Marinov
committed
if (logger.isDebugEnabled())
logger.debug(message);
throw new IOException(message);
}
if (logger.isDebugEnabled())
logger.debug("Home directory created : "
+ homedir.getAbsolutePath());

Lyubomir Marinov
committed
}
else if (!homedir.canWrite())

Lyubomir Marinov
committed
{
file = null;
}
if(file != null && !file.getParentFile().exists())
{
if (!file.getParentFile().mkdirs())
{
String message = "Could not create the parent directory : "
+ homedir.getAbsolutePath();

Lyubomir Marinov
committed

Lyubomir Marinov
committed
throw new IOException(message);
}
}

Lyubomir Marinov
committed
{
logger.logExit();
}
return file;
}
/**
* Returns the default download directory.
*
* @return the default download directory
* @throws IOException if it I/O error occurred
*/
public File getDefaultDownloadDirectory()
throws IOException
{
// For Windows use the intended API to get the correct location.
// Everything else is prone to failure due to folder redirection and
// roaming profiles.
if (OSUtils.IS_WINDOWS)

Lyubomir Marinov
committed
{
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
if (getMajorOSVersion() < 6)
{
char[] pszPath = new char[Shell32.MAX_PATH];
int hResult = Shell32.INSTANCE.SHGetFolderPath(
null,
Shell32.CSIDL_MYDOCUMENTS,
null,
Shell32.SHGFP_TYPE_CURRENT, pszPath);
if (hResult == Shell32.S_OK)
{
String path = new String(pszPath);
return new File(path.substring(0, path.indexOf('\0')));
}
}
else
{
// FOLDERID_Downloads
GUID g = new GUID();
g.data1 = 0x374DE290;
g.data2 = 0x123F;
g.data3 = 0x4565;
g.data4 = new byte[] { (byte) 0x91, 0x64,
0x39, (byte) 0xC4, (byte) 0x92, 0x5E, 0x46, 0x7B };
PointerByReference pszPath = new PointerByReference();
int hResult = Shell32.INSTANCE.SHGetKnownFolderPath(
g,
Shell32.KF_FLAG_INIT | Shell32.KF_FLAG_CREATE,
null,
pszPath);
if (hResult == Shell32.S_OK)
{
File f = new File(pszPath.getValue().getString(0, true));
Ole32.INSTANCE.CoTaskMemFree(pszPath.getValue());
return f;
}
}

Lyubomir Marinov
committed
}

Lyubomir Marinov
committed
// For all other operating systems we return the Downloads folder.
return new File(getSystemProperty("user.home"), "Downloads");
}

Lyubomir Marinov
committed
private static class HANDLE extends PointerType implements NativeMapped
{}
private static class HWND extends HANDLE
{}
public static class GUID extends Structure
{
//public static class ByValue extends GUID implements Structure.ByValue {}
public int data1;
public short data2;
public short data3;
public byte[] data4;
}

paweldomas
committed
private static Map<String, Object> OPT;
static
{
if(OSUtils.IS_WINDOWS)
{

Lyubomir Marinov
committed
OPT = new HashMap<String, Object>();
OPT.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
OPT.put(
Library.OPTION_FUNCTION_MAPPER,
W32APIFunctionMapper.UNICODE);

paweldomas
committed
}
}
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
private static interface Shell32 extends Library
{
public static final int MAX_PATH = 260;
public static final int CSIDL_MYDOCUMENTS = 5;
public static final int SHGFP_TYPE_CURRENT = 0;
public static final int S_OK = 0;
public static final int KF_FLAG_INIT = 0x00000800;
public static final int KF_FLAG_CREATE = 0x00008000;
static Shell32 INSTANCE = (Shell32)Native.loadLibrary("shell32",
Shell32.class, OPT);
/**
* http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
*/
public int SHGetFolderPath(HWND hwndOwner, int nFolder, HANDLE hToken,
int dwFlags, char[] pszPath);
/**
* http://msdn.microsoft.com/en-us/library/bb762188(v=vs.85).aspx
*/
public int SHGetKnownFolderPath(GUID rfid, int dwFlags, HANDLE hToken,
PointerByReference pszPath);
}
private interface Ole32 extends Library
{
static Ole32 INSTANCE = (Ole32)Native.loadLibrary("Ole32",
Ole32.class, OPT);

Lyubomir Marinov
committed
public void CoTaskMemFree(Pointer p);

Lyubomir Marinov
committed
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
}
/**
* Gets the major version of the executing operating system as defined by
* the <tt>os.version</tt> system property.
*
* @return the major version of the executing operating system as defined by
* the <tt>os.version</tt> system property
*/
private static int getMajorOSVersion()
{
String osVersion = System.getProperty("os.version");
int majorOSVersion;
if ((osVersion != null) && (osVersion.length() > 0))
{
int majorOSVersionEnd = osVersion.indexOf('.');
String majorOSVersionString
= (majorOSVersionEnd > -1)
? osVersion.substring(0, majorOSVersionEnd)
: osVersion;
majorOSVersion = Integer.parseInt(majorOSVersionString);
}
else
majorOSVersion = 0;
return majorOSVersion;
}
/**
* Creates a failsafe transaction which can be used to safely store
* informations into a file.
*
* @param file The file concerned by the transaction, null if file is null.
*
* @return A new failsafe transaction related to the given file.
*/
public FailSafeTransaction createFailSafeTransaction(File file)
{
return (file == null) ? null : new FailSafeTransactionImpl(file);
}

Lyubomir Marinov
committed
/**
* Initializes this instance if it has not been initialized yet i.e. acts as
* a delayed constructor of this instance. Introduced because this
* <tt>FileAccessServiceImpl</tt> queries <tt>System</tt> properties that
* may not be set yet at construction time and, consequently, throws an
* <tt>IllegalStateException</tt> which could be avoided.
*/
private synchronized void initialize()
{
if (initialized)
return;
ConfigurationService cfg = LibJitsi.getConfigurationService();

Lyubomir Marinov
committed
profileDirLocation
= cfg != null
? cfg.getScHomeDirLocation()
: getSystemProperty(

Lyubomir Marinov
committed
ConfigurationService.PNAME_SC_HOME_DIR_LOCATION);
if (profileDirLocation == null)
{
throw new IllegalStateException(
ConfigurationService.PNAME_SC_HOME_DIR_LOCATION);
}

Lyubomir Marinov
committed
scHomeDirName
= cfg != null
? cfg.getScHomeDirName()
: getSystemProperty(
ConfigurationService.PNAME_SC_HOME_DIR_NAME);

Lyubomir Marinov
committed
if (scHomeDirName == null)
{
throw new IllegalStateException(
ConfigurationService.PNAME_SC_HOME_DIR_NAME);
}
String cacheDir
= getSystemProperty(
ConfigurationService.PNAME_SC_CACHE_DIR_LOCATION);
cacheDirLocation = (cacheDir == null) ? profileDirLocation : cacheDir;
String logDir
= getSystemProperty(
ConfigurationService.PNAME_SC_LOG_DIR_LOCATION);
logDirLocation = (logDir == null) ? profileDirLocation : logDir;
initialized = true;
}