implement support for MEGA, closes #99
This commit is contained in:
@@ -246,7 +246,7 @@ namespace keepass2android.Io
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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\IoUtil.cs" />
|
||||
<Compile Include="Io\JavaFileStorage.cs" />
|
||||
<Compile Include="Io\MegaFileStorage.cs" />
|
||||
<Compile Include="Io\NetFtpFileStorage.cs" />
|
||||
<Compile Include="Io\OfflineSwitchableFileStorage.cs" />
|
||||
<Compile Include="Io\OneDrive2FileStorage.cs" />
|
||||
@@ -165,6 +166,9 @@
|
||||
<PackageReference Include="FluentFTP">
|
||||
<Version>31.3.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MegaApiClient">
|
||||
<Version>1.10.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Graph">
|
||||
<Version>1.21.0</Version>
|
||||
</PackageReference>
|
||||
@@ -276,6 +280,9 @@
|
||||
<PackageReference Include="Xamarin.Android.Support.ViewPager">
|
||||
<Version>28.0.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.AndroidX.Preference">
|
||||
<Version>1.1.1.11</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (defaultPath.StartsWith("sftp://"))
|
||||
@@ -241,7 +302,9 @@ namespace keepass2android
|
||||
ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud");
|
||||
else if (defaultPath.StartsWith("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;
|
||||
Util.ShowFilenameDialog(_activity,
|
||||
@@ -472,7 +535,8 @@ namespace keepass2android
|
||||
{
|
||||
return ioc.Path.StartsWith("http")
|
||||
|| 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_mega_login_title">Enter your MEGA account login data:</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_appfolder">Keepass2Android App folder</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>
|
||||
|
||||
|
||||
@@ -733,6 +733,7 @@ namespace keepass2android
|
||||
new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new WebDavFileStorage(this),
|
||||
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new MegaFileStorage(App.Context),
|
||||
//new LegacyWebDavStorage(this),
|
||||
//new LegacyFtpStorage(this),
|
||||
#endif
|
||||
|
||||
@@ -467,6 +467,7 @@
|
||||
<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_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_dropboxKP2A.png" />
|
||||
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_file.png" />
|
||||
@@ -1078,6 +1079,9 @@
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_dropbox.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_mega.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable-xhdpi\ic_storage_dropboxKP2A.png" />
|
||||
</ItemGroup>
|
||||
@@ -1936,6 +1940,12 @@
|
||||
<Name>ZlibAndroid</Name>
|
||||
</ProjectReference>
|
||||
</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" />
|
||||
<!-- 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.
|
||||
|
||||
Reference in New Issue
Block a user