Added tests and functionality to ensure that caching and syncing works when the remote file is removed.
Added UI strings for sync and cache functionality
This commit is contained in:
		| @@ -3,6 +3,7 @@ using System.Collections.Generic; | ||||
| using System.Globalization; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Net; | ||||
| using System.Text; | ||||
|  | ||||
| using Android.App; | ||||
| @@ -57,7 +58,19 @@ namespace keepass2android.Io | ||||
|  | ||||
| 		public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			return IOConnection.OpenRead(ioc); | ||||
| 			try | ||||
| 			{ | ||||
| 				return IOConnection.OpenRead(ioc); | ||||
| 			} | ||||
| 			catch (WebException ex) | ||||
| 			{ | ||||
| 				if ((ex.Response is HttpWebResponse) && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound)) | ||||
| 				{ | ||||
| 					throw new FileNotFoundException("404!", ioc.Path, ex); | ||||
| 				} | ||||
| 				throw; | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
|   | ||||
| @@ -39,6 +39,7 @@ namespace keepass2android | ||||
| 		DownloadingRemoteFile, | ||||
| 		UploadingFile, | ||||
| 		FilesInSync, | ||||
| 		SynchronizedDatabaseSuccessfully | ||||
| 		SynchronizedDatabaseSuccessfully, | ||||
| 		RestoringRemoteFile | ||||
|     } | ||||
| } | ||||
| @@ -13,6 +13,7 @@ namespace keepass2android | ||||
| 	{ | ||||
| 		private readonly Context _context; | ||||
| 		private readonly IKp2aApp _app; | ||||
| 		private SaveDb _saveDb; | ||||
|  | ||||
| 		public SynchronizeCachedDatabase(Context context, IKp2aApp app, OnFinish finish) | ||||
| 			: base(finish) | ||||
| @@ -37,10 +38,19 @@ namespace keepass2android | ||||
| 				//download file from remote location and calculate hash: | ||||
| 				StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); | ||||
| 				string hash; | ||||
| 				//todo: catch filenotfound and upload then | ||||
| 				MemoryStream remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash); | ||||
|  | ||||
| 				//todo: what happens if something fails here? | ||||
| 				 | ||||
| 				MemoryStream remoteData; | ||||
| 				try | ||||
| 				{ | ||||
| 					remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash); | ||||
| 				} | ||||
| 				catch (FileNotFoundException) | ||||
| 				{ | ||||
| 					StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile)); | ||||
| 					cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)); | ||||
| 					Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				//check if remote file was modified: | ||||
| 				if (cachingFileStorage.GetBaseVersionHash(ioc) != hash) | ||||
| @@ -49,7 +59,7 @@ namespace keepass2android | ||||
| 					if (cachingFileStorage.HasLocalChanges(ioc)) | ||||
| 					{ | ||||
| 						//conflict! need to merge | ||||
| 						SaveDb saveDb = new SaveDb(_context, _app, new ActionOnFinish((success, result) => | ||||
| 						_saveDb = new SaveDb(_context, _app, new ActionOnFinish((success, result) => | ||||
| 							{ | ||||
| 								if (!success) | ||||
| 								{ | ||||
| @@ -59,8 +69,9 @@ namespace keepass2android | ||||
| 								{ | ||||
| 									Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
| 								} | ||||
| 								_saveDb = null; | ||||
| 							}), false, remoteData); | ||||
| 						saveDb.Run(); | ||||
| 						_saveDb.Run(); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| @@ -94,5 +105,11 @@ namespace keepass2android | ||||
| 			} | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		public void JoinWorkerThread() | ||||
| 		{ | ||||
| 			if (_saveDb != null) | ||||
| 				_saveDb.JoinWorkerThread(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,8 @@ namespace Kp2aUnitTests | ||||
|             // Run all tests from this assembly | ||||
|             runner.AddTests(Assembly.GetExecutingAssembly()); | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) }); | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestLoadDb) });}} | ||||
| 			//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) }); | ||||
| 			//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote")); | ||||
| 			//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly")); | ||||
| 			//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles")); | ||||
|   | ||||
| @@ -168,7 +168,32 @@ namespace Kp2aUnitTests | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
|  | ||||
| 			Assert.AreEqual(newContent, File.ReadAllText(CachingTestFile)); | ||||
| 			Assert.AreEqual(newContent, File.ReadAllText(CachingTestFile));			 | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestLoadFromRemoteWhenRemoteDeleted() | ||||
| 		{ | ||||
| 			SetupFileStorage(); | ||||
|  | ||||
| 			//read the file once. Should now be in the cache. | ||||
| 			ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			//delete remote file: | ||||
| 			_testFileStorage.DeleteFile(IocForCacheFile); | ||||
|  | ||||
| 			//read again. shouldn't throw and give the same result: | ||||
| 			var memStream = ReadToMemoryStream(_fileStorage, CachingTestFile); | ||||
|  | ||||
| 			//check if we received the correct content: | ||||
| 			Assert.AreEqual(_defaultCacheFileContents, MemoryStreamToString(memStream));  | ||||
|  | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			Assert.IsFalse(_testCacheSupervisor.RestoredRemoteCalled); | ||||
|  | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		private void WriteContentToCacheFile(string newContent) | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| using System.Linq; | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Threading; | ||||
| using Android.App; | ||||
| using Android.OS; | ||||
| using KeePassLib.Serialization; | ||||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||||
| using keepass2android; | ||||
| using keepass2android.Io; | ||||
|  | ||||
| namespace Kp2aUnitTests | ||||
| { | ||||
| @@ -91,6 +94,135 @@ namespace Kp2aUnitTests | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void LoadFromRemote1and1() | ||||
| 		{ | ||||
| 			var ioc = RemoteIoc1and1; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public! | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
| 			app.CreateNewDatabase(); | ||||
| 			 | ||||
| 			bool loadSuccesful = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) => | ||||
| 				{ | ||||
| 					if (!success) | ||||
| 						Android.Util.Log.Debug("KP2ATest", "error loading db: " + message); | ||||
| 					loadSuccesful = success; | ||||
| 				}) | ||||
| 				); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "Running ProgressTask"); | ||||
| 			pt.Run(); | ||||
| 			pt.JoinWorkerThread(); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "PT.run finished"); | ||||
| 			Assert.IsTrue(loadSuccesful, "didn't succesfully load database :-("); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void LoadFromRemote1and1NonExisting() | ||||
| 		{ | ||||
| 			var ioc = RemoteIoc1and1NonExisting; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public! | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
| 			app.CreateNewDatabase(); | ||||
|  | ||||
| 			bool loadSuccesful = false; | ||||
| 			bool gotError = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) => | ||||
| 			{ | ||||
| 				if (!success) | ||||
| 				{ | ||||
| 					Android.Util.Log.Debug("KP2ATest", "error loading db: " + message); | ||||
| 					gotError = true; | ||||
| 				} | ||||
| 				loadSuccesful = success; | ||||
| 			}) | ||||
| 				); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "Running ProgressTask"); | ||||
| 			pt.Run(); | ||||
| 			pt.JoinWorkerThread(); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "PT.run finished"); | ||||
| 			Assert.IsFalse(loadSuccesful); | ||||
| 			Assert.IsTrue(gotError); | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void LoadFromRemote1and1WrongCredentials() | ||||
| 		{ | ||||
| 			var ioc = RemoteIoc1and1WrongCredentials; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public! | ||||
| 			IKp2aApp app = new TestKp2aApp(); | ||||
| 			app.CreateNewDatabase(); | ||||
|  | ||||
| 			bool loadSuccesful = false; | ||||
| 			bool gotError = false; | ||||
| 			LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) => | ||||
| 			{ | ||||
| 				if (!success) | ||||
| 				{ | ||||
| 					Android.Util.Log.Debug("KP2ATest", "error loading db: " + message); | ||||
| 					gotError = true; | ||||
| 				} | ||||
| 				loadSuccesful = success; | ||||
| 			}) | ||||
| 				); | ||||
| 			ProgressTask pt = new ProgressTask(app, Application.Context, task); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "Running ProgressTask"); | ||||
| 			pt.Run(); | ||||
| 			pt.JoinWorkerThread(); | ||||
| 			Android.Util.Log.Debug("KP2ATest", "PT.run finished"); | ||||
| 			Assert.IsFalse(loadSuccesful); | ||||
| 			Assert.IsTrue(gotError); | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void FileNotFoundExceptionWithWebDav() | ||||
| 		{ | ||||
| 			var fileStorage = new BuiltInFileStorage(); | ||||
| 			 | ||||
| 			//should work: | ||||
| 			using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1)) | ||||
| 			{ | ||||
| 				stream.CopyTo(new MemoryStream()); | ||||
| 			} | ||||
| 		 | ||||
| 			//shouldn't give FileNotFound: | ||||
| 			bool gotException = false; | ||||
| 			try | ||||
| 			{ | ||||
| 				using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1WrongCredentials)) | ||||
| 				{ | ||||
| 					stream.CopyTo(new MemoryStream()); | ||||
| 				} | ||||
| 			} | ||||
| 			catch (FileNotFoundException) | ||||
| 			{ | ||||
| 				Assert.Fail("shouldn't get FileNotFound with wrong credentials"); | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Kp2aLog.Log("received "+e); | ||||
| 				gotException = true; | ||||
| 			} | ||||
| 			Assert.IsTrue(gotException); | ||||
| 			//should give FileNotFound: | ||||
| 			gotException = false; | ||||
| 			try | ||||
| 			{ | ||||
| 				using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1NonExisting)) | ||||
| 				{ | ||||
| 					stream.CopyTo(new MemoryStream()); | ||||
| 				} | ||||
| 			} | ||||
| 			catch (FileNotFoundException) | ||||
| 			{ | ||||
| 				gotException = true; | ||||
| 			} | ||||
| 			Assert.IsTrue(gotException); | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestLoadKdbpWithPasswordOnly() | ||||
| 		{ | ||||
|   | ||||
| @@ -22,15 +22,6 @@ namespace Kp2aUnitTests | ||||
| 		private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor(); | ||||
| 		private TestFileStorage _testFileStorage = new TestFileStorage(); | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestTodos() | ||||
| 		{ | ||||
| 			Assert.IsFalse(true, "Wird immer ManagedTransform benutzt??"); | ||||
| 			Assert.IsFalse(true, "TODOs in SyncDb"); | ||||
| 			Assert.IsFalse(true, "FileNotFound"); | ||||
| 			Assert.IsFalse(true, "Test merge files"); | ||||
| 		} | ||||
|  | ||||
| 		protected override TestKp2aApp CreateTestKp2aApp() | ||||
| 		{ | ||||
| 			TestKp2aApp app = base.CreateTestKp2aApp(); | ||||
| @@ -100,6 +91,87 @@ namespace Kp2aUnitTests | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, appRemoteLoaded.GetDb().KpDatabase); | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestSyncWhenRemoteDeleted() | ||||
| 		{ | ||||
| 			//create the default database: | ||||
| 			TestKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
|  | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo {Path = DefaultFilename}); | ||||
| 			//save it and reload it so we have a base version ("remote" and in the cache) | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			//delete remote: | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename }); | ||||
|  | ||||
| 			string resultMessage; | ||||
| 			bool wasSuccessful; | ||||
|  | ||||
| 			//sync: | ||||
| 			Synchronize(app, out wasSuccessful, out resultMessage); | ||||
| 			Assert.IsTrue(wasSuccessful); | ||||
| 			Assert.AreEqual(resultMessage, app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
|  | ||||
| 			//ensure the file is back here: | ||||
| 			var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, app2.GetDb().KpDatabase); | ||||
| 		} | ||||
|  | ||||
| 		[TestMethod] | ||||
| 		public void TestSyncWhenConflict() | ||||
| 		{ | ||||
| 			//create the default database: | ||||
| 			TestKp2aApp app = SetupAppWithDefaultDatabase(); | ||||
|  | ||||
| 			IOConnection.DeleteFile(new IOConnectionInfo {Path = DefaultFilename}); | ||||
| 			//save it and reload it so we have a base version ("remote" and in the cache) | ||||
| 			SaveDatabase(app); | ||||
| 			app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
| 			app2.FileStorage = _testFileStorage; //give app2 direct access to the remote file | ||||
|  | ||||
| 			//go offline: | ||||
| 			_testFileStorage.Offline = true; | ||||
|  | ||||
|  | ||||
| 			string resultMessage; | ||||
| 			bool wasSuccessful; | ||||
|  | ||||
| 			//modify the database by adding a group in both apps: | ||||
| 			PwGroup newGroup1 = new PwGroup(true, true, "TestGroup", PwIcon.Apple); | ||||
| 			app.GetDb().KpDatabase.RootGroup.AddGroup(newGroup1, true); | ||||
| 			PwGroup newGroup2 = new PwGroup(true, true, "TestGroupApp2", PwIcon.Apple); | ||||
| 			app2.GetDb().KpDatabase.RootGroup.AddGroup(newGroup2, true); | ||||
| 			//save the database again (will be saved locally only for "app") | ||||
| 			SaveDatabase(app); | ||||
| 			Assert.IsTrue(_testCacheSupervisor.CouldntSaveToRemoteCalled); | ||||
| 			_testCacheSupervisor.CouldntSaveToRemoteCalled = false; | ||||
|  | ||||
| 			//go online again: | ||||
| 			_testFileStorage.Offline = false; | ||||
| 			 | ||||
| 			//...and remote only for "app2": | ||||
| 			SaveDatabase(app2); | ||||
|  | ||||
| 			//try to sync: | ||||
| 			Synchronize(app, out wasSuccessful, out resultMessage); | ||||
|  | ||||
| 			Assert.IsTrue(wasSuccessful); | ||||
| 			Assert.AreEqual(UiStringKey.SynchronizedDatabaseSuccessfully.ToString(), resultMessage); | ||||
|  | ||||
| 			//build app2 with the newGroup1: | ||||
| 			app2.GetDb().KpDatabase.RootGroup.AddGroup(newGroup1, true); | ||||
|  | ||||
| 			var app3 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile); | ||||
|  | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, app2.GetDb().KpDatabase); | ||||
| 			AssertDatabasesAreEqual(app.GetDb().KpDatabase, app3.GetDb().KpDatabase); | ||||
|  | ||||
|  | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 		private void Synchronize(TestKp2aApp app, out bool wasSuccessful, out string resultMessage) | ||||
| 		{ | ||||
| 			bool success = false; | ||||
| @@ -110,6 +182,7 @@ namespace Kp2aUnitTests | ||||
| 					result = _result; | ||||
| 				})); | ||||
| 			sync.Run(); | ||||
| 			sync.JoinWorkerThread(); | ||||
| 			wasSuccessful = success; | ||||
| 			resultMessage = result; | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										58
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										58
									
								
								src/keepass2android/Resources/Resource.designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -1302,32 +1302,32 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f0800fa | ||||
| 			public const int BinaryDirectory_title = 2131230970; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080143 | ||||
| 			public const int ChangeLog = 2131231043; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080142 | ||||
| 			public const int ChangeLog_0_7 = 2131231042; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080140 | ||||
| 			public const int ChangeLog_0_8 = 2131231040; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013f | ||||
| 			public const int ChangeLog_0_8_1 = 2131231039; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013e | ||||
| 			public const int ChangeLog_0_8_2 = 2131231038; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013d | ||||
| 			public const int ChangeLog = 2131231037; | ||||
| 			public const int ChangeLog_0_8_3 = 2131231037; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013c | ||||
| 			public const int ChangeLog_0_7 = 2131231036; | ||||
| 			public const int ChangeLog_0_8_4 = 2131231036; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013a | ||||
| 			public const int ChangeLog_0_8 = 2131231034; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080139 | ||||
| 			public const int ChangeLog_0_8_1 = 2131231033; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080138 | ||||
| 			public const int ChangeLog_0_8_2 = 2131231032; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080137 | ||||
| 			public const int ChangeLog_0_8_3 = 2131231031; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080136 | ||||
| 			public const int ChangeLog_0_8_4 = 2131231030; | ||||
| 			// aapt resource value: 0x7f080141 | ||||
| 			public const int ChangeLog_keptDonate = 2131231041; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013b | ||||
| 			public const int ChangeLog_keptDonate = 2131231035; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080135 | ||||
| 			public const int ChangeLog_title = 2131231029; | ||||
| 			public const int ChangeLog_title = 2131231035; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08002a | ||||
| 			public const int CheckForFileChangesOnSave_key = 2131230762; | ||||
| @@ -1359,9 +1359,15 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f080129 | ||||
| 			public const int DeletingGroup = 2131231017; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080136 | ||||
| 			public const int DownloadingRemoteFile = 2131231030; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080083 | ||||
| 			public const int FileNotFound = 2131230851; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080139 | ||||
| 			public const int FilesInSync = 2131231033; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080096 | ||||
| 			public const int InvalidPassword = 2131230870; | ||||
| 			 | ||||
| @@ -1437,6 +1443,9 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f0800e5 | ||||
| 			public const int RememberRecentFiles_title = 2131230949; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080138 | ||||
| 			public const int RestoringRemoteFile = 2131231032; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f0800ff | ||||
| 			public const int SaveAttachmentDialog_open = 2131230975; | ||||
| 			 | ||||
| @@ -1482,6 +1491,12 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f08002c | ||||
| 			public const int SuggestionsURL = 2131230764; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f08013a | ||||
| 			public const int SynchronizedDatabaseSuccessfully = 2131231034; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080135 | ||||
| 			public const int SynchronizingCachedDatabase = 2131231029; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080132 | ||||
| 			public const int SynchronizingDatabase = 2131231026; | ||||
| 			 | ||||
| @@ -1506,6 +1521,9 @@ namespace keepass2android | ||||
| 			// aapt resource value: 0x7f08012b | ||||
| 			public const int UndoingChanges = 2131231019; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080137 | ||||
| 			public const int UploadingFile = 2131231031; | ||||
| 			 | ||||
| 			// aapt resource value: 0x7f080027 | ||||
| 			public const int UsageCount_key = 2131230759; | ||||
| 			 | ||||
|   | ||||
| @@ -264,7 +264,14 @@ | ||||
| 	<string name="YesSynchronize">Yes, merge</string> | ||||
| 	<string name="NoOverwrite">No, overwrite</string> | ||||
|  | ||||
| 	<string name="ChangeLog_title">Change log</string> | ||||
| 	<string name="SynchronizingCachedDatabase">Synchronizing cached database...</string> | ||||
| 	<string name="DownloadingRemoteFile">Downloading remote file...</string> | ||||
| 	<string name="UploadingFile">Uploading file...</string> | ||||
| 	<string name="RestoringRemoteFile">Restoring remote file...</string> | ||||
| 	<string name="FilesInSync">Files are in sync.</string> | ||||
| 	<string name="SynchronizedDatabaseSuccessfully">Database synchronized successfully!</string> | ||||
|  | ||||
| 		<string name="ChangeLog_title">Change log</string> | ||||
| 	<string name="ChangeLog_0_8_4"> | ||||
| 		<b>Version 0.8.4</b>\n | ||||
| 		* External database changes are detected and merged when saving\n | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll