2013-07-30 14:42:16 -04:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
using System.Text;
|
2013-10-07 00:28:06 -04:00
|
|
|
|
using Android.Content;
|
|
|
|
|
using Android.OS;
|
2013-07-30 14:42:16 -04:00
|
|
|
|
using KeePassLib.Cryptography;
|
|
|
|
|
using KeePassLib.Serialization;
|
|
|
|
|
using KeePassLib.Utility;
|
|
|
|
|
|
|
|
|
|
namespace keepass2android.Io
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Interface for classes which can handle certain Cache events on a higher level (e.g. by user interaction)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public interface ICacheSupervisor
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// called when a save operation only updated the cache but not the remote file
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ioc">The file which we tried to write</param>
|
2013-10-07 00:28:06 -04:00
|
|
|
|
/// <param name="ex">The exception why the remote file couldn't be updated</param>
|
2013-08-08 14:40:02 -04:00
|
|
|
|
void CouldntSaveToRemote(IOConnectionInfo ioc, Exception ex);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when only the local file could be opened during an open operation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex);
|
|
|
|
|
|
2013-08-14 00:05:25 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when the local file either didn't exist or was unmodified, so the remote file
|
|
|
|
|
/// was loaded and the cache was updated during the load operation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
void UpdatedCachedFileOnLoad(IOConnectionInfo ioc);
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when the remote file either didn't exist or was unmodified, so the local file
|
|
|
|
|
/// was loaded and the remote file was updated during the load operation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc);
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called to notify the supervisor that the file described by ioc is opened from the cache because there's a conflict
|
|
|
|
|
/// with local and remote changes
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ioc"></param>
|
|
|
|
|
void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc);
|
2013-08-14 00:05:25 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when the load operation was performed and the remote file was identical with the local file
|
|
|
|
|
/// </summary>
|
|
|
|
|
void LoadedFromRemoteInSync(IOConnectionInfo ioc);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Implements the IFileStorage interface as a proxy: A base storage is used as a remote storage. Local files are used to cache the
|
|
|
|
|
/// files on remote.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class CachingFileStorage: IFileStorage
|
|
|
|
|
{
|
2013-11-20 13:14:57 -05:00
|
|
|
|
protected readonly IFileStorage _cachedStorage;
|
2013-07-30 14:42:16 -04:00
|
|
|
|
private readonly ICacheSupervisor _cacheSupervisor;
|
|
|
|
|
private readonly string _streamCacheDir;
|
|
|
|
|
|
|
|
|
|
public CachingFileStorage(IFileStorage cachedStorage, string cacheDir, ICacheSupervisor cacheSupervisor)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage = cachedStorage;
|
|
|
|
|
_cacheSupervisor = cacheSupervisor;
|
|
|
|
|
_streamCacheDir = cacheDir + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator;
|
|
|
|
|
if (!Directory.Exists(_streamCacheDir))
|
|
|
|
|
Directory.CreateDirectory(_streamCacheDir);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ClearCache()
|
|
|
|
|
{
|
|
|
|
|
IoUtil.DeleteDir(new Java.IO.File(_streamCacheDir), true);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-15 14:08:14 -04:00
|
|
|
|
public IEnumerable<string> SupportedProtocols { get { return _cachedStorage.SupportedProtocols; } }
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
public void DeleteFile(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
if (IsCached(ioc))
|
|
|
|
|
{
|
|
|
|
|
File.Delete(CachedFilePath(ioc));
|
|
|
|
|
File.Delete(VersionFilePath(ioc));
|
|
|
|
|
File.Delete(BaseVersionFilePath(ioc));
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-28 01:46:44 -04:00
|
|
|
|
_cachedStorage.Delete(ioc);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string CachedFilePath(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
SHA256Managed sha256 = new SHA256Managed();
|
|
|
|
|
string iocAsHexString = MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray())))+".cache";
|
|
|
|
|
return _streamCacheDir + iocAsHexString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool IsCached(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return File.Exists(CachedFilePath(ioc))
|
|
|
|
|
&& File.Exists(VersionFilePath(ioc))
|
|
|
|
|
&& File.Exists(BaseVersionFilePath(ioc));
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-28 01:46:44 -04:00
|
|
|
|
public void Delete(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage.Delete(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
|
|
|
|
{
|
|
|
|
|
//see comment in GetCurrentFileVersionFast
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
//fast file version checking is not supported by CachingFileStorage:
|
|
|
|
|
//it's hard to return good versions in cases that the base source is offline
|
|
|
|
|
//or after modifying the cache.
|
|
|
|
|
//It's probably not relevant because fast file version checking is meant for local storage
|
|
|
|
|
//which is not cached.
|
|
|
|
|
return String.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string VersionFilePath(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return CachedFilePath(ioc)+".version";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string BaseVersionFilePath(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return CachedFilePath(ioc) + ".baseversion";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
string cachedFilePath = CachedFilePath(ioc);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!IsCached(ioc)
|
2013-08-01 16:20:39 -04:00
|
|
|
|
|| GetLocalVersionHash(ioc) == GetBaseVersionHash(ioc))
|
2013-07-30 14:42:16 -04:00
|
|
|
|
{
|
|
|
|
|
return OpenFileForReadWhenNoLocalChanges(ioc, cachedFilePath);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return OpenFileForReadWhenLocalChanges(ioc, cachedFilePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2013-08-14 00:36:12 -04:00
|
|
|
|
if (!IsCached(ioc))
|
|
|
|
|
throw;
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
Kp2aLog.Log("couldn't open from remote " + ioc.Path);
|
|
|
|
|
Kp2aLog.Log(ex.ToString());
|
|
|
|
|
|
|
|
|
|
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
|
|
|
|
|
return File.OpenRead(cachedFilePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Stream OpenFileForReadWhenLocalChanges(IOConnectionInfo ioc, string cachedFilePath)
|
|
|
|
|
{
|
|
|
|
|
//file is cached but has local modifications
|
|
|
|
|
//try to upload the changes if remote file doesn't have changes as well:
|
2013-08-01 16:20:39 -04:00
|
|
|
|
var hash = CalculateHash(ioc);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
|
|
|
|
|
if (File.ReadAllText(BaseVersionFilePath(ioc)) == hash)
|
|
|
|
|
{
|
|
|
|
|
//no changes in remote file -> upload
|
|
|
|
|
using (Stream localData = File.OpenRead(CachedFilePath(ioc)))
|
|
|
|
|
{
|
2013-08-14 00:05:25 -04:00
|
|
|
|
if (TryUpdateRemoteFile(localData, ioc, true, hash))
|
|
|
|
|
_cacheSupervisor.UpdatedRemoteFileOnLoad(ioc);
|
2013-11-20 13:14:57 -05:00
|
|
|
|
return File.OpenRead(cachedFilePath);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//conflict: both files changed.
|
2013-11-20 13:14:57 -05:00
|
|
|
|
return OpenFileForReadWithConflict(ioc, cachedFilePath);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
2013-11-20 13:14:57 -05:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
|
|
|
|
|
{
|
|
|
|
|
//signal that we're loading from local
|
|
|
|
|
_cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
return File.OpenRead(cachedFilePath);
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-01 16:20:39 -04:00
|
|
|
|
public MemoryStream GetRemoteDataAndHash(IOConnectionInfo ioc, out string hash)
|
2013-07-30 14:42:16 -04:00
|
|
|
|
{
|
|
|
|
|
MemoryStream remoteData = new MemoryStream();
|
2013-11-22 15:58:32 -05:00
|
|
|
|
|
|
|
|
|
using (var remoteStream =_cachedStorage.OpenFileForRead(ioc))
|
2013-07-30 14:42:16 -04:00
|
|
|
|
{
|
2013-11-22 15:58:32 -05:00
|
|
|
|
//note: directly copying to remoteData and hashing causes NullReferenceExceptions in FTP and with Digest auth
|
|
|
|
|
// -> use the temp data approach:
|
|
|
|
|
MemoryStream tempData = new MemoryStream();
|
|
|
|
|
remoteStream.CopyTo(tempData);
|
|
|
|
|
tempData.Position = 0;
|
|
|
|
|
HashingStreamEx hashingRemoteStream = new HashingStreamEx(tempData, false, new SHA256Managed());
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
hashingRemoteStream.CopyTo(remoteData);
|
|
|
|
|
hashingRemoteStream.Close();
|
|
|
|
|
hash = MemUtil.ByteArrayToHexString(hashingRemoteStream.Hash);
|
|
|
|
|
}
|
2013-08-01 16:20:39 -04:00
|
|
|
|
remoteData.Position = 0;
|
|
|
|
|
return remoteData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string CalculateHash(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
string hash;
|
|
|
|
|
GetRemoteDataAndHash(ioc, out hash);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Stream OpenFileForReadWhenNoLocalChanges(IOConnectionInfo ioc, string cachedFilePath)
|
|
|
|
|
{
|
2013-08-14 00:05:25 -04:00
|
|
|
|
|
2013-11-20 13:14:57 -05:00
|
|
|
|
//remember current hash
|
|
|
|
|
string previousHash = null;
|
|
|
|
|
string baseVersionFilePath = BaseVersionFilePath(ioc);
|
|
|
|
|
if (File.Exists(baseVersionFilePath))
|
|
|
|
|
previousHash = File.ReadAllText(baseVersionFilePath);
|
|
|
|
|
|
2013-08-14 00:05:25 -04:00
|
|
|
|
|
2013-11-20 13:14:57 -05:00
|
|
|
|
|
|
|
|
|
//copy to cache:
|
|
|
|
|
var fileHash = UpdateCacheFromRemote(ioc, cachedFilePath);
|
2013-08-14 00:05:25 -04:00
|
|
|
|
|
|
|
|
|
//notify supervisor what we did:
|
|
|
|
|
if (previousHash != fileHash)
|
|
|
|
|
_cacheSupervisor.UpdatedCachedFileOnLoad(ioc);
|
|
|
|
|
else
|
|
|
|
|
_cacheSupervisor.LoadedFromRemoteInSync(ioc);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
|
|
|
|
|
return File.OpenRead(cachedFilePath);
|
2013-11-20 13:14:57 -05:00
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 13:14:57 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// copies the file in ioc to the local cache. Updates the cache version files and returns the new file hash.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected string UpdateCacheFromRemote(IOConnectionInfo ioc, string cachedFilePath)
|
|
|
|
|
{
|
|
|
|
|
//note: we might use the file version to check if it's already in the cache and if copying is required.
|
|
|
|
|
//However, this is safer.
|
|
|
|
|
string fileHash;
|
|
|
|
|
|
|
|
|
|
//open stream:
|
|
|
|
|
using (Stream remoteFile = _cachedStorage.OpenFileForRead(ioc))
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
|
|
|
|
|
{
|
|
|
|
|
remoteFile.CopyTo(cachedFile);
|
|
|
|
|
cachedFile.Close();
|
|
|
|
|
fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//save hash in cache files:
|
|
|
|
|
File.WriteAllText(VersionFilePath(ioc), fileHash);
|
|
|
|
|
File.WriteAllText(BaseVersionFilePath(ioc), fileHash);
|
|
|
|
|
return fileHash;
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-14 00:05:25 -04:00
|
|
|
|
private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
2013-07-30 14:42:16 -04:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2013-08-01 16:20:39 -04:00
|
|
|
|
UpdateRemoteFile(cachedData, ioc, useFileTransaction, hash);
|
2013-08-14 00:05:25 -04:00
|
|
|
|
return true;
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Kp2aLog.Log("couldn't save to remote " + ioc.Path);
|
|
|
|
|
Kp2aLog.Log(e.ToString());
|
|
|
|
|
//notify the supervisor so it might display a warning or schedule a retry
|
|
|
|
|
_cacheSupervisor.CouldntSaveToRemote(ioc, e);
|
2013-08-14 00:05:25 -04:00
|
|
|
|
return false;
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-20 13:14:57 -05:00
|
|
|
|
protected void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
2013-08-01 16:20:39 -04:00
|
|
|
|
{
|
|
|
|
|
//try to write to remote:
|
|
|
|
|
using (
|
|
|
|
|
IWriteTransaction remoteTrans = _cachedStorage.OpenWriteTransaction(ioc, useFileTransaction))
|
|
|
|
|
{
|
|
|
|
|
Stream remoteStream = remoteTrans.OpenFile();
|
|
|
|
|
cachedData.CopyTo(remoteStream);
|
|
|
|
|
remoteStream.Close();
|
|
|
|
|
remoteTrans.CommitWrite();
|
|
|
|
|
}
|
|
|
|
|
//success. Update base-version of cache:
|
|
|
|
|
File.WriteAllText(BaseVersionFilePath(ioc), hash);
|
|
|
|
|
File.WriteAllText(VersionFilePath(ioc), hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void UpdateRemoteFile(IOConnectionInfo ioc, bool useFileTransaction)
|
|
|
|
|
{
|
|
|
|
|
using (Stream cachedData = File.OpenRead(CachedFilePath(ioc)))
|
|
|
|
|
{
|
|
|
|
|
UpdateRemoteFile(cachedData, ioc, useFileTransaction, GetLocalVersionHash(ioc));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
private class CachedWriteTransaction: IWriteTransaction
|
|
|
|
|
{
|
|
|
|
|
private class CachedWriteMemoryStream : MemoryStream
|
|
|
|
|
{
|
|
|
|
|
private readonly IOConnectionInfo ioc;
|
|
|
|
|
private readonly CachingFileStorage _cachingFileStorage;
|
|
|
|
|
private readonly bool _useFileTransaction;
|
2013-08-01 16:20:39 -04:00
|
|
|
|
private bool _closed;
|
2013-07-30 14:42:16 -04:00
|
|
|
|
|
|
|
|
|
public CachedWriteMemoryStream(IOConnectionInfo ioc, CachingFileStorage cachingFileStorage, bool useFileTransaction)
|
|
|
|
|
{
|
|
|
|
|
this.ioc = ioc;
|
|
|
|
|
_cachingFileStorage = cachingFileStorage;
|
|
|
|
|
_useFileTransaction = useFileTransaction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override void Close()
|
|
|
|
|
{
|
2013-08-01 16:20:39 -04:00
|
|
|
|
if (_closed) return;
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
//write file to cache:
|
|
|
|
|
//(note: this might overwrite local changes. It's assumed that a sync operation or check was performed before
|
|
|
|
|
string hash;
|
|
|
|
|
using (var hashingStream = new HashingStreamEx(File.Create(_cachingFileStorage.CachedFilePath(ioc)), true, new SHA256Managed()))
|
|
|
|
|
{
|
|
|
|
|
Position = 0;
|
|
|
|
|
CopyTo(hashingStream);
|
|
|
|
|
|
|
|
|
|
hashingStream.Close();
|
|
|
|
|
hash = MemUtil.ByteArrayToHexString(hashingStream.Hash);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(_cachingFileStorage.VersionFilePath(ioc), hash);
|
|
|
|
|
//update file on remote. This might overwrite changes there as well, see above.
|
|
|
|
|
Position = 0;
|
2013-08-01 16:20:39 -04:00
|
|
|
|
if (_cachingFileStorage.IsCached(ioc))
|
|
|
|
|
{
|
|
|
|
|
//if the file already is in the cache, it's ok if writing to remote fails.
|
|
|
|
|
_cachingFileStorage.TryUpdateRemoteFile(this, ioc, _useFileTransaction, hash);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//if not, we don't accept a failure (e.g. invalid credentials would always remain a problem)
|
|
|
|
|
_cachingFileStorage.UpdateRemoteFile(this, ioc, _useFileTransaction, hash);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
base.Close();
|
2013-08-01 16:20:39 -04:00
|
|
|
|
|
|
|
|
|
_closed = true;
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private readonly IOConnectionInfo _ioc;
|
|
|
|
|
private readonly bool _useFileTransaction;
|
|
|
|
|
private readonly CachingFileStorage _cachingFileStorage;
|
|
|
|
|
private MemoryStream _memoryStream;
|
|
|
|
|
private bool _committed;
|
|
|
|
|
|
|
|
|
|
public CachedWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction, CachingFileStorage cachingFileStorage)
|
|
|
|
|
{
|
|
|
|
|
_ioc = ioc;
|
|
|
|
|
_useFileTransaction = useFileTransaction;
|
|
|
|
|
_cachingFileStorage = cachingFileStorage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (!_committed)
|
2013-08-01 16:20:39 -04:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_memoryStream.Dispose();
|
|
|
|
|
}
|
|
|
|
|
catch (ObjectDisposedException e)
|
|
|
|
|
{
|
|
|
|
|
Kp2aLog.Log("Ignoring exception in Dispose: "+e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Stream OpenFile()
|
|
|
|
|
{
|
|
|
|
|
_memoryStream = new CachedWriteMemoryStream(_ioc, _cachingFileStorage, _useFileTransaction);
|
|
|
|
|
return _memoryStream;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CommitWrite()
|
|
|
|
|
{
|
|
|
|
|
//the transaction is committed in the stream's Close
|
|
|
|
|
_committed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
|
|
|
|
{
|
|
|
|
|
//create a transaction which writes to memory stream
|
|
|
|
|
//on close: write to cache. If possible, write to online
|
|
|
|
|
//update versions
|
|
|
|
|
return new CachedWriteTransaction(ioc, useFileTransaction, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
|
|
|
|
{
|
2013-12-16 00:50:02 -05:00
|
|
|
|
return _cachedStorage.GetFilenameWithoutPathAndExt(ioc);
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
2013-08-01 16:20:39 -04:00
|
|
|
|
|
2013-09-15 14:08:14 -04:00
|
|
|
|
public bool RequiresCredentials(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.RequiresCredentials(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-24 22:05:37 -04:00
|
|
|
|
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
2013-09-28 01:46:44 -04:00
|
|
|
|
{
|
2013-10-24 22:05:37 -04:00
|
|
|
|
_cachedStorage.CreateDirectory(ioc, newDirName);
|
2013-09-28 01:46:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.ListContents(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-28 15:14:21 -04:00
|
|
|
|
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.GetFileDescription(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-07 00:28:06 -04:00
|
|
|
|
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.RequiresSetup(ioConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string IocToPath(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.IocToPath(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage.StartSelectFile(activity, isForSave, requestCode, protocolId);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-07 15:33:35 -05:00
|
|
|
|
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess)
|
2013-10-07 00:28:06 -04:00
|
|
|
|
{
|
2013-11-07 15:33:35 -05:00
|
|
|
|
//we try to prepare the file usage by the underlying file storage but if the ioc is cached, set the flag to ignore errors
|
|
|
|
|
_cachedStorage.PrepareFileUsage(activity, ioc, requestCode, alwaysReturnSuccess || IsCached(ioc));
|
2013-10-07 00:28:06 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage.OnCreate(activity, savedInstanceState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnResume(IFileStorageSetupActivity activity)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage.OnResume(activity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnStart(IFileStorageSetupActivity activity)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage.OnStart(activity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
|
|
|
|
{
|
|
|
|
|
_cachedStorage.OnActivityResult(activity, requestCode, resultCode, data);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-27 10:06:57 -04:00
|
|
|
|
public string GetDisplayName(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.GetDisplayName(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-27 16:55:19 -04:00
|
|
|
|
public string CreateFilePath(string parent, string newFilename)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.CreateFilePath(parent, newFilename);
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-17 01:17:15 -05:00
|
|
|
|
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.GetParentPath(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
IOConnectionInfo res = _cachedStorage.GetFilePath(folderPath, filename);
|
|
|
|
|
//some file storage implementations require accessing the network to determine the file path (e.g. because
|
|
|
|
|
//they might contain file ids). In this case, we need to cache the result to enable cached access to such files
|
|
|
|
|
StoreFilePath(folderPath, filename, res);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
IOConnectionInfo res;
|
|
|
|
|
if (!TryGetCachedFilePath(folderPath, filename, out res)) throw;
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res)
|
|
|
|
|
{
|
|
|
|
|
File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IOConnectionInfo GetPseudoIoc(IOConnectionInfo folderPath, string filename)
|
|
|
|
|
{
|
|
|
|
|
IOConnectionInfo res = folderPath.CloneDeep();
|
|
|
|
|
if (!res.Path.EndsWith("/"))
|
|
|
|
|
res.Path += "/";
|
|
|
|
|
res.Path += filename;
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TryGetCachedFilePath(IOConnectionInfo folderPath, string filename, out IOConnectionInfo res)
|
|
|
|
|
{
|
|
|
|
|
res = folderPath.CloneDeep();
|
|
|
|
|
string filePathCache = CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath";
|
|
|
|
|
if (!File.Exists(filePathCache))
|
|
|
|
|
return false;
|
|
|
|
|
res.Path = File.ReadAllText(filePathCache);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-01 16:20:39 -04:00
|
|
|
|
|
|
|
|
|
public string GetBaseVersionHash(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return File.ReadAllText(BaseVersionFilePath(ioc));
|
|
|
|
|
}
|
|
|
|
|
public string GetLocalVersionHash(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return File.ReadAllText(VersionFilePath(ioc));
|
|
|
|
|
}
|
|
|
|
|
public bool HasLocalChanges(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
return IsCached(ioc)
|
|
|
|
|
&& GetLocalVersionHash(ioc) != GetBaseVersionHash(ioc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Stream OpenRemoteForReadIfAvailable(IOConnectionInfo ioc)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return _cachedStorage.OpenFileForRead(ioc);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
return File.OpenRead(CachedFilePath(ioc));
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-07-30 14:42:16 -04:00
|
|
|
|
}
|
|
|
|
|
}
|