mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-14 21:45:14 -05:00
implement first version of NetFtpFileStorage
This commit is contained in:
parent
5fdcc28328
commit
53877a16a9
@ -256,10 +256,6 @@ namespace keepass2android.Io
|
||||
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class AndroidContentWriteTransaction : IWriteTransaction
|
||||
|
@ -20,7 +20,7 @@ using IOException = System.IO.IOException;
|
||||
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
public class BuiltInFileStorage : IFileStorage, IPermissionRequestingFileStorage
|
||||
public abstract class BuiltInFileStorage : IFileStorage, IPermissionRequestingFileStorage
|
||||
{
|
||||
private const string PermissionGrantedKey = "PermissionGranted";
|
||||
|
||||
@ -56,18 +56,8 @@ namespace keepass2android.Io
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "file";
|
||||
#if !NoNet
|
||||
yield return "ftp";
|
||||
yield return "http";
|
||||
yield return "https";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public abstract IEnumerable<string> SupportedProtocols { get; }
|
||||
|
||||
public void Delete(IOConnectionInfo ioc)
|
||||
{
|
||||
@ -398,11 +388,6 @@ namespace keepass2android.Io
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private bool IsLocalFileFlaggedReadOnly(IOConnectionInfo ioc)
|
||||
{
|
||||
try
|
||||
@ -429,4 +414,55 @@ namespace keepass2android.Io
|
||||
fileStorageSetupActivity.State.PutBoolean(PermissionGrantedKey, grantResults[0] == Permission.Granted);
|
||||
}
|
||||
}
|
||||
|
||||
class LegacyFtpStorage : BuiltInFileStorage
|
||||
{
|
||||
public LegacyFtpStorage(IKp2aApp app) : base(app)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !NoNet
|
||||
yield return "ftp";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LegacyWebDavStorage : BuiltInFileStorage
|
||||
{
|
||||
public LegacyWebDavStorage(IKp2aApp app) : base(app)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !NoNet
|
||||
yield return "http";
|
||||
yield return "https";
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalFileStorage : BuiltInFileStorage
|
||||
{
|
||||
public LocalFileStorage(IKp2aApp app) : base(app)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "file";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -563,11 +563,6 @@ namespace keepass2android.Io
|
||||
return _cachedStorage.IsReadOnly(ioc, reason);
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
_cachedStorage.ResolveAccount(ioc);
|
||||
}
|
||||
|
||||
private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res)
|
||||
{
|
||||
File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path);
|
||||
|
@ -1,287 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using ModernHttpClient;
|
||||
|
||||
namespace keepass2android.Io
|
||||
{
|
||||
public class HttpFileStorage: IFileStorage
|
||||
{
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return "http";
|
||||
yield return "https";
|
||||
}
|
||||
}
|
||||
|
||||
HttpClient GetHttpClient(IOConnectionInfo ioc)
|
||||
{
|
||||
var handler = new NativeMessageHandler();
|
||||
//var handler = new HttpClientHandler();
|
||||
|
||||
if ((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
|
||||
{
|
||||
int backslashPos = ioc.UserName.IndexOf("\\", StringComparison.Ordinal);
|
||||
if (backslashPos > 0)
|
||||
{
|
||||
string domain = ioc.UserName.Substring(0, backslashPos);
|
||||
string user = ioc.UserName.Substring(backslashPos + 1);
|
||||
handler.Credentials = new NetworkCredential(user, ioc.Password, domain);
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.PreAuthenticate = true;
|
||||
handler.Credentials = new NetworkCredential(ioc.UserName, ioc.Password);
|
||||
}
|
||||
}
|
||||
|
||||
return new HttpClient(handler);
|
||||
|
||||
}
|
||||
|
||||
public void Delete(IOConnectionInfo ioc)
|
||||
{
|
||||
GetHttpClient(ioc).DeleteAsync(ioc.Path).Result.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
return GetHttpClient(ioc).GetStreamAsync(ioc.Path).Result;
|
||||
}
|
||||
|
||||
|
||||
class UploadOnCloseMemoryStream : MemoryStream
|
||||
{
|
||||
IOConnectionInfo ioc;
|
||||
private HttpClient client;
|
||||
|
||||
|
||||
public UploadOnCloseMemoryStream(IOConnectionInfo _ioc, HttpClient _client)
|
||||
{
|
||||
this.ioc = _ioc;
|
||||
this.client = _client;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
var msg = new HttpRequestMessage(HttpMethod.Put, ioc.Path);
|
||||
msg.Headers.Add("Translate", "f");
|
||||
msg.Content = new StreamContent(new MemoryStream(this.ToArray()));
|
||||
|
||||
client.SendAsync(msg).Result.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
}
|
||||
public class UntransactedWrite : IWriteTransaction
|
||||
{
|
||||
private readonly IOConnectionInfo _ioc;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
|
||||
public UntransactedWrite(IOConnectionInfo ioc, HttpClient client)
|
||||
{
|
||||
_ioc = ioc;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Stream OpenFile()
|
||||
{
|
||||
return new UploadOnCloseMemoryStream(_ioc, _client);
|
||||
}
|
||||
|
||||
public void CommitWrite()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class TransactedWrite : IWriteTransaction
|
||||
{
|
||||
private readonly IOConnectionInfo _ioc;
|
||||
private readonly HttpFileStorage _fileStorage;
|
||||
private readonly IOConnectionInfo _iocTemp;
|
||||
|
||||
|
||||
|
||||
public TransactedWrite(IOConnectionInfo ioc, HttpFileStorage fileStorage)
|
||||
{
|
||||
_ioc = ioc;
|
||||
_iocTemp = _ioc.CloneDeep();
|
||||
_iocTemp.Path += "." + new PwUuid(true).ToHexString().Substring(0, 6) + ".tmp";
|
||||
|
||||
_fileStorage = fileStorage;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Stream OpenFile()
|
||||
{
|
||||
return new UploadOnCloseMemoryStream(_ioc, _fileStorage.GetHttpClient(_ioc));
|
||||
}
|
||||
|
||||
public void CommitWrite()
|
||||
{
|
||||
var client = _fileStorage.GetHttpClient(_ioc);
|
||||
client.DeleteAsync(_ioc.Path).Result.EnsureSuccessStatusCode();
|
||||
var msg = new HttpRequestMessage(new HttpMethod("MOVE"), _iocTemp.Path);
|
||||
msg.Headers.Add("Destination", _ioc.Path);
|
||||
client.SendAsync(msg).Result.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
if (useFileTransaction)
|
||||
return new TransactedWrite(ioc, this);
|
||||
else
|
||||
return new UntransactedWrite(ioc, GetHttpClient(ioc));
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
{
|
||||
return UrlUtil.StripExtension(
|
||||
UrlUtil.GetFileName(ioc.Path));
|
||||
}
|
||||
|
||||
public bool RequiresCredentials(IOConnectionInfo ioc)
|
||||
{
|
||||
return ioc.CredSaveMode != IOCredSaveMode.SaveCred;
|
||||
}
|
||||
|
||||
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public string IocToPath(IOConnectionInfo ioc)
|
||||
{
|
||||
return ioc.Path;
|
||||
}
|
||||
|
||||
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
||||
{
|
||||
activity.PerformManualFileSelect(isForSave, requestCode, protocolId);
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
|
||||
bool alwaysReturnSuccess)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
activity.IocToIntent(intent, ioc);
|
||||
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
|
||||
}
|
||||
|
||||
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnResume(IFileStorageSetupActivity activity)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnStart(IFileStorageSetupActivity activity)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
||||
{
|
||||
}
|
||||
|
||||
public string GetDisplayName(IOConnectionInfo ioc)
|
||||
{
|
||||
return ioc.GetDisplayName();
|
||||
}
|
||||
|
||||
public string CreateFilePath(string parent, string newFilename)
|
||||
{
|
||||
if (!parent.EndsWith("/"))
|
||||
parent += "/";
|
||||
return parent + newFilename;
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||
{
|
||||
return IoUtil.GetParentPath(ioc);
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
||||
{
|
||||
IOConnectionInfo res = folderPath.CloneDeep();
|
||||
if (!res.Path.EndsWith("/"))
|
||||
res.Path += "/";
|
||||
res.Path += filename;
|
||||
return res;
|
||||
}
|
||||
|
||||
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -177,12 +177,6 @@ namespace keepass2android.Io
|
||||
/// </summary>
|
||||
bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null );
|
||||
|
||||
/// <summary>
|
||||
/// if the ioc.Path contains a string which refers to a stored account (like FTP account with specific settings),
|
||||
/// this method resolves the path so that it is a path which can be consumed by the file storage "operational" methods (=all other methods)
|
||||
/// </summary>
|
||||
/// <param name="ioc"></param>
|
||||
void ResolveAccount(IOConnectionInfo ioc);
|
||||
}
|
||||
|
||||
public interface IPermissionRequestingFileStorage
|
||||
|
@ -294,11 +294,6 @@ namespace keepass2android.Io
|
||||
return false; //TODO implement. note, however, that we MAY return false even if it's read-only
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||
{
|
||||
_jfs.OnCreate(((IJavaFileStorageFileStorageSetupActivity)activity), savedInstanceState);
|
||||
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.FtpClient;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
@ -14,14 +16,79 @@ namespace keepass2android.Io
|
||||
{
|
||||
public class NetFtpFileStorage: IFileStorage
|
||||
{
|
||||
struct ConnectionSettings
|
||||
class RetryConnectFtpClient : FtpClient
|
||||
{
|
||||
protected override FtpClient CloneConnection()
|
||||
{
|
||||
RetryConnectFtpClient conn = new RetryConnectFtpClient();
|
||||
|
||||
conn.m_isClone = true;
|
||||
|
||||
foreach (PropertyInfo prop in GetType().GetProperties())
|
||||
{
|
||||
object[] attributes = prop.GetCustomAttributes(typeof(FtpControlConnectionClone), true);
|
||||
|
||||
if (attributes != null && attributes.Length > 0)
|
||||
{
|
||||
prop.SetValue(conn, prop.GetValue(this, null), null);
|
||||
}
|
||||
}
|
||||
|
||||
// always accept certficate no matter what because if code execution ever
|
||||
// gets here it means the certificate on the control connection object being
|
||||
// cloned was already accepted.
|
||||
conn.ValidateCertificate += new FtpSslValidation(
|
||||
delegate(FtpClient obj, FtpSslValidationEventArgs e)
|
||||
{
|
||||
e.Accept = true;
|
||||
});
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
private static T DoInRetryLoop<T>(Func<T> func)
|
||||
{
|
||||
double timeout = 30.0;
|
||||
double timePerRequest = 1.0;
|
||||
var startTime = DateTime.Now;
|
||||
while (true)
|
||||
{
|
||||
var attemptStartTime = DateTime.Now;
|
||||
try
|
||||
{
|
||||
return func();
|
||||
}
|
||||
catch (System.Net.Sockets.SocketException e)
|
||||
{
|
||||
if ((e.ErrorCode != 10061) || (DateTime.Now > startTime.AddSeconds(timeout)))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
double secondsSinceAttemptStart = (DateTime.Now - attemptStartTime).TotalSeconds;
|
||||
if (secondsSinceAttemptStart < timePerRequest)
|
||||
{
|
||||
Thread.Sleep(TimeSpan.FromSeconds(timePerRequest - secondsSinceAttemptStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void Connect()
|
||||
{
|
||||
DoInRetryLoop(() =>
|
||||
{
|
||||
base.Connect();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ConnectionSettings
|
||||
{
|
||||
public FtpEncryptionMode EncryptionMode {get; set; }
|
||||
|
||||
public static ConnectionSettings FromIoc(IOConnectionInfo ioc)
|
||||
{
|
||||
if (ioc.Path.StartsWith(Kp2AAccountPathPrefix))
|
||||
throw new InvalidOperationException("cannot extract settings from account-path");
|
||||
string path = ioc.Path;
|
||||
int schemeLength = path.IndexOf("://", StringComparison.Ordinal);
|
||||
path = path.Substring(schemeLength + 3);
|
||||
@ -32,14 +99,23 @@ namespace keepass2android.Io
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public string ToString()
|
||||
{
|
||||
return ((int) EncryptionMode).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private const string Kp2AAccountPathPrefix = "__kp2a_account__";
|
||||
private readonly Context _context;
|
||||
|
||||
public MemoryStream traceStream;
|
||||
|
||||
public NetFtpFileStorage(Context context)
|
||||
{
|
||||
_context = context;
|
||||
traceStream = new MemoryStream();
|
||||
FtpTrace.AddListener(new System.Diagnostics.TextWriterTraceListener(traceStream));
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<string> SupportedProtocols
|
||||
@ -49,16 +125,42 @@ namespace keepass2android.Io
|
||||
|
||||
public void Delete(IOConnectionInfo ioc)
|
||||
{
|
||||
using (FtpClient client = GetClient(ioc))
|
||||
try
|
||||
{
|
||||
client.DeleteFile(IocPathToUri(ioc.Path).PathAndQuery);
|
||||
using (FtpClient client = GetClient(ioc))
|
||||
{
|
||||
string localPath = IocPathToUri(ioc.Path).PathAndQuery;
|
||||
if (client.DirectoryExists(localPath))
|
||||
client.DeleteDirectory(localPath, true);
|
||||
else
|
||||
client.DeleteFile(localPath);
|
||||
}
|
||||
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Exception ConvertException(Exception exception)
|
||||
{
|
||||
if (exception is FtpCommandException)
|
||||
{
|
||||
var ftpEx = (FtpCommandException) exception;
|
||||
|
||||
if (ftpEx.CompletionCode == "550")
|
||||
throw new FileNotFoundException(exception.Message, exception);
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
|
||||
internal FtpClient GetClient(IOConnectionInfo ioc, bool enableCloneClient = true)
|
||||
{
|
||||
FtpClient client = new FtpClient();
|
||||
FtpClient client = new RetryConnectFtpClient();
|
||||
if ((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
|
||||
client.Credentials = new NetworkCredential(ioc.UserName, ioc.Password);
|
||||
else
|
||||
@ -74,17 +176,22 @@ namespace keepass2android.Io
|
||||
|
||||
// we don't need to be thread safe in a classic sense, but OpenRead and OpenWrite don't
|
||||
//perform the actual stream operation so we'd need to wrap the stream (or just enable this:)
|
||||
client.EnableThreadSafeDataConnections = enableCloneClient;
|
||||
|
||||
|
||||
client.EncryptionMode = ConnectionSettings.FromIoc(ioc).EncryptionMode;
|
||||
|
||||
client.Connect();
|
||||
return client;
|
||||
|
||||
client.Connect();
|
||||
return client;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
internal Uri IocPathToUri(string path)
|
||||
{
|
||||
//remove addition stuff like TLS param
|
||||
//remove additional stuff like TLS param
|
||||
int schemeLength = path.IndexOf("://", StringComparison.Ordinal);
|
||||
string scheme = path.Substring(0, schemeLength);
|
||||
path = path.Substring(schemeLength + 3);
|
||||
@ -100,7 +207,9 @@ namespace keepass2android.Io
|
||||
string scheme = basePath.Substring(0, schemeLength);
|
||||
basePath = basePath.Substring(schemeLength + 3);
|
||||
string baseSettings = basePath.Substring(0, basePath.IndexOf("/", StringComparison.Ordinal));
|
||||
return scheme + "://" + baseSettings + "/" + uri.AbsolutePath; //TODO does this contain Query?
|
||||
basePath = basePath.Substring(baseSettings.Length+1);
|
||||
string baseHost = basePath.Substring(0, basePath.IndexOf("/", StringComparison.Ordinal));
|
||||
return scheme + "://" + baseSettings + "/" + baseHost + uri.AbsolutePath; //TODO does this contain Query?
|
||||
}
|
||||
|
||||
|
||||
@ -116,18 +225,34 @@ namespace keepass2android.Io
|
||||
|
||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||
{
|
||||
using (var cl = GetClient(ioc))
|
||||
try
|
||||
{
|
||||
return cl.OpenRead(IocPathToUri(ioc.Path).PathAndQuery, FtpDataType.Binary, 0);
|
||||
using (var cl = GetClient(ioc))
|
||||
{
|
||||
return cl.OpenRead(IocPathToUri(ioc.Path).PathAndQuery, FtpDataType.Binary, 0);
|
||||
}
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
if (useFileTransaction)
|
||||
return new UntransactedWrite(ioc, this);
|
||||
else
|
||||
return new TransactedWrite(ioc, this);
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
if (!useFileTransaction)
|
||||
return new UntransactedWrite(ioc, this);
|
||||
else
|
||||
return new TransactedWrite(ioc, this);
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
@ -144,73 +269,95 @@ namespace keepass2android.Io
|
||||
|
||||
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||
{
|
||||
using (var client = GetClient(ioc))
|
||||
try
|
||||
{
|
||||
client.CreateDirectory(IocPathToUri(ioc.Path).PathAndQuery);
|
||||
using (var client = GetClient(ioc))
|
||||
{
|
||||
client.CreateDirectory(IocPathToUri(GetFilePath(ioc, newDirName).Path).PathAndQuery);
|
||||
}
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||
{
|
||||
using (var client = GetClient(ioc))
|
||||
try
|
||||
{
|
||||
List<FileDescription> files = new List<FileDescription>();
|
||||
foreach (FtpListItem item in client.GetListing(IocPathToUri(ioc.Path).PathAndQuery,
|
||||
FtpListOption.Modify | FtpListOption.Size | FtpListOption.DerefLinks))
|
||||
using (var client = GetClient(ioc))
|
||||
{
|
||||
|
||||
switch (item.Type)
|
||||
List<FileDescription> files = new List<FileDescription>();
|
||||
foreach (FtpListItem item in client.GetListing(IocPathToUri(ioc.Path).PathAndQuery,
|
||||
FtpListOption.Modify | FtpListOption.Size | FtpListOption.DerefLinks))
|
||||
{
|
||||
case FtpFileSystemObjectType.Directory:
|
||||
files.Add(new FileDescription()
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
DisplayName = item.Name,
|
||||
IsDirectory = true,
|
||||
LastModified = item.Modified,
|
||||
Path = IocPathFromUri(ioc, new Uri(item.FullName))
|
||||
});
|
||||
break;
|
||||
case FtpFileSystemObjectType.File:
|
||||
files.Add(new FileDescription()
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
DisplayName = item.Name,
|
||||
IsDirectory = false,
|
||||
LastModified = item.Modified,
|
||||
Path = IocPathFromUri(ioc, new Uri(item.FullName)),
|
||||
SizeInBytes = item.Size
|
||||
});
|
||||
break;
|
||||
|
||||
|
||||
switch (item.Type)
|
||||
{
|
||||
case FtpFileSystemObjectType.Directory:
|
||||
files.Add(new FileDescription()
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
DisplayName = item.Name,
|
||||
IsDirectory = true,
|
||||
LastModified = item.Modified,
|
||||
Path = IocPathFromUri(ioc, new Uri(item.FullName))
|
||||
});
|
||||
break;
|
||||
case FtpFileSystemObjectType.File:
|
||||
files.Add(new FileDescription()
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
DisplayName = item.Name,
|
||||
IsDirectory = false,
|
||||
LastModified = item.Modified,
|
||||
Path = IocPathFromUri(ioc, new Uri(item.FullName)),
|
||||
SizeInBytes = item.Size
|
||||
});
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
return files;
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||
{
|
||||
//TODO when is this called?
|
||||
//is it very inefficient to connect for each description?
|
||||
|
||||
using (FtpClient client = GetClient(ioc))
|
||||
try
|
||||
{
|
||||
var uri = IocPathToUri(ioc.Path);
|
||||
string path = uri.PathAndQuery;
|
||||
return new FileDescription()
|
||||
//TODO when is this called?
|
||||
//is it very inefficient to connect for each description?
|
||||
|
||||
using (FtpClient client = GetClient(ioc))
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
Path = ioc.Path,
|
||||
LastModified = client.GetModifiedTime(path),
|
||||
SizeInBytes = client.GetFileSize(path),
|
||||
DisplayName = UrlUtil.GetFileName(path),
|
||||
IsDirectory = false
|
||||
};
|
||||
|
||||
var uri = IocPathToUri(ioc.Path);
|
||||
string path = uri.PathAndQuery;
|
||||
return new FileDescription()
|
||||
{
|
||||
CanRead = true,
|
||||
CanWrite = true,
|
||||
Path = ioc.Path,
|
||||
LastModified = client.GetModifiedTime(path),
|
||||
SizeInBytes = client.GetFileSize(path),
|
||||
DisplayName = UrlUtil.GetFileName(path),
|
||||
IsDirectory = false
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,37 +445,19 @@ namespace keepass2android.Io
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
string path = ioc.Path;
|
||||
int schemeLength = path.IndexOf("://", StringComparison.Ordinal);
|
||||
string scheme = path.Substring(0, schemeLength);
|
||||
path = path.Substring(schemeLength+3);
|
||||
if (path.StartsWith(Kp2AAccountPathPrefix))
|
||||
{
|
||||
string accountId = path.Substring(0, path.IndexOf("/", StringComparison.Ordinal));
|
||||
path = path.Substring(accountId.Length + 1);
|
||||
|
||||
var prefs = PreferenceManager.GetDefaultSharedPreferences(_context);
|
||||
string host = prefs.GetString(accountId + "_Host", null);
|
||||
int port = prefs.GetInt(accountId + "_Port", 0 /* auto*/);
|
||||
string initialPath = prefs.GetString(accountId + "_InitPath", "");
|
||||
if (initialPath.StartsWith("/"))
|
||||
initialPath = initialPath.Substring(1);
|
||||
if ((!initialPath.EndsWith("/") && (initialPath != "")))
|
||||
initialPath += "/";
|
||||
int encMode = prefs.GetInt(accountId + "_EncMode", (int) FtpEncryptionMode.None);
|
||||
string settings = encMode.ToString();
|
||||
ioc.Path = scheme + "://" + settings + "/" + host + (port == 0 ? "" : (":" + port)) + "/" + initialPath + path;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream OpenWrite(IOConnectionInfo ioc)
|
||||
{
|
||||
using (var client = GetClient(ioc))
|
||||
try
|
||||
{
|
||||
return client.OpenWrite(IocPathToUri(ioc.Path).PathAndQuery);
|
||||
using (var client = GetClient(ioc))
|
||||
{
|
||||
return client.OpenWrite(IocPathToUri(ioc.Path).PathAndQuery);
|
||||
|
||||
}
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw ConvertException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,7 +468,7 @@ namespace keepass2android.Io
|
||||
private readonly NetFtpFileStorage _fileStorage;
|
||||
private readonly IOConnectionInfo _iocTemp;
|
||||
private FtpClient _client;
|
||||
|
||||
private Stream _stream;
|
||||
|
||||
public TransactedWrite(IOConnectionInfo ioc, NetFtpFileStorage fileStorage)
|
||||
{
|
||||
@ -352,21 +481,54 @@ namespace keepass2android.Io
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_client != null)
|
||||
_client.Dispose();
|
||||
_client = null;
|
||||
if (_stream != null)
|
||||
_stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
public Stream OpenFile()
|
||||
{
|
||||
_client = _fileStorage.GetClient(_ioc, false);
|
||||
return _client.OpenWrite(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery);
|
||||
try
|
||||
{
|
||||
|
||||
_client = _fileStorage.GetClient(_ioc, false);
|
||||
_stream = _client.OpenWrite(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery);
|
||||
return _stream;
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw NetFtpFileStorage.ConvertException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void CommitWrite()
|
||||
{
|
||||
_client.DeleteFile(_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery);
|
||||
_client.Rename(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery, _fileStorage.IocPathToUri(_ioc.Path).PathAndQuery);
|
||||
try
|
||||
{
|
||||
Android.Util.Log.Debug("NETFTP","connected: " + _client.IsConnected.ToString());
|
||||
_stream.Close();
|
||||
Android.Util.Log.Debug("NETFTP", "connected: " + _client.IsConnected.ToString());
|
||||
|
||||
//make sure target file does not exist:
|
||||
//try
|
||||
{
|
||||
if (_client.FileExists(_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery))
|
||||
_client.DeleteFile(_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery);
|
||||
|
||||
}
|
||||
//catch (FtpCommandException)
|
||||
{
|
||||
//TODO get a new clien? might be stale
|
||||
}
|
||||
|
||||
_client.Rename(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery,
|
||||
_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery);
|
||||
|
||||
}
|
||||
catch (FtpCommandException ex)
|
||||
{
|
||||
throw NetFtpFileStorage.ConvertException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,6 +536,7 @@ namespace keepass2android.Io
|
||||
{
|
||||
private readonly IOConnectionInfo _ioc;
|
||||
private readonly NetFtpFileStorage _fileStorage;
|
||||
private Stream _stream;
|
||||
|
||||
public UntransactedWrite(IOConnectionInfo ioc, NetFtpFileStorage fileStorage)
|
||||
{
|
||||
@ -383,17 +546,20 @@ namespace keepass2android.Io
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
if (_stream != null)
|
||||
_stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
public Stream OpenFile()
|
||||
{
|
||||
return _fileStorage.OpenWrite(_ioc);
|
||||
_stream = _fileStorage.OpenWrite(_ioc);
|
||||
return _stream;
|
||||
}
|
||||
|
||||
public void CommitWrite()
|
||||
{
|
||||
|
||||
_stream.Close();
|
||||
}
|
||||
}
|
||||
}
|
@ -181,11 +181,6 @@ namespace keepass2android.Io
|
||||
return _baseStorage.IsReadOnly(ioc, reason);
|
||||
}
|
||||
|
||||
public void ResolveAccount(IOConnectionInfo ioc)
|
||||
{
|
||||
_baseStorage.ResolveAccount(ioc);
|
||||
}
|
||||
|
||||
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
|
||||
string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
|
@ -90,10 +90,10 @@
|
||||
<Compile Include="Io\FileStorageSetupActivity.cs" />
|
||||
<Compile Include="Io\FileStorageSetupInitiatorActivity.cs" />
|
||||
<Compile Include="Io\GDriveFileStorage.cs" />
|
||||
<Compile Include="Io\HttpFileStorage.cs" />
|
||||
<Compile Include="Io\IFileStorage.cs" />
|
||||
<Compile Include="Io\IoUtil.cs" />
|
||||
<Compile Include="Io\JavaFileStorage.cs" />
|
||||
<Compile Include="Io\NetFtpFileStorage.cs" />
|
||||
<Compile Include="Io\OfflineSwitchableFileStorage.cs" />
|
||||
<Compile Include="Io\SftpFileStorage.cs" />
|
||||
<Compile Include="Io\SkyDriveFileStorage.cs" />
|
||||
|
@ -458,7 +458,7 @@ namespace keepass2android
|
||||
{
|
||||
IFileStorage fileStorage;
|
||||
if (iocInfo.IsLocalFile())
|
||||
fileStorage = new BuiltInFileStorage(this);
|
||||
fileStorage = new LocalFileStorage(this);
|
||||
else
|
||||
{
|
||||
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);
|
||||
@ -511,9 +511,10 @@ namespace keepass2android
|
||||
new GoogleDriveFileStorage(Application.Context, this),
|
||||
new SkyDriveFileStorage(Application.Context, this),
|
||||
new SftpFileStorage(this),
|
||||
new NetFtpFileStorage(Application.Context),
|
||||
#endif
|
||||
#endif
|
||||
new BuiltInFileStorage(this)
|
||||
new LocalFileStorage(this)
|
||||
};
|
||||
}
|
||||
return _fileStorages;
|
||||
|
@ -286,8 +286,6 @@ namespace keepass2android
|
||||
|
||||
ioc.Obfuscate(false);
|
||||
|
||||
//TODO enable for 1.1 release App.Kp2a.GetFileStorage(ioc).ResolveAccount(ioc);
|
||||
|
||||
return ioc;
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +219,6 @@ namespace keepass2android
|
||||
TextView textView = (TextView)view;
|
||||
IOConnectionInfo ioc = new IOConnectionInfo {Path = path};
|
||||
var fileStorage = _app.GetFileStorage(ioc);
|
||||
//TODO enable for 1.1 release fileStorage.ResolveAccount(ioc);
|
||||
textView.Text = fileStorage.GetDisplayName(ioc);
|
||||
textView.Tag = ioc.Path;
|
||||
return true;
|
||||
|
@ -2,6 +2,7 @@
|
||||
<packages>
|
||||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="MonoAndroid60" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="MonoAndroid60" />
|
||||
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="MonoAndroid60" />
|
||||
<package id="Xamarin.Android.Support.Design" version="22.2.1.0" targetFramework="MonoAndroid50" />
|
||||
<package id="Xamarin.Android.Support.v4" version="22.2.1.0" targetFramework="MonoAndroid50" />
|
||||
<package id="Xamarin.Android.Support.v7.AppCompat" version="22.2.1.0" targetFramework="MonoAndroid50" />
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 961c87189241f38332a2dc0564eacf8bddfd37a6
|
||||
Subproject commit 3a5b618ccf68d30678d6ad4d6f16cbb6685fa21d
|
Loading…
Reference in New Issue
Block a user