implement support for MEGA, closes #99
This commit is contained in:
@@ -246,7 +246,7 @@ namespace keepass2android.Io
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId+"://"});
|
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId+"://", });
|
||||||
activity.OnImmediateResult(requestCode, (int) FileStorageResults.FileChooserPrepared, intent);
|
activity.OnImmediateResult(requestCode, (int) FileStorageResults.FileChooserPrepared, intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
506
src/Kp2aBusinessLogic/Io/MegaFileStorage.cs
Normal file
506
src/Kp2aBusinessLogic/Io/MegaFileStorage.cs
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Preferences;
|
||||||
|
using Android.Util;
|
||||||
|
using CG.Web.MegaApiClient;
|
||||||
|
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
|
||||||
|
using KeePassLib.Cryptography.Cipher;
|
||||||
|
using KeePassLib.Serialization;
|
||||||
|
using KeePassLib.Utility;
|
||||||
|
|
||||||
|
namespace keepass2android.Io
|
||||||
|
{
|
||||||
|
public class MegaFileStorage : IFileStorage
|
||||||
|
{
|
||||||
|
private readonly Context _appContext;
|
||||||
|
public const string ProtocolId = "mega";
|
||||||
|
private const string PreferenceKey = "KP2A-Mega-Accounts";
|
||||||
|
|
||||||
|
public MegaFileStorage(Context appContext)
|
||||||
|
{
|
||||||
|
_appContext = appContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
//we don't want to store passwords in plain text, encrypt them with this key at least:
|
||||||
|
public static readonly byte[] EncryptionKey = new byte[] { 86,239,128,218,160,22,245,114,193,92,151,10,134,104,121,170,
|
||||||
|
183,110,60,38,179,181,24,206,169,43,125,193,142,156,47,45};
|
||||||
|
|
||||||
|
public class AccountSettings
|
||||||
|
{
|
||||||
|
public Dictionary<string, string> PasswordByUsername { get; set; } = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
public static byte[] exclusiveOR(byte[] arr1, byte[] arr2)
|
||||||
|
{
|
||||||
|
byte[] result = new byte[arr1.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < arr1.Length; ++i)
|
||||||
|
result[i] = (byte)(arr1[i] ^ arr2[i % arr2.Length]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string Encrypt(string s)
|
||||||
|
{
|
||||||
|
var plainTextBytes = exclusiveOR(System.Text.Encoding.UTF8.GetBytes(s), EncryptionKey);
|
||||||
|
return System.Convert.ToBase64String(plainTextBytes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static string Decrypt(string s)
|
||||||
|
{
|
||||||
|
var base64EncodedBytes = System.Convert.FromBase64String(s);
|
||||||
|
return System.Text.Encoding.UTF8.GetString(exclusiveOR(base64EncodedBytes, EncryptionKey));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Serialize()
|
||||||
|
{
|
||||||
|
Dictionary<string, string> encryptedPasswordByUsername = PasswordByUsername
|
||||||
|
.Select(kvp => new KeyValuePair<string, string>(kvp.Key, Encrypt(kvp.Value)))
|
||||||
|
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
|
return Newtonsoft.Json.JsonConvert.SerializeObject(encryptedPasswordByUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Deserialize(string data)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(data))
|
||||||
|
{
|
||||||
|
PasswordByUsername = new Dictionary<string, string>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Dictionary<string, string> encryptedPasswordByUsername =
|
||||||
|
Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
|
||||||
|
PasswordByUsername = encryptedPasswordByUsername
|
||||||
|
.Select(kvp => new KeyValuePair<string, string>(kvp.Key, Decrypt(kvp.Value)))
|
||||||
|
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> SupportedProtocols
|
||||||
|
{
|
||||||
|
get { yield return ProtocolId; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UserShouldBackup
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MegaFileStorageWriteTransaction : IWriteTransaction
|
||||||
|
{
|
||||||
|
public bool UseFileTransaction { get; }
|
||||||
|
private readonly string _path;
|
||||||
|
private readonly MegaFileStorage _filestorage;
|
||||||
|
private MemoryStream _memoryStream;
|
||||||
|
|
||||||
|
public MegaFileStorageWriteTransaction(string path, MegaFileStorage filestorage, bool useFileTransaction)
|
||||||
|
{
|
||||||
|
UseFileTransaction = useFileTransaction;
|
||||||
|
_path = path;
|
||||||
|
_filestorage = filestorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_memoryStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OpenFile()
|
||||||
|
{
|
||||||
|
_memoryStream = new MemoryStream();
|
||||||
|
return _memoryStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CommitWrite()
|
||||||
|
{
|
||||||
|
_filestorage.UploadFile(_path, new MemoryStream(_memoryStream.ToArray()), UseFileTransaction);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UploadFile(string path, MemoryStream memoryStream, bool useTransaction)
|
||||||
|
{
|
||||||
|
var accountData = GetAccountData(path);
|
||||||
|
|
||||||
|
if (accountData.TryGetNode(path, out var node))
|
||||||
|
{
|
||||||
|
if (useTransaction)
|
||||||
|
{
|
||||||
|
string temporaryName = node.Name + "." + new Guid().ToString() + ".tmp";
|
||||||
|
var newNode = accountData.Client.Upload(memoryStream, temporaryName, accountData.GetParentNode(node));
|
||||||
|
accountData.Client.Delete(node);
|
||||||
|
newNode = accountData.Client.Rename(newNode, node.Name);
|
||||||
|
accountData._nodes.Remove(node);
|
||||||
|
accountData._nodes.Add(newNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newNode = accountData.Client.Upload(memoryStream, node.Name, accountData.GetParentNode(node));
|
||||||
|
//we now have two nodes with the same name. Delete the old one:
|
||||||
|
accountData.Client.Delete(node);
|
||||||
|
accountData._nodes.Remove(node);
|
||||||
|
accountData._nodes.Add(newNode);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//file did not exist yet
|
||||||
|
string parentPath = GetParentPath(new IOConnectionInfo() { Path = path }).Path;
|
||||||
|
string name = path.Substring(parentPath.Length + 1);
|
||||||
|
var newNode = accountData.Client.Upload(memoryStream, name, accountData.GetNode(parentPath));
|
||||||
|
accountData._nodes.Add(newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode,
|
||||||
|
string protocolId)
|
||||||
|
{
|
||||||
|
activity.PerformManualFileSelect(isForSave, requestCode, protocolId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||||
|
{
|
||||||
|
return new MegaFileStorageWriteTransaction(ioc.Path, this, useFileTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return UrlUtil.StripExtension(
|
||||||
|
UrlUtil.GetFileName(ioc.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFileExtension(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return UrlUtil.GetExtension(ioc.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CreateFilePath(string parent, string newFilename)
|
||||||
|
{
|
||||||
|
if (!parent.EndsWith("/"))
|
||||||
|
parent += "/";
|
||||||
|
return parent + newFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
||||||
|
{
|
||||||
|
IOConnectionInfo res = folderPath.CloneDeep();
|
||||||
|
if (!res.Path.EndsWith("/"))
|
||||||
|
res.Path += "/";
|
||||||
|
res.Path += filename;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return IoUtil.GetParentPath(ioc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDisplayName(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return ioc.GetDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
//nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
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 string IocToPath(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return ioc.Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
var accountData = GetAccountData(ioc);
|
||||||
|
return MakeFileDescription(accountData, accountData.GetNode(ioc));
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountData
|
||||||
|
{
|
||||||
|
public string Account { get; set; }
|
||||||
|
public IMegaApiClient Client { get; set; }
|
||||||
|
|
||||||
|
public void RefreshMetadata()
|
||||||
|
{
|
||||||
|
//make sure we refresh meta data after one minute:
|
||||||
|
if (DateTime.Now.Subtract(_nodesLoadingTime).TotalMinutes > 1.0)
|
||||||
|
{
|
||||||
|
_nodes.Clear();
|
||||||
|
EnsureMetadataLoaded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<INode> _nodes = new List<INode>();
|
||||||
|
private DateTime _nodesLoadingTime;
|
||||||
|
private INode _rootNode;
|
||||||
|
|
||||||
|
public INode GetNode(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return GetNode(ioc.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetNode(string path, out INode node)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
node = GetNode(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
node = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public INode GetNode(string path)
|
||||||
|
{
|
||||||
|
EnsureMetadataLoaded();
|
||||||
|
if (!path.StartsWith(ProtocolId + "://"))
|
||||||
|
throw new Exception("Invalid Mega URL: " + path);
|
||||||
|
path = path.Substring(ProtocolId.Length + 3);
|
||||||
|
var parts = path.Split('/');
|
||||||
|
if (parts.Length < 1 || parts[0] == "")
|
||||||
|
throw new Exception("Invalid Mega URL: " + path);
|
||||||
|
|
||||||
|
INode node = _rootNode;
|
||||||
|
for (int i = 1; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
if (parts[i] == "")
|
||||||
|
continue;
|
||||||
|
var matchingChildren = _nodes.Where(n => n.ParentId == node.Id && n.Name == parts[i]).ToList();
|
||||||
|
if (matchingChildren.Count == 0)
|
||||||
|
throw new FileNotFoundException("Did not find " + path);
|
||||||
|
if (matchingChildren.Count > 1)
|
||||||
|
throw new Java.IO.FileNotFoundException(
|
||||||
|
$"Found more than one child with name {parts[i]} while trying to get node for {path}");
|
||||||
|
node = matchingChildren.Single();
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureMetadataLoaded()
|
||||||
|
{
|
||||||
|
if (_nodes.Any() == false)
|
||||||
|
{
|
||||||
|
_nodes = Client.GetNodes().ToList();
|
||||||
|
|
||||||
|
_rootNode = _nodes.Single(n => n.Type == NodeType.Root);
|
||||||
|
_nodesLoadingTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public INode GetParentNode(INode node)
|
||||||
|
{
|
||||||
|
return _nodes.Single(n => n.Id == node.ParentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvalidateMetaData()
|
||||||
|
{
|
||||||
|
_nodes.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<INode> GetChildNodes(INode node)
|
||||||
|
{
|
||||||
|
EnsureMetadataLoaded();
|
||||||
|
return _nodes.Where(n => n.ParentId == node.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPath(INode node)
|
||||||
|
{
|
||||||
|
if (node.Type == NodeType.Root)
|
||||||
|
return ProtocolId + "://" + this.Account;
|
||||||
|
var parent = _nodes.Single(n => n.Id == node.ParentId);
|
||||||
|
return GetPath(parent) + "/" + node.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
readonly Dictionary<string /*account*/, AccountData> _allAccountData = new Dictionary<string, AccountData>();
|
||||||
|
|
||||||
|
public string GetAccount(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return GetAccount(ioc.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAccount(string path)
|
||||||
|
{
|
||||||
|
if (!path.StartsWith(ProtocolId + "://"))
|
||||||
|
throw new Exception("Invalid Mega URL: " + path);
|
||||||
|
path = path.Substring(ProtocolId.Length + 3);
|
||||||
|
var parts = path.Split('/');
|
||||||
|
if (parts.Length < 1 || parts[0] == "")
|
||||||
|
throw new Exception("Invalid Mega URL: " + path);
|
||||||
|
return parts[0];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountData GetAccountData(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return GetAccountData(ioc.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccountSettings GetAccountSettings(Context ctx)
|
||||||
|
{
|
||||||
|
string accountSettingsString = PreferenceManager.GetDefaultSharedPreferences(ctx).GetString(PreferenceKey, null);
|
||||||
|
AccountSettings settings = new AccountSettings();
|
||||||
|
settings.Deserialize(accountSettingsString);
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateAccountSettings(AccountSettings settings, Context ctx)
|
||||||
|
{
|
||||||
|
PreferenceManager.GetDefaultSharedPreferences(ctx).Edit().PutString(PreferenceKey, settings.Serialize())
|
||||||
|
.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountData GetAccountData(string path)
|
||||||
|
{
|
||||||
|
string account = GetAccount(path);
|
||||||
|
if (_allAccountData.TryGetValue(account, out var accountData))
|
||||||
|
{
|
||||||
|
return accountData;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountData newAccountData = new AccountData()
|
||||||
|
{
|
||||||
|
Account = account,
|
||||||
|
Client = new MegaApiClient()
|
||||||
|
};
|
||||||
|
|
||||||
|
var settings = GetAccountSettings(_appContext);
|
||||||
|
if (!settings.PasswordByUsername.TryGetValue(account, out string password))
|
||||||
|
{
|
||||||
|
throw new Exception("No account configured with username = " + account);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newAccountData.Client.Login(account, password);
|
||||||
|
}
|
||||||
|
catch (CG.Web.MegaApiClient.ApiException e)
|
||||||
|
{
|
||||||
|
if (e.ApiResultCode == CG.Web.MegaApiClient.ApiResultCode.ResourceNotExists)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to login to MEGA account. Please check username and password!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_allAccountData[account] = newAccountData;
|
||||||
|
return newAccountData;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
AccountData accountData = GetAccountData(ioc);
|
||||||
|
accountData.RefreshMetadata();
|
||||||
|
return accountData.GetChildNodes(accountData.GetNode(ioc)).Select(n => MakeFileDescription(accountData, n));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileDescription MakeFileDescription(AccountData account, INode n)
|
||||||
|
{
|
||||||
|
return new FileDescription()
|
||||||
|
{
|
||||||
|
CanRead = true,
|
||||||
|
CanWrite = true,
|
||||||
|
DisplayName = n.Name ?? (n.Type == NodeType.Root ? "root" : ""),
|
||||||
|
IsDirectory = n.Type != NodeType.File,
|
||||||
|
LastModified = n.ModificationDate ?? n.CreationDate ?? DateTime.MinValue,
|
||||||
|
Path = account.GetPath(n),
|
||||||
|
SizeInBytes = n.Size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||||
|
{
|
||||||
|
var accountData = GetAccountData(ioc);
|
||||||
|
var newNode = accountData.Client.CreateFolder(newDirName, accountData.GetNode(ioc));
|
||||||
|
accountData._nodes.Add(newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequiresCredentials(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
var accountData = GetAccountData(ioc);
|
||||||
|
return accountData.Client.Download(accountData.GetNode(ioc));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
var accountData = GetAccountData(ioc);
|
||||||
|
accountData.Client.Delete(accountData.GetNode(ioc));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
<Compile Include="Io\IFileStorage.cs" />
|
<Compile Include="Io\IFileStorage.cs" />
|
||||||
<Compile Include="Io\IoUtil.cs" />
|
<Compile Include="Io\IoUtil.cs" />
|
||||||
<Compile Include="Io\JavaFileStorage.cs" />
|
<Compile Include="Io\JavaFileStorage.cs" />
|
||||||
|
<Compile Include="Io\MegaFileStorage.cs" />
|
||||||
<Compile Include="Io\NetFtpFileStorage.cs" />
|
<Compile Include="Io\NetFtpFileStorage.cs" />
|
||||||
<Compile Include="Io\OfflineSwitchableFileStorage.cs" />
|
<Compile Include="Io\OfflineSwitchableFileStorage.cs" />
|
||||||
<Compile Include="Io\OneDrive2FileStorage.cs" />
|
<Compile Include="Io\OneDrive2FileStorage.cs" />
|
||||||
@@ -165,6 +166,9 @@
|
|||||||
<PackageReference Include="FluentFTP">
|
<PackageReference Include="FluentFTP">
|
||||||
<Version>31.3.1</Version>
|
<Version>31.3.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="MegaApiClient">
|
||||||
|
<Version>1.10.2</Version>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Graph">
|
<PackageReference Include="Microsoft.Graph">
|
||||||
<Version>1.21.0</Version>
|
<Version>1.21.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -276,6 +280,9 @@
|
|||||||
<PackageReference Include="Xamarin.Android.Support.ViewPager">
|
<PackageReference Include="Xamarin.Android.Support.ViewPager">
|
||||||
<Version>28.0.0.3</Version>
|
<Version>28.0.0.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Xamarin.AndroidX.Preference">
|
||||||
|
<Version>1.1.1.11</Version>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
|||||||
@@ -229,6 +229,67 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void ShowMegaDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
|
||||||
|
{
|
||||||
|
#if !NoNet
|
||||||
|
var settings = MegaFileStorage.GetAccountSettings(activity);
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.megacredentials, null);
|
||||||
|
if (!defaultPath.EndsWith(_schemeSeparator))
|
||||||
|
{
|
||||||
|
string user = "";
|
||||||
|
string password = "";
|
||||||
|
if (!defaultPath.EndsWith(_schemeSeparator))
|
||||||
|
{
|
||||||
|
user = MegaFileStorage.GetAccount(defaultPath);
|
||||||
|
if (!settings.PasswordByUsername.TryGetValue(user, out password))
|
||||||
|
password = "";
|
||||||
|
dlgContents.FindViewById<EditText>(Resource.Id.mega_user).Enabled = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
dlgContents.FindViewById<EditText>(Resource.Id.mega_user).Text = user;
|
||||||
|
dlgContents.FindViewById<EditText>(Resource.Id.mega_password).Text = password;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var userView = ((AutoCompleteTextView)dlgContents.FindViewById(Resource.Id.mega_user));
|
||||||
|
userView.Adapter = new ArrayAdapter(activity, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, settings.PasswordByUsername.Keys.ToArray());
|
||||||
|
|
||||||
|
userView.TextChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
if (userView.Text != null && settings.PasswordByUsername.TryGetValue(userView.Text, out string pwd))
|
||||||
|
{
|
||||||
|
dlgContents.FindViewById<EditText>(Resource.Id.mega_password).Text = pwd;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builder.SetCancelable(false);
|
||||||
|
builder.SetView(dlgContents);
|
||||||
|
builder.SetPositiveButton(Android.Resource.String.Ok,
|
||||||
|
(sender, args) =>
|
||||||
|
{
|
||||||
|
string user = dlgContents.FindViewById<EditText>(Resource.Id.mega_user).Text;
|
||||||
|
string password = dlgContents.FindViewById<EditText>(Resource.Id.mega_password).Text;
|
||||||
|
//store the credentials in the mega credentials store:
|
||||||
|
|
||||||
|
|
||||||
|
settings.PasswordByUsername[user] = password;
|
||||||
|
|
||||||
|
MegaFileStorage.UpdateAccountSettings(settings, activity);
|
||||||
|
|
||||||
|
onStartBrowse(MegaFileStorage.ProtocolId + "://" + user);
|
||||||
|
});
|
||||||
|
EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
|
||||||
|
|
||||||
|
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
|
||||||
|
builder.SetTitle(activity.GetString(Resource.String.enter_mega_login_title));
|
||||||
|
Dialog dialog = builder.Create();
|
||||||
|
|
||||||
|
dialog.Show();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void PerformManualFileSelect(string defaultPath)
|
public void PerformManualFileSelect(string defaultPath)
|
||||||
{
|
{
|
||||||
if (defaultPath.StartsWith("sftp://"))
|
if (defaultPath.StartsWith("sftp://"))
|
||||||
@@ -241,7 +302,9 @@ namespace keepass2android
|
|||||||
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud");
|
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud");
|
||||||
else if (defaultPath.StartsWith("nextcloud://"))
|
else if (defaultPath.StartsWith("nextcloud://"))
|
||||||
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud");
|
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud");
|
||||||
else
|
else if (defaultPath.StartsWith("mega://"))
|
||||||
|
ShowMegaDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Func<string, Dialog, bool> onOpen = OnOpenButton;
|
Func<string, Dialog, bool> onOpen = OnOpenButton;
|
||||||
Util.ShowFilenameDialog(_activity,
|
Util.ShowFilenameDialog(_activity,
|
||||||
@@ -472,7 +535,8 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
return ioc.Path.StartsWith("http")
|
return ioc.Path.StartsWith("http")
|
||||||
|| ioc.Path.StartsWith("ftp")
|
|| ioc.Path.StartsWith("ftp")
|
||||||
|| ioc.Path.StartsWith("sftp");
|
|| ioc.Path.StartsWith("sftp")
|
||||||
|
|| ioc.Path.StartsWith("mega");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src/keepass2android/Resources/drawable-mdpi/ic_storage_mega.png
Normal file
BIN
src/keepass2android/Resources/drawable-mdpi/ic_storage_mega.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/keepass2android/Resources/drawable-xhdpi/ic_storage_mega.png
Normal file
BIN
src/keepass2android/Resources/drawable-xhdpi/ic_storage_mega.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
30
src/keepass2android/Resources/layout/megacredentials.xml
Normal file
30
src/keepass2android/Resources/layout/megacredentials.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dip"
|
||||||
|
>
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/mega_user"
|
||||||
|
android:hint="@string/hint_username"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="textWebEmailAddress"
|
||||||
|
|
||||||
|
android:dropDownWidth="match_parent"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/mega_password"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:text=""
|
||||||
|
android:singleLine="true"
|
||||||
|
android:hint="@string/hint_pass"
|
||||||
|
android:importantForAccessibility="no"/>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -591,6 +591,7 @@
|
|||||||
|
|
||||||
<string name="enter_ftp_login_title">Enter FTP login data:</string>
|
<string name="enter_ftp_login_title">Enter FTP login data:</string>
|
||||||
|
|
||||||
|
<string name="enter_mega_login_title">Enter your MEGA account login data:</string>
|
||||||
|
|
||||||
<string name="select_storage_type">Select the storage type:</string>
|
<string name="select_storage_type">Select the storage type:</string>
|
||||||
|
|
||||||
@@ -615,7 +616,9 @@
|
|||||||
<string name="filestoragename_onedrive2_myfiles">My files</string>
|
<string name="filestoragename_onedrive2_myfiles">My files</string>
|
||||||
<string name="filestoragename_onedrive2_appfolder">Keepass2Android App folder</string>
|
<string name="filestoragename_onedrive2_appfolder">Keepass2Android App folder</string>
|
||||||
<string name="filestoragename_sftp">SFTP (SSH File Transfer)</string>
|
<string name="filestoragename_sftp">SFTP (SSH File Transfer)</string>
|
||||||
<string name="filestoragename_content">System file picker</string>
|
<string name="filestoragename_mega">MEGA</string>
|
||||||
|
<string name="filestoragehelp_mega">Please note: Keepass2Android must download the list of all files in your Mega account to work properly. For this reason, accessing accounts with many files might be slow.</string>
|
||||||
|
<string name="filestoragename_content">System file picker</string>
|
||||||
|
|
||||||
<string name="filestorage_setup_title">File access initialization</string>
|
<string name="filestorage_setup_title">File access initialization</string>
|
||||||
|
|
||||||
|
|||||||
@@ -733,6 +733,7 @@ namespace keepass2android
|
|||||||
new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this),
|
new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||||
new WebDavFileStorage(this),
|
new WebDavFileStorage(this),
|
||||||
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||||
|
new MegaFileStorage(App.Context),
|
||||||
//new LegacyWebDavStorage(this),
|
//new LegacyWebDavStorage(this),
|
||||||
//new LegacyFtpStorage(this),
|
//new LegacyFtpStorage(this),
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -467,6 +467,7 @@
|
|||||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_androidsend.png" />
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_androidsend.png" />
|
||||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_content.png" />
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_content.png" />
|
||||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_dropbox.png" />
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_dropbox.png" />
|
||||||
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_mega.png" />
|
||||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_pcloud.png" />
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_pcloud.png" />
|
||||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_dropboxKP2A.png" />
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_dropboxKP2A.png" />
|
||||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_file.png" />
|
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_file.png" />
|
||||||
@@ -1078,6 +1079,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_dropbox.png" />
|
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_dropbox.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_mega.png" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_dropboxKP2A.png" />
|
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_dropboxKP2A.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -1936,6 +1940,12 @@
|
|||||||
<Name>ZlibAndroid</Name>
|
<Name>ZlibAndroid</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AndroidResource Include="Resources\layout\megacredentials.xml">
|
||||||
|
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</AndroidResource>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
|||||||
Reference in New Issue
Block a user