improvements to OneDrive2 implementation: it's now possible select between different access scopes, also fixed several issues

This commit is contained in:
Philipp Crocoll
2019-10-03 16:42:50 +02:00
parent 99d0ecfd59
commit 57e9253270
10 changed files with 246 additions and 80 deletions

View File

@@ -11,6 +11,7 @@ using Android.Content;
using Android.OS;
using Android.Util;
using Java.Net;
using keepass2android.Io.ItemLocation;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using Microsoft.Graph;
@@ -18,15 +19,13 @@ using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using Newtonsoft.Json;
using Exception = System.Exception;
using File = Microsoft.Graph.File;
using String = System.String;
namespace keepass2android.Io
{
public class OneDrive2FileStorage : IFileStorage
namespace ItemLocation
{
public static IPublicClientApplication _publicClientApp = null;
private string ClientID = "8374f801-0f55-407d-80cc-9a04fe86d9b2";
public class User
{
public string Name { get; set; }
@@ -45,9 +44,10 @@ namespace keepass2android.Io
public string Id { get; set; }
}
public class OneDrive2ItemLocation
public class OneDrive2ItemLocation<OneDrive2PrefixContainerType> where OneDrive2PrefixContainerType: OneDrive2PrefixContainer, new()
{
private const string Onedrive2Prefix = "onedrive2://";
public User User { get; set; } = new User();
public Share Share { get; set; } = new Share();
public string DriveId { get; set; }
@@ -55,11 +55,11 @@ namespace keepass2android.Io
public List<Item> LocalPath { get; set; } = new List<Item>();
public string LocalPathString { get { return string.Join("/", LocalPath.Select(i => i.Name)); } }
public OneDrive2ItemLocation Parent
public OneDrive2ItemLocation<OneDrive2PrefixContainerType> Parent
{
get
{
OneDrive2ItemLocation copy = OneDrive2ItemLocation.FromString(this.ToString());
OneDrive2ItemLocation< OneDrive2PrefixContainerType> copy = OneDrive2ItemLocation< OneDrive2PrefixContainerType>.FromString(this.ToString());
if (copy.LocalPath.Any())
{
//pop last:
@@ -76,7 +76,7 @@ namespace keepass2android.Io
public override string ToString()
{
string path = Onedrive2Prefix + string.Join("\\", (new List<string> { User.Id, User.Name,
string path = (new OneDrive2PrefixContainerType()).Onedrive2Prefix + string.Join("\\", (new List<string> { User.Id, User.Name,
Share.Id, Share.Name,Share.WebUrl,
string.Join("/", LocalPath.Select(i => Encode(i.Id)+":"+Encode(i.Name))),
DriveId
@@ -90,12 +90,12 @@ namespace keepass2android.Io
return WebUtility.UrlEncode(s);
}
public static OneDrive2ItemLocation FromString(string p)
public static OneDrive2ItemLocation<OneDrive2PrefixContainerType> FromString(string p)
{
if ((p == null) || (p == Onedrive2Prefix))
return new OneDrive2ItemLocation();
if ((p == null) || (p == (new OneDrive2PrefixContainerType()).Onedrive2Prefix))
return new OneDrive2ItemLocation<OneDrive2PrefixContainerType>();
if (!p.StartsWith(Onedrive2Prefix))
if (!p.StartsWith((new OneDrive2PrefixContainerType()).Onedrive2Prefix))
throw new Exception("path not starting with prefix!");
if (!p.Contains("?"))
throw new Exception("not found postfix");
@@ -104,10 +104,10 @@ namespace keepass2android.Io
if (int.Parse(lengthParts[1]) != p.Length)
throw new Exception("Invalid length postfix in " + p);
p = p.Substring(Onedrive2Prefix.Length);
p = p.Substring((new OneDrive2PrefixContainerType()).Onedrive2Prefix.Length);
if (p == "")
return new OneDrive2ItemLocation();
OneDrive2ItemLocation result = new OneDrive2ItemLocation();
return new OneDrive2ItemLocation<OneDrive2PrefixContainerType>();
OneDrive2ItemLocation<OneDrive2PrefixContainerType> result = new OneDrive2ItemLocation<OneDrive2PrefixContainerType>();
var parts = p.Split("\\");
if (parts.Length != 7)
{
@@ -140,18 +140,18 @@ namespace keepass2android.Io
return WebUtility.UrlDecode(p0);
}
public OneDrive2ItemLocation BuildLocalChildLocation(string name, string id, string parentReferenceDriveId)
public OneDrive2ItemLocation<OneDrive2PrefixContainerType> BuildLocalChildLocation(string name, string id, string parentReferenceDriveId)
{
//copy this:
OneDrive2ItemLocation copy = OneDrive2ItemLocation.FromString(this.ToString());
OneDrive2ItemLocation<OneDrive2PrefixContainerType> copy = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(this.ToString());
copy.LocalPath.Add(new Item { Name = name, Id = id });
copy.DriveId = parentReferenceDriveId;
return copy;
}
public static OneDrive2ItemLocation RootForUser(string accountUsername, string accountHomeAccountId)
public static OneDrive2ItemLocation<OneDrive2PrefixContainerType> RootForUser(string accountUsername, string accountHomeAccountId)
{
OneDrive2ItemLocation loc = new OneDrive2ItemLocation
OneDrive2ItemLocation<OneDrive2PrefixContainerType> loc = new OneDrive2ItemLocation<OneDrive2PrefixContainerType>
{
User =
{
@@ -163,9 +163,9 @@ namespace keepass2android.Io
return loc;
}
public OneDrive2ItemLocation BuildShare(string id, string name, string webUrl, string driveId)
public OneDrive2ItemLocation<OneDrive2PrefixContainerType> BuildShare(string id, string name, string webUrl, string driveId)
{
OneDrive2ItemLocation copy = OneDrive2ItemLocation.FromString(this.ToString());
OneDrive2ItemLocation<OneDrive2PrefixContainerType> copy = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(this.ToString());
copy.Share.Id = id;
copy.Share.Name = name;
copy.Share.WebUrl = webUrl;
@@ -176,20 +176,20 @@ namespace keepass2android.Io
}
public static IEnumerable<string> Scopes
{
get
{
return new List<string>
{
"https://graph.microsoft.com/Files.Read",
"https://graph.microsoft.com/Files.Read.All",
"https://graph.microsoft.com/Files.ReadWrite",
"https://graph.microsoft.com/Files.ReadWrite.All",
"https://graph.microsoft.com/User.Read"
};
}
public abstract class OneDrive2FileStorage<OneDrive2PrefixContainerType> : IFileStorage where OneDrive2PrefixContainerType: OneDrive2PrefixContainer, new()
{
public static IPublicClientApplication _publicClientApp = null;
private string ClientID = "8374f801-0f55-407d-80cc-9a04fe86d9b2";
public abstract IEnumerable<string> Scopes
{
get;
}
public OneDrive2FileStorage()
@@ -201,8 +201,14 @@ namespace keepass2android.Io
class PathItemBuilder
{
private readonly string _specialFolder;
public IGraphServiceClient client;
public OneDrive2ItemLocation itemLocation;
public OneDrive2ItemLocation<OneDrive2PrefixContainerType> itemLocation;
public PathItemBuilder(string specialFolder)
{
_specialFolder = specialFolder;
}
public IDriveItemRequestBuilder getPathItem()
@@ -214,7 +220,10 @@ namespace keepass2android.Io
}
if ("me".Equals(itemLocation.Share.Id))
{
if (_specialFolder == null)
pathItem = client.Me.Drive.Root;
else
pathItem = client.Me.Drive.Special[_specialFolder];
if (itemLocation.LocalPath.Any())
{
pathItem = pathItem.ItemWithPath(itemLocation.LocalPathString);
@@ -266,17 +275,23 @@ namespace keepass2android.Io
}
}
private string protocolId;
private const string protocolId = "onedrive2";
private string getProtocolId()
protected string ProtocolId
{
get
{
if (protocolId == null)
{
protocolId = (new OneDrive2PrefixContainerType()).Onedrive2ProtocolId;
}
return protocolId;
}
}
public IEnumerable<string> SupportedProtocols
{
get { yield return protocolId; }
get { yield return ProtocolId; }
}
Dictionary<String /*userid*/, IGraphServiceClient> mClientByUser =
@@ -284,7 +299,7 @@ namespace keepass2android.Io
private IGraphServiceClient tryGetMsGraphClient(String path)
{
String userId = OneDrive2ItemLocation.FromString(path).User.Id;
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).User.Id;
if (mClientByUser.ContainsKey(userId))
return mClientByUser[userId];
return null;
@@ -324,14 +339,14 @@ namespace keepass2android.Io
}
protected abstract string SpecialFolder { get; }
private PathItemBuilder GetPathItemBuilder(String path)
{
PathItemBuilder result = new PathItemBuilder();
PathItemBuilder result = new PathItemBuilder(SpecialFolder);
result.itemLocation = OneDrive2ItemLocation.FromString(path);
result.itemLocation = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path);
if (string.IsNullOrEmpty(result.itemLocation.User?.Name))
{
throw new Exception("path does not contain user");
@@ -433,10 +448,10 @@ namespace keepass2android.Io
class OneDrive2FileStorageWriteTransaction : IWriteTransaction
{
private readonly string _path;
private readonly OneDrive2FileStorage _filestorage;
private readonly OneDrive2FileStorage<OneDrive2PrefixContainerType> _filestorage;
private MemoryStream _memoryStream;
public OneDrive2FileStorageWriteTransaction(string path, OneDrive2FileStorage filestorage)
public OneDrive2FileStorageWriteTransaction(string path, OneDrive2FileStorage<OneDrive2PrefixContainerType> filestorage)
{
_path = path;
_filestorage = filestorage;
@@ -493,12 +508,12 @@ namespace keepass2android.Io
public string GetFileExtension(IOConnectionInfo ioc)
{
return UrlUtil.GetExtension(OneDrive2ItemLocation.FromString(ioc.Path).LocalPathString);
return UrlUtil.GetExtension(OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path).LocalPathString);
}
private string GetFilename(string path)
{
string localPath = "/"+OneDrive2ItemLocation.FromString(path).LocalPath;
string localPath = "/"+OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).LocalPathString;
return localPath.Substring(localPath.LastIndexOf("/", StringComparison.Ordinal) + 1);
}
@@ -582,7 +597,7 @@ namespace keepass2android.Io
}
private FileDescription GetFileDescription(OneDrive2ItemLocation path, DriveItem i)
private FileDescription GetFileDescription(OneDrive2ItemLocation<OneDrive2PrefixContainerType> path, DriveItem i)
{
FileDescription e = new FileDescription();
if (i.Size != null)
@@ -643,7 +658,7 @@ namespace keepass2android.Io
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode,
string protocolId)
{
String path = getProtocolId() + "://";
String path = ProtocolId+ "://";
activity.StartSelectFileProcess(IOConnectionInfo.FromPath(path), isForSave, requestCode);
}
@@ -740,7 +755,7 @@ namespace keepass2android.Io
IAccount account = null;
try
{
String userId = OneDrive2ItemLocation.FromString(activity.Ioc.Path).User?.Id;
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(activity.Ioc.Path).User?.Id;
if (mClientByUser.ContainsKey(userId))
{
finishActivityWithSuccess(activity);
@@ -808,7 +823,7 @@ namespace keepass2android.Io
string buildRootPathForUser(AuthenticationResult res)
{
return OneDrive2ItemLocation.RootForUser(res.Account.Username, res.Account.HomeAccountId.Identifier).ToString();
return OneDrive2ItemLocation<OneDrive2PrefixContainerType>.RootForUser(res.Account.Username, res.Account.HomeAccountId.Identifier).ToString();
}
@@ -828,8 +843,8 @@ namespace keepass2android.Io
{
try
{
var itemLocation = OneDrive2ItemLocation.FromString(ioc.Path);
string result = getProtocolId() + "://";
var itemLocation = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path);
string result = ProtocolId+ "://";
if (!string.IsNullOrEmpty(itemLocation.User?.Id))
{
result += itemLocation.User?.Name;
@@ -848,23 +863,31 @@ namespace keepass2android.Io
}
catch (Exception e)
{
return e.ToString(); //TODO throw
Kp2aLog.Log("Invalid OneDrive location " + ioc.Path +
". Note that SprEnging expressions like {DB_PATH} are not supported with OneDrive!");
return ProtocolId + "://(invalid)";
}
}
private List<FileDescription> ListShares(OneDrive2ItemLocation parentPath, IGraphServiceClient client)
private List<FileDescription> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, IGraphServiceClient client)
{
List<FileDescription> result = new List<FileDescription>();
DriveItem root = Task.Run(async () => await client.Me.Drive.Root.Request().GetAsync()).Result;
FileDescription myEntry = GetFileDescription(parentPath.BuildShare("me","me","me", root.ParentReference?.DriveId), root);
myEntry.DisplayName = "My OneDrive";
myEntry.DisplayName = MyOneDriveDisplayName;
result.Add(myEntry);
if (!CanListShares)
return result;
IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage =
Task.Run(async () => await client.Me.Drive.SharedWithMe().Request().GetAsync()).Result;
@@ -887,6 +910,10 @@ namespace keepass2android.Io
return result;
}
protected virtual string MyOneDriveDisplayName { get { return "My OneDrive"; } }
public abstract bool CanListShares { get; }
DriveItem TryFindFile(PathItemBuilder parent, string filename)
{
IDriveItemChildrenCollectionPage itemsPage = Task.Run(async () => await parent.getPathItem()
@@ -920,7 +947,7 @@ namespace keepass2android.Io
{
DriveItem driveItem = new DriveItem();
driveItem.Name = newFilename;
driveItem.File = new File();
PathItemBuilder pathItemBuilder = GetPathItemBuilder(parent);
//see if such a file exists already:
@@ -951,7 +978,7 @@ namespace keepass2android.Io
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
return IOConnectionInfo.FromPath(OneDrive2ItemLocation.FromString(ioc.Path).Parent.ToString());
return IOConnectionInfo.FromPath(OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path).Parent.ToString());
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
@@ -969,4 +996,61 @@ namespace keepass2android.Io
return false;
}
}
public class OneDrive2FullFileStorage: OneDrive2FileStorage<OneDrive2FullPrefixContainer>
{
public override IEnumerable<string> Scopes
{
get
{
return new List<string>
{
"https://graph.microsoft.com/Files.ReadWrite",
"https://graph.microsoft.com/Files.ReadWrite.All"
};
}
}
public override bool CanListShares { get { return true; } }
protected override string SpecialFolder { get { return null; } }
}
public class OneDrive2MyFilesFileStorage : OneDrive2FileStorage<OneDrive2MyFilesPrefixContainer>
{
public override IEnumerable<string> Scopes
{
get
{
return new List<string>
{
"https://graph.microsoft.com/Files.ReadWrite"
};
}
}
public override bool CanListShares { get { return false; } }
protected override string SpecialFolder { get { return null; } }
}
public class OneDrive2AppFolderFileStorage : OneDrive2FileStorage<OneDrive2AppFolderPrefixContainer>
{
public override IEnumerable<string> Scopes
{
get
{
return new List<string>
{
"https://graph.microsoft.com/Files.ReadWrite.AppFolder"
};
}
}
protected override string SpecialFolder { get { return "approot"; } }
public override bool CanListShares { get { return false; } }
protected override string MyOneDriveDisplayName { get { return "Keepass2Android App Folder"; } }
}
}

View File

@@ -0,0 +1,26 @@
namespace keepass2android.Io.ItemLocation
{
public abstract class OneDrive2PrefixContainer
{
public abstract string Onedrive2ProtocolId { get; }
public string Onedrive2Prefix { get { return Onedrive2ProtocolId + "://"; } }
}
//for permissions including all my files and all shared files
public class OneDrive2FullPrefixContainer : OneDrive2PrefixContainer
{
public override string Onedrive2ProtocolId { get { return "onedrive2_full"; }}
}
//for permissions including all my files
public class OneDrive2MyFilesPrefixContainer : OneDrive2PrefixContainer
{
public override string Onedrive2ProtocolId { get { return "onedrive2_myfiles"; } }
}
//for permissions to app folder only
public class OneDrive2AppFolderPrefixContainer : OneDrive2PrefixContainer
{
public override string Onedrive2ProtocolId { get { return "onedrive2_appfolder"; } }
}
}

View File

@@ -89,6 +89,7 @@
<Compile Include="Io\NetFtpFileStorage.cs" />
<Compile Include="Io\OfflineSwitchableFileStorage.cs" />
<Compile Include="Io\OneDrive2FileStorage.cs" />
<Compile Include="Io\OneDrive2PrefixContainer.cs" />
<Compile Include="Io\PCloudFileStorage.cs" />
<Compile Include="Io\SftpFileStorage.cs" />
<Compile Include="Io\OneDriveFileStorage.cs" />

View File

@@ -253,10 +253,10 @@ namespace keepass2android
int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
string protocolId = protocolSeparatorPos < 0 ?
"file" : displayPath.Substring(0, protocolSeparatorPos);
Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId);
Drawable drawable = App.Kp2a.GetStorageIcon(protocolId);
FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
String title = App.Kp2a.GetResourceString("filestoragename_" + protocolId);
String title = App.Kp2a.GetStorageDisplayName(protocolId);
FindViewById<TextView>(Resource.Id.filestorage_label).Text = title;
FindViewById<TextView>(Resource.Id.label_filename).Text = protocolSeparatorPos < 0 ?

View File

@@ -968,10 +968,10 @@ namespace keepass2android
int protocolSeparatorPos = ioc.Path.IndexOf("://", StringComparison.Ordinal);
string protocolId = protocolSeparatorPos < 0 ?
"file" : ioc.Path.Substring(0, protocolSeparatorPos);
Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId);
Drawable drawable = App.Kp2a.GetStorageIcon(protocolId);
ees.FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
String fs_title = App.Kp2a.GetResourceString("filestoragename_" + protocolId);
String fs_title = App.Kp2a.GetStorageDisplayName(protocolId);
ees.FindViewById<TextView>(Resource.Id.filestorage_label).Text = fs_title;
string displayPath = fileStorage.GetDisplayName(ioc);

View File

@@ -86,6 +86,9 @@ namespace keepass2android
#if NoNet
_displayedProtocolIds.Add("kp2a");
#endif
_displayedProtocolIds = _displayedProtocolIds.GroupBy(p => App.Kp2a.GetStorageMainTypeDisplayName(p))
.Select(g => string.Join(",", g)).ToList();
}
public override Object GetItem(int position)
@@ -135,14 +138,15 @@ namespace keepass2android
var protocolId = _displayedProtocolIds[position];
btn.Tag = protocolId;
string firstProtocolInList = protocolId.Split(",").First();
Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId);
Drawable drawable = App.Kp2a.GetStorageIcon(firstProtocolInList);
String title =
protocolId == "kp2a" ? App.Kp2a.GetResourceString("get_regular_version")
:
App.Kp2a.GetResourceString("filestoragename_" + protocolId);
App.Kp2a.GetStorageMainTypeDisplayName(firstProtocolInList);
var str = new SpannableString(title);
btn.TextFormatted = str;
@@ -167,6 +171,21 @@ namespace keepass2android
return;
}
if (protocolId.Contains(","))
{
//bring up a selection dialog to select the variant of the file storage
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetItems(protocolId.Split(",").Select(singleProtocolId => App.Kp2a.GetStorageDisplayName(singleProtocolId)).ToArray(),
delegate(object sender, DialogClickEventArgs args)
{
string[] singleProtocolIds = protocolId.Split(",");
OnItemSelected(singleProtocolIds[args.Which]);
});
builder.Show();
return;
}
var field = typeof(Resource.String).GetField("filestoragehelp_" + protocolId);
if (field == null)
{

View File

@@ -346,12 +346,12 @@ namespace keepass2android
int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
string protocolId = protocolSeparatorPos < 0 ?
"file" : displayPath.Substring(0, protocolSeparatorPos);
Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId);
Drawable drawable = App.Kp2a.GetStorageIcon(protocolId);
FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Visible;
String title = App.Kp2a.GetResourceString("filestoragename_" + protocolId);
String title = App.Kp2a.GetStorageDisplayName(protocolId);
FindViewById<TextView>(Resource.Id.filestorage_label).Text = title;
FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Visible;

View File

@@ -553,6 +553,9 @@
<string name="filestoragehelp_pcloud">This storage type will only request access to the "Applications/Keepass2Android" folder. If you want to use an existing database from your PCloud account, please make sure the file is placed in this folder.</string>
<string name="filestoragename_onedrive">OneDrive</string>
<string name="filestoragename_onedrive2">OneDrive</string>
<string name="filestoragename_onedrive2_full">All files and shared files</string>
<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>

View File

@@ -112,7 +112,7 @@ namespace keepass2android
if (position < _displayedDatabases.Count)
{
var db = _displayedDatabases[position];
drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + Util.GetProtocolId(db.Ioc));
drawable = App.Kp2a.GetStorageIcon(Util.GetProtocolId(db.Ioc));
displayName = db.KpDatabase.Name;
displayName += "\n" + App.Kp2a.GetFileStorage(db.Ioc).GetDisplayName(db.Ioc);
btn.SetBackgroundResource(Resource.Drawable.storagetype_button_bg);

View File

@@ -492,6 +492,16 @@ namespace keepass2android
throw new Exception("Invalid key " + key);
return Application.Context.GetString((int)field.GetValue(null));
}
public Drawable GetStorageIcon(string protocolId)
{
//storages can provide variants but still use the same icon for all
if (protocolId.Contains("_"))
protocolId = protocolId.Split("_").First();
return GetResourceDrawable("ic_storage_" + protocolId);
}
public Drawable GetResourceDrawable(string key)
{
if (key == "ic_storage_skydrive")
@@ -688,7 +698,9 @@ namespace keepass2android
new DropboxAppFolderFileStorage(Application.Context, this),
new GoogleDriveFileStorage(Application.Context, this),
new OneDriveFileStorage(Application.Context, this),
new OneDrive2FileStorage(),
new OneDrive2FullFileStorage(),
new OneDrive2MyFilesFileStorage(),
new OneDrive2AppFolderFileStorage(),
new SftpFileStorage(Application.Context, this),
new NetFtpFileStorage(Application.Context, this),
new WebDavFileStorage(this),
@@ -1104,6 +1116,27 @@ namespace keepass2android
{
return _childDatabases.Any(ioc => ioc.IsSameFileAs(db.Ioc));
}
public string GetStorageMainTypeDisplayName(string protocolId)
{
var parts = protocolId.Split("_");
return GetResourceString("filestoragename_" + parts[0]);
}
public string GetStorageDisplayName(string protocolId)
{
if (protocolId.Contains("_"))
{
var parts = protocolId.Split("_");
return GetResourceString("filestoragename_" + parts[0]) + " (" +
GetResourceString("filestoragename_" + protocolId) + ")";
}
else
return GetResourceString("filestoragename_" + protocolId);
}
}