Compare commits
	
		
			795 Commits
		
	
	
		
			1.07
			...
			pcloud-hel
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 37c10e7b48 | ||
|   | 02b8292a2e | ||
|   | 2d1909c227 | ||
|   | 87cfa4c4d4 | ||
|   | 58ec829d1a | ||
|   | 90fd2daa01 | ||
|   | d0749324c9 | ||
|   | a43f72d18a | ||
|   | 3d691088be | ||
|   | bca3becadb | ||
|   | d937fbe7ad | ||
|   | c13e00871d | ||
|   | b5b716da4c | ||
|   | 45d2a8e0b2 | ||
|   | d47c948699 | ||
|   | fdf36babba | ||
|   | d47411264e | ||
|   | b9ebad7942 | ||
|   | 2878cc1cb5 | ||
|   | 9277d76f37 | ||
|   | b7b1301334 | ||
|   | 954dd73ffa | ||
|   | 7aeab98c5b | ||
|   | 51123d60d3 | ||
|   | 395d4b5a97 | ||
|   | 654f025f64 | ||
|   | 8a9ccea5d3 | ||
|   | 92e00075c6 | ||
|   | 624e78e734 | ||
|   | 76bb8822fa | ||
|   | e3cba097fa | ||
|   | c812fca8c1 | ||
|   | f86f4ee18c | ||
|   | dafed9786e | ||
|   | cd3a119053 | ||
|   | f143a171aa | ||
|   | ee93d7df82 | ||
|   | 61a3bcc53d | ||
|   | 17a46eb92c | ||
|   | 14b121a8ba | ||
|   | 41b57417e5 | ||
|   | bb492c3043 | ||
|   | b683dca67c | ||
|   | b6e6024bda | ||
|   | 2b3a95e21d | ||
|   | de9c27f1f5 | ||
|   | 85c9cb26b1 | ||
|   | 8ffa27e93d | ||
|   | ea743fc2c5 | ||
|   | 7fab9aab00 | ||
|   | 444e224d41 | ||
|   | 9fde19f1ce | ||
|   | 4e085b5dca | ||
|   | 06ab58cc07 | ||
|   | 9dae1746ec | ||
|   | db87191f1f | ||
|   | 76abe79b6d | ||
|   | 970bc29e01 | ||
|   | 79a3262659 | ||
|   | 3add6d64ef | ||
|   | 43168b18d1 | ||
|   | 6970b42456 | ||
|   | a545b96601 | ||
|   | 59b66409e8 | ||
|   | 25c50bbcaf | ||
|   | a9a5df00d2 | ||
|   | 5f4d9c29dc | ||
|   | 31e158e39b | ||
|   | 25af5844be | ||
|   | f79bb49af1 | ||
|   | 2756bae656 | ||
|   | b574b55f84 | ||
|   | 76ef1c4033 | ||
|   | 409deaee9d | ||
|   | 8396e18283 | ||
|   | 5e952e06ff | ||
|   | 7b6dc3d773 | ||
|   | 1759b67921 | ||
|   | 1307b0e5d1 | ||
|   | c1af6994bf | ||
|   | d09c15e5b8 | ||
|   | ea98b170d9 | ||
|   | c67f4e45c7 | ||
|   | c21a5afe94 | ||
|   | 49de16f0f3 | ||
|   | 6d60afadf4 | ||
|   | c6751af075 | ||
|   | d41aa389a7 | ||
|   | f95986fd7a | ||
|   | ac522bce43 | ||
|   | 6e0729c3b3 | ||
|   | 9ce674da2b | ||
|   | da8a8e7e77 | ||
|   | 566a1088bd | ||
|   | 11f1f07653 | ||
|   | 45194bb26f | ||
|   | 1f6440189f | ||
|   | 60e98d87d8 | ||
|   | f560124088 | ||
|   | 7521d0b787 | ||
|   | c45e52f571 | ||
|   | 275920e5be | ||
|   | 7045c6ee8d | ||
|   | 7a7b65e8c6 | ||
|   | 261526b4d1 | ||
|   | bc74c91a2c | ||
|   | ab47543df0 | ||
|   | 3a601468a9 | ||
|   | f2002e2b90 | ||
|   | 29031167d6 | ||
|   | 7305ac47c3 | ||
|   | 2aec6c9e4b | ||
|   | 5a2cdf3cda | ||
|   | a943df63fc | ||
|   | ca771abebe | ||
|   | 010db6148d | ||
|   | 8f065f480c | ||
|   | f163f9382f | ||
|   | e752244b5f | ||
|   | 4bbb958a05 | ||
|   | d1ddfed8dd | ||
|   | bdb39f203a | ||
|   | 1bcb6aabe7 | ||
|   | 90b09b0f7e | ||
|   | 4e6e0a0e73 | ||
|   | ad4c764adb | ||
|   | 2265432ec4 | ||
|   | bb45e60a3f | ||
|   | b1ff92d63e | ||
|   | 016864edfc | ||
|   | dc5133e676 | ||
|   | 1fbd10c918 | ||
|   | 4cd219ab32 | ||
|   | 871e84fec9 | ||
|   | 08ce132587 | ||
|   | 4c58d8df72 | ||
|   | bf554d2417 | ||
|   | 631cd8e35f | ||
|   | f46c4d8054 | ||
|   | 0010713b78 | ||
|   | f5bfd4f8f2 | ||
|   | 6d04d2f579 | ||
|   | 0d3cef1626 | ||
|   | 4d251becb0 | ||
|   | 372d4c4bf3 | ||
|   | 2dbda89973 | ||
|   | 2f55bbddd8 | ||
|   | 928773b183 | ||
|   | bc9803cbc4 | ||
|   | fca8f5efbc | ||
|   | f09fe4f7e2 | ||
|   | de3bc0ff6c | ||
|   | c4a1b6a82e | ||
|   | bd08dd54e2 | ||
|   | b1f93fdb75 | ||
|   | 51599b3f1a | ||
|   | 1b753877a9 | ||
|   | d33ffa9d22 | ||
|   | df710e523a | ||
|   | 7d218e0706 | ||
|   | 88127a4858 | ||
|   | 63f2a6f902 | ||
|   | d8286eb639 | ||
|   | 668fa46a98 | ||
|   | e16a671a26 | ||
|   | 7f89ef358c | ||
|   | e0df319279 | ||
|   | 61c7a260b4 | ||
|   | f26fce63ad | ||
|   | 74c3c96b43 | ||
|   | 765b020152 | ||
|   | 59c05235f6 | ||
|   | 92a4131c88 | ||
|   | 540e14bbe7 | ||
|   | 0ebd355edd | ||
|   | 68d4dfed04 | ||
|   | 37a482d2e4 | ||
|   | b79cc65728 | ||
|   | 78977c7e31 | ||
|   | 1957fc5345 | ||
|   | 49cefff75d | ||
|   | 2ebb145b7b | ||
|   | 0a6f1394e4 | ||
|   | 1310913281 | ||
|   | c245c5ade3 | ||
|   | 9e6f45a59b | ||
|   | f6a78c8890 | ||
|   | 76717fa3cb | ||
|   | a19230393d | ||
|   | 0b89969a33 | ||
|   | bbe97230d2 | ||
|   | ee0161737c | ||
|   | e7e0b91703 | ||
|   | dbcdf71bbe | ||
|   | 779615c09a | ||
|   | 1c3a1ce53c | ||
|   | 981c524e3f | ||
|   | 44a7d68f31 | ||
|   | ae858e3dc4 | ||
|   | 22fd23a31d | ||
|   | 2babe87d6d | ||
|   | 95a8b3fe34 | ||
|   | 32c526a26a | ||
|   | b2adfad2ee | ||
|   | ee5a8534a7 | ||
|   | 80ba3b969d | ||
|   | 83bd6911c8 | ||
|   | 6c66f6199c | ||
|   | c11731541c | ||
|   | 609593ebd0 | ||
|   | 85b867a5ca | ||
|   | a3690051eb | ||
|   | ecb44a2611 | ||
|   | 7898ea9ba7 | ||
|   | 90a2029f3d | ||
|   | 1d9a9ea658 | ||
|   | e88f80eda3 | ||
|   | 025e2f415d | ||
|   | e113ca79de | ||
|   | f988c44440 | ||
|   | 37cda26e27 | ||
|   | 65c2da9afa | ||
|   | 434b7b756b | ||
|   | 9dfcab6c1c | ||
|   | 9f39f4e377 | ||
|   | 58da4284ac | ||
|   | 55787ff6cf | ||
|   | 3b93610e43 | ||
|   | 40f3066ee0 | ||
|   | 2108aac41c | ||
|   | 68f8300395 | ||
|   | 325a61912e | ||
|   | 7363c88fce | ||
|   | 0b0f95c65c | ||
|   | 354675f1e5 | ||
|   | 7a769f283b | ||
|   | 3dc67d5978 | ||
|   | d3a0d71857 | ||
|   | 72f7534a5f | ||
|   | 27a733cbed | ||
|   | 92a0ce98bb | ||
|   | 84c9632508 | ||
|   | 22c2b406db | ||
|   | bc374a1317 | ||
|   | 0bf82a999a | ||
|   | 453a8459ac | ||
|   | 53f6ddad22 | ||
|   | 14bd128931 | ||
|   | b0b8daa03d | ||
|   | 940cd70f64 | ||
|   | d8cdef0622 | ||
|   | 44a7cfe32e | ||
|   | 8584352e21 | ||
|   | 1c39d027e0 | ||
|   | ce82e2cdcf | ||
|   | a8492f75cd | ||
|   | 03afb38aa3 | ||
|   | acc2acce2c | ||
|   | 3a612864f8 | ||
|   | 7e1789a3f4 | ||
|   | 69dea794e1 | ||
|   | 4e024bd8b1 | ||
|   | 6078e511aa | ||
|   | 58574cd824 | ||
|   | 635fa99cf2 | ||
|   | 074d71cef7 | ||
|   | 027075b1b2 | ||
|   | 898ad42f96 | ||
|   | c87ffeff71 | ||
|   | 40f0744adc | ||
|   | eba555f260 | ||
|   | e7db208a40 | ||
|   | 69399bb328 | ||
|   | 749e928d66 | ||
|   | a3b8690609 | ||
|   | 340e6794ca | ||
|   | 7a23152cee | ||
|   | d00f71884a | ||
|   | 587014159d | ||
|   | e2cb3691e4 | ||
|   | a40be3b658 | ||
|   | 598d7c699a | ||
|   | 23c9b9f2d9 | ||
|   | 22aeff708a | ||
|   | e9b7b935ee | ||
|   | eb0d9ec482 | ||
|   | cfd0d72100 | ||
|   | f615dc265a | ||
|   | 53c4971c4e | ||
|   | 3a98fa9f53 | ||
|   | 6ed5386c12 | ||
|   | a998a7f92b | ||
|   | 3218989b13 | ||
|   | 3aff954063 | ||
|   | 11e824c133 | ||
|   | a26662a813 | ||
|   | 007838197b | ||
|   | 0ad82d5b91 | ||
|   | 52e47a62d2 | ||
|   | cbaaf3ee54 | ||
|   | 26f26ee35d | ||
|   | 0102ac77e3 | ||
|   | d24101bee2 | ||
|   | 36cced5ce7 | ||
|   | 87eb537ebe | ||
|   | 86fdc17d2a | ||
|   | bd8763c62f | ||
|   | bc917d41ad | ||
|   | 68b60b427a | ||
|   | c9e64c53ec | ||
|   | 6ba6c6bac7 | ||
|   | f2967e1d8f | ||
|   | 60783ca88e | ||
|   | 8d7c1371df | ||
|   | 090bfe9091 | ||
|   | 3ad2ba9f33 | ||
|   | 1036def599 | ||
|   | 2acad69940 | ||
|   | 1f18011a5b | ||
|   | 3da63b378c | ||
|   | 0852aa7429 | ||
|   | 8a863ca636 | ||
|   | 83db5c205d | ||
|   | 760e73959c | ||
|   | e516e06a53 | ||
|   | a954461be3 | ||
|   | 5998369af6 | ||
|   | f41a2604c4 | ||
|   | 6fc578d286 | ||
|   | f2604a5fec | ||
|   | 642026156a | ||
|   | 708c511a98 | ||
|   | 2c75b5886f | ||
|   | 0ef7f5a955 | ||
|   | 28856c01cd | ||
|   | a47bd03e94 | ||
|   | c5551ddeb5 | ||
|   | 020f274ef3 | ||
|   | e2f8f04ace | ||
|   | 6d61d885c1 | ||
|   | e98cd16032 | ||
|   | 4ec8bdda2f | ||
|   | 16df1624f8 | ||
|   | cb978e012b | ||
|   | b7ea02696b | ||
|   | 49cb8874ca | ||
|   | 1384256d87 | ||
|   | 705b2fcd9a | ||
|   | 906ca77973 | ||
|   | 7dc5c734d0 | ||
|   | a3c1e311f7 | ||
|   | 4d246a250d | ||
|   | 8d2c41f558 | ||
|   | 3b06c4c14a | ||
|   | f98dcb813d | ||
|   | 17cf31e8cf | ||
|   | 6771c378bf | ||
|   | 013aa4d071 | ||
|   | b887e16628 | ||
|   | 7774ed65bd | ||
|   | c85f996aa3 | ||
|   | 64d9cc00d1 | ||
|   | a0efbecee4 | ||
|   | cceb4c6677 | ||
|   | 66e448a648 | ||
|   | eb134fd41a | ||
|   | 857d6f9008 | ||
|   | 32396c4534 | ||
|   | 2df46b6647 | ||
|   | 9f6ad37547 | ||
|   | 65955843e5 | ||
|   | 9e9b5fb9dd | ||
|   | f96fc67bc2 | ||
|   | 07ebd0b4ca | ||
|   | 64e955c512 | ||
|   | 161753b195 | ||
|   | f92adb847a | ||
|   | 98f780c277 | ||
|   | 3c512e74db | ||
|   | 9b752dd28f | ||
|   | 3cf2719c29 | ||
|   | be420c0394 | ||
|   | 1b1d995b9d | ||
|   | 6410f41fa2 | ||
|   | f90a45b1e6 | ||
|   | 9ca5569122 | ||
|   | a6b4a35973 | ||
|   | 06cb6bac3e | ||
|   | 5241aeea9f | ||
|   | 6b69ad20ad | ||
|   | a79d2ec323 | ||
|   | 209a0fe2da | ||
|   | 277f439f70 | ||
|   | d9f29fdcaa | ||
|   | a9141d3fe2 | ||
|   | 4ce55e88fa | ||
|   | c930d388dc | ||
|   | 12c7911cf4 | ||
|   | 06377ff497 | ||
|   | 7a1938e515 | ||
|   | 445166d220 | ||
|   | 3963501d0c | ||
|   | acb51d7859 | ||
|   | 4dbfb76ced | ||
|   | 4188464652 | ||
|   | fb552a8d62 | ||
|   | c998e4e34f | ||
|   | ab46015a5a | ||
|   | dd10740bf2 | ||
|   | 44f26685e4 | ||
|   | 3caa56ae8c | ||
|   | 1ca4fbb078 | ||
|   | bdf5270fa0 | ||
|   | e3e865b108 | ||
|   | 33f3736798 | ||
|   | a86fbcee44 | ||
|   | cdbf8a2811 | ||
|   | da4fae4e04 | ||
|   | ddeefc4ab6 | ||
|   | 33702064a5 | ||
|   | b72995c8bf | ||
|   | 868eb1c4fb | ||
|   | d93140efc6 | ||
|   | 0f59d46ede | ||
|   | 812b5f22f9 | ||
|   | d1b5feae0d | ||
|   | 0861fa9e03 | ||
|   | c3fe9f513d | ||
|   | b513669f4d | ||
|   | 1ad74fdacc | ||
|   | 9a24ede989 | ||
|   | 7ea8dfa8ce | ||
|   | 670068eeba | ||
|   | 70310f1970 | ||
|   | 4463c148a5 | ||
|   | c982b45134 | ||
|   | d209a2c7f8 | ||
|   | 75c73193a2 | ||
|   | fc99f6774d | ||
|   | d40fab86c9 | ||
|   | 5c0520e093 | ||
|   | 1bb9ead202 | ||
|   | 3afd8915f0 | ||
|   | ba3f2d2823 | ||
|   | 24989d80ac | ||
|   | d2c16158cc | ||
|   | 8514fd6266 | ||
|   | 30d1534c01 | ||
|   | 4f0800d4fc | ||
|   | 41fa396318 | ||
|   | d94af84002 | ||
|   | 63b92cc35a | ||
|   | 3238d9fbc5 | ||
|   | 780d4c95fe | ||
|   | c3b27c75bf | ||
|   | 23c7031672 | ||
|   | 9e98d09a93 | ||
|   | 06e64f7347 | ||
|   | 92594db78d | ||
|   | c574f2d6bd | ||
|   | ee156cbfd6 | ||
|   | 73d12daf04 | ||
|   | e4179a934b | ||
|   | afd309d9a8 | ||
|   | 1a24b00c5e | ||
|   | 445e4d8b31 | ||
|   | 224ca98d0e | ||
|   | 13419c9fb7 | ||
|   | 00646bc84b | ||
|   | cb4b686335 | ||
|   | 5e900b62c8 | ||
|   | 74c94d37e5 | ||
|   | 588814d1a6 | ||
|   | 572b858f81 | ||
|   | 1a9fc48490 | ||
|   | 2dd131e6c3 | ||
|   | d1ad2a681f | ||
|   | e2e7666c4f | ||
|   | 665639fdca | ||
|   | 8bb930c1fc | ||
|   | b3b9d8d105 | ||
|   | 82c409252b | ||
|   | 14c63a03c4 | ||
|   | 0c7ad8b16c | ||
|   | 1bf2d8219a | ||
|   | a72264ef36 | ||
|   | 1acc399568 | ||
|   | 160960437c | ||
|   | 61992adcca | ||
|   | 10e0ae8200 | ||
|   | eb58b8576e | ||
|   | 9b4d2d6ed7 | ||
|   | 6417e9dec5 | ||
|   | f5ac217f13 | ||
|   | a3d3b14dd1 | ||
|   | 8fccce8684 | ||
|   | 4a847cd74a | ||
|   | f4c1db39ad | ||
|   | d9ff177427 | ||
|   | 95e3566c5f | ||
|   | 76e02ff514 | ||
|   | 8aca9a2d82 | ||
|   | 3abab2072a | ||
|   | ca9e479dc5 | ||
|   | 4d230a7b3e | ||
|   | 974cc59e6c | ||
|   | 9f09b144f0 | ||
|   | 9d4dad5fee | ||
|   | 46ffa93d93 | ||
|   | 8b709bc070 | ||
|   | c1284c760a | ||
|   | 6529a94d3a | ||
|   | f5e91a0524 | ||
|   | f63bd3016b | ||
|   | 09cd590796 | ||
|   | da728c4039 | ||
|   | 14b7b8cec1 | ||
|   | 3695c04933 | ||
|   | f0f3bb6ede | ||
|   | 7824326c5b | ||
|   | a65d263ae2 | ||
|   | f3e628be6b | ||
|   | 62e8dc7767 | ||
|   | 8e8cfa704a | ||
|   | 8b2eff0194 | ||
|   | d4d063b97a | ||
|   | d5ec33e1bd | ||
|   | 0726f22ec7 | ||
|   | 531ab93388 | ||
|   | a7ad00d402 | ||
|   | 1f18848b4e | ||
|   | 38179d9ce3 | ||
|   | b86c493cf8 | ||
|   | 0cc2df85da | ||
|   | 3157bac5f6 | ||
|   | 5cb1709f5d | ||
|   | e6834b13a5 | ||
|   | 8a17daa002 | ||
|   | 1b09189195 | ||
|   | 7f21fa2c40 | ||
|   | e0e4fc85f7 | ||
|   | b3e57db9f8 | ||
|   | f8d93a5de7 | ||
|   | c5423ac650 | ||
|   | 6e93d17d14 | ||
|   | 127bc1be5c | ||
|   | 8136b09992 | ||
|   | 2ffac3268e | ||
|   | c7056e9d40 | ||
|   | 3fb8b4e7ff | ||
|   | c728a7802d | ||
|   | e7d3049456 | ||
|   | 316675c77e | ||
|   | c836d3de71 | ||
|   | 230429135a | ||
|   | 0be8092382 | ||
|   | eb2019e568 | ||
|   | bffbb17271 | ||
|   | 16b997096d | ||
|   | 3b23f2a4ed | ||
|   | 87f86fd2e8 | ||
|   | 396509579a | ||
|   | 91175f19c4 | ||
|   | 7c22ca0f91 | ||
|   | 5eb416d8d9 | ||
|   | 12e374bd2f | ||
|   | 9b50f0e77c | ||
|   | 6aa89287b1 | ||
|   | 4b9ebdc87c | ||
|   | 336b45667e | ||
|   | 3c33a1126b | ||
|   | 836cdfb16c | ||
|   | 8512bae997 | ||
|   | 6fbe8c5dee | ||
|   | 2132ea416b | ||
|   | 24081b9223 | ||
|   | 220cc98559 | ||
|   | ef8b9b0685 | ||
|   | e8dd47369c | ||
|   | 537eaddecd | ||
|   | 183c171da6 | ||
|   | 58c6114840 | ||
|   | df5162ce56 | ||
|   | ae61fe892d | ||
|   | a78de3b4b9 | ||
|   | f26a864e83 | ||
|   | 0e0b03e6f2 | ||
|   | e5595f13eb | ||
|   | 1b12c16685 | ||
|   | 048055daac | ||
|   | f2590f1fed | ||
|   | 2eb7b22100 | ||
|   | f169750d1a | ||
|   | fbb361fa7a | ||
|   | 902faca13c | ||
|   | 7e476a5785 | ||
|   | c78c5aad02 | ||
|   | 42c49645da | ||
|   | 9427fb9ecc | ||
|   | 61878f293f | ||
|   | 2bd1727076 | ||
|   | f0fbf7bf8b | ||
|   | 4a1fccc395 | ||
|   | 54c0497933 | ||
|   | 49a99e9f15 | ||
|   | 134fd5b2c2 | ||
|   | 696f9b3550 | ||
|   | aeda42cb71 | ||
|   | 7ba8400c38 | ||
|   | 02f10f07e8 | ||
|   | 83cc32afaa | ||
|   | 5b8382643a | ||
|   | 003cb8719a | ||
|   | c625ebc128 | ||
|   | 89c0b02327 | ||
|   | 94bb17a301 | ||
|   | 4f29b921e5 | ||
|   | 8d5dbb0f3d | ||
|   | db321e45da | ||
|   | 8e254a341c | ||
|   | 847a52ab08 | ||
|   | 94e69e4e84 | ||
|   | 8834526cac | ||
|   | 788365687e | ||
|   | 4c3d328140 | ||
|   | 2588f74f7c | ||
|   | b70a4ef428 | ||
|   | 026a903251 | ||
|   | 3557113ed8 | ||
|   | acd8c27a13 | ||
|   | 3c186eb113 | ||
|   | 841eb34224 | ||
|   | ef583ba7ec | ||
|   | fa0e06df75 | ||
|   | c26ee7271b | ||
|   | f37c0a9124 | ||
|   | 80fbd656a7 | ||
|   | 3fb5749c86 | ||
|   | 1692130559 | ||
|   | cf77a9eae2 | ||
|   | 26151af48a | ||
|   | c3b858f0fd | ||
|   | 914e788ad8 | ||
|   | 074178621c | ||
|   | 25d1b6b695 | ||
|   | f121c73c4b | ||
|   | 3f79e7677c | ||
|   | ca573f27be | ||
|   | 056a3342bf | ||
|   | eaa661cf09 | ||
|   | b2d791d6ea | ||
|   | f1ea1935c5 | ||
|   | d9c101debe | ||
|   | e3a720a69d | ||
|   | 4f3f18a0ad | ||
|   | 10a3a8324b | ||
|   | a2dab72b25 | ||
|   | ca0a381d8c | ||
|   | a503e85a57 | ||
|   | 0d668561b4 | ||
|   | c8d39a2c15 | ||
|   | 6146ac90c1 | ||
|   | 2bb00c948d | ||
|   | 01eadd986c | ||
|   | b482ea4ecc | ||
|   | 19bb98c857 | ||
|   | 225afb85e4 | ||
|   | 2873ffdff7 | ||
|   | 52ba506138 | ||
|   | 798f70a706 | ||
|   | d731d55a7a | ||
|   | 1bfcea0227 | ||
|   | 71a307bfef | ||
|   | 7a16a8eaff | ||
|   | c1c2ccd940 | ||
|   | 8a993b7dcb | ||
|   | 977393a9aa | ||
|   | 4f36de9900 | ||
|   | ef658eb4cd | ||
|   | 6f77577482 | ||
|   | bfdb1a8f62 | ||
|   | 0935d70ae4 | ||
|   | 24e4e2b960 | ||
|   | e608d7463e | ||
|   | 47453f1471 | ||
|   | 362780f59a | ||
|   | a2a511b3c0 | ||
|   | 78cde8ab62 | ||
|   | 46b779c9d5 | ||
|   | 23129bf95a | ||
|   | fba137bb3f | ||
|   | e50a73b4bf | ||
|   | 31afe11d0e | ||
|   | 5dc1d047ac | ||
|   | 19e0cf406c | ||
|   | 0bc1eece0a | ||
|   | f2e93e915b | ||
|   | 8db6a99194 | ||
|   | 1490ebce03 | ||
|   | cd189e01dc | ||
|   | 15467248ee | ||
|   | 23008ab5b8 | ||
|   | 041fbb10a1 | ||
|   | 9ba0e59df5 | ||
|   | 0bb9f57919 | ||
|   | 106da869c1 | ||
|   | d34a3352f2 | ||
|   | 4b5c922f11 | ||
|   | aacb415364 | ||
|   | bdc8bcfe5c | ||
|   | 154238edf0 | ||
|   | eba789c200 | ||
|   | 63cb67a03c | ||
|   | 1c299c6d5d | ||
|   | 486c5310d7 | ||
|   | 5ef4a97d5f | ||
|   | 03adb8f730 | ||
|   | abdcd0b3f0 | ||
|   | 6e225808a9 | ||
|   | 382c96f587 | ||
|   | 80cb0119c7 | ||
|   | e7bc5072c0 | ||
|   | a65057679c | ||
|   | 025186d22f | ||
|   | e17217206f | ||
|   | c64ca44457 | ||
|   | c6e9a16274 | ||
|   | f945380a87 | ||
|   | bb9664eafc | ||
|   | eaca802ecc | ||
|   | 2e6ca574b6 | ||
|   | d4c61ce932 | ||
|   | 7387ebe182 | ||
|   | d6f305cfa6 | ||
|   | 73b9fbb435 | ||
|   | 51c2f313d4 | ||
|   | 0a390107a3 | ||
|   | 077bf997c6 | ||
|   | 48cb473ecb | ||
|   | 40dc542302 | ||
|   | 28bb4894b2 | ||
|   | 774d17dd25 | ||
|   | 790637f1bd | ||
|   | 99fb5d3b8e | ||
|   | 6676e62579 | ||
|   | d735992f5c | ||
|   | 8a32cfc185 | ||
|   | 285fafc300 | ||
|   | 0c90433a34 | ||
|   | d6070368b7 | ||
|   | b42389500f | ||
|   | daca972fc2 | ||
|   | 8447e59d1f | ||
|   | 07ff8cd662 | ||
|   | dbc734eee5 | ||
|   | eaa95fdba4 | ||
|   | 3ba1169e9c | ||
|   | 0117b53ea2 | ||
|   | a2ec04b641 | ||
|   | 8fdcef9c42 | ||
|   | d311de764d | ||
|   | e9b16b2f70 | ||
|   | 21a81b977b | ||
|   | 9ce1c2b075 | ||
|   | b974cbd14c | ||
|   | 1d96282fd9 | ||
|   | 48efda6bd9 | ||
|   | aba3e0033f | ||
|   | 5bf61a63cc | ||
|   | 48095c0433 | ||
|   | 3e1599119a | ||
|   | f488e8a7ab | ||
|   | b5c17bf7bc | ||
|   | 77e994e9fd | ||
|   | bbd3fc4d1d | ||
|   | 2a57b94e08 | ||
|   | 366a37b765 | ||
|   | a3806f7a81 | ||
|   | cf28e373f7 | ||
|   | 8d80295e07 | ||
|   | 1f7ffe6ea0 | ||
|   | a740753175 | ||
|   | c70f996915 | ||
|   | 707dfc2a0b | ||
|   | 77db3362fc | ||
|   | c8e0687288 | ||
|   | f61a6191ec | ||
|   | 2648236bb4 | ||
|   | 382e23f545 | ||
|   | 2eddbe3669 | ||
|   | 5e41517178 | ||
|   | 177c709e0a | ||
|   | 699eb824a1 | ||
|   | 6b19e305f3 | ||
|   | 8b1b4044b3 | 
| @@ -4,3 +4,7 @@ files: | |||||||
|       /src/keepass2android/Resources/values-%two_letters_code%/%original_file_name% |       /src/keepass2android/Resources/values-%two_letters_code%/%original_file_name% | ||||||
|     translate_attributes: '0' |     translate_attributes: '0' | ||||||
|     content_segmentation: '0' |     content_segmentation: '0' | ||||||
|  |     languages_mapping: | ||||||
|  |       two_letters_code: | ||||||
|  |         zh-CN: zh | ||||||
|  |         zh-TW: zh-rTW | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ Keepass2Android's apk is pretty big, e.g. when comparing to Keepassdroid. The ma | |||||||
|  |  | ||||||
| Here's a list of what is contained in the Keepass2Android 0.9.1 application package: | Here's a list of what is contained in the Keepass2Android 0.9.1 application package: | ||||||
|  |  | ||||||
| {{ | ``` | ||||||
| Mono for Android		 | Mono for Android		 | ||||||
| 			.net dlls			5.0 MB | 			.net dlls			5.0 MB | ||||||
| 			Runtime				2.5 MB				 | 			Runtime				2.5 MB				 | ||||||
| @@ -22,4 +22,4 @@ Java/Mono bindings					0.5 MB | |||||||
| rest							0.3 MB		 | rest							0.3 MB		 | ||||||
|  |  | ||||||
| TOTAL							13 MB | TOTAL							13 MB | ||||||
| }} | ``` | ||||||
|   | |||||||
| @@ -1,3 +1,6 @@ | |||||||
|  | <h1 align="center"><img src="/src/keepass2android/Resources/mipmap-xxxhdpi/ic_launcher_online.png" align="center" width="100" alt="Keepass2Android Logo">Keepass2Android</h1> | ||||||
|  |  | ||||||
|  |  | ||||||
| # What is Keepass2Android? | # What is Keepass2Android? | ||||||
| Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database". This database is secured with a so-called master password. The master password typically is a strong password and can be complemented with a second factor for additional security. | Keepass2Android is a password manager app. It allows to store and retrieve passwords and other sensitive information in a file called "database". This database is secured with a so-called master password. The master password typically is a strong password and can be complemented with a second factor for additional security. | ||||||
| The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with Keepass 1 and Keepass 2 on Windows and KeepassX on Linux. | The password database file can be synchronized across different devices. This works best using one of the built-in cloud storage options, but can also be performed with third-party apps. Keepass2Android is compatible with Keepass 1 and Keepass 2 on Windows and KeepassX on Linux. | ||||||
|   | |||||||
| @@ -81,10 +81,6 @@ | |||||||
|     </XamarinComponentReference> |     </XamarinComponentReference> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <ProjectReference Include="..\AdalBindings\AdalBindings.csproj"> |  | ||||||
|       <Project>{0b109c0e-0514-4340-8779-5bd6a0dde84e}</Project> |  | ||||||
|       <Name>AdalBindings</Name> |  | ||||||
|     </ProjectReference> |  | ||||||
|     <ProjectReference Include="..\PCloudBindings\PCloudBindings.csproj" /> |     <ProjectReference Include="..\PCloudBindings\PCloudBindings.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|   | |||||||
| @@ -29,8 +29,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamsungPass", "SamsungPass\ | |||||||
| EndProject | EndProject | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCloudBindings", "PCloudBindings\PCloudBindings.csproj", "{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}" | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCloudBindings", "PCloudBindings\PCloudBindings.csproj", "{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}" | ||||||
| EndProject | EndProject | ||||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdalBindings", "AdalBindings\AdalBindings.csproj", "{0B109C0E-0514-4340-8779-5BD6A0DDE84E}" |  | ||||||
| EndProject |  | ||||||
| Global | Global | ||||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||||
| 		Debug|Any CPU = Debug|Any CPU | 		Debug|Any CPU = Debug|Any CPU | ||||||
| @@ -317,30 +315,6 @@ Global | |||||||
| 		{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|Win32.Build.0 = ReleaseNoNet|Any CPU | 		{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|Win32.Build.0 = ReleaseNoNet|Any CPU | ||||||
| 		{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU | 		{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU | ||||||
| 		{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|x64.Build.0 = ReleaseNoNet|Any CPU | 		{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|x64.Build.0 = ReleaseNoNet|Any CPU | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|Any CPU.Build.0 = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|Win32.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|Win32.Build.0 = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|x64.ActiveCfg = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Debug|x64.Build.0 = Debug|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|Mixed Platforms.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|Win32.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|Win32.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|x64.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.Release|x64.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU |  | ||||||
| 		{0B109C0E-0514-4340-8779-5BD6A0DDE84E}.ReleaseNoNet|x64.Build.0 = Release|Any CPU |  | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
| 	GlobalSection(SolutionProperties) = preSolution | 	GlobalSection(SolutionProperties) = preSolution | ||||||
| 		HideSolutionNode = FALSE | 		HideSolutionNode = FALSE | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ using System; | |||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Text; | using System.Text; | ||||||
|  | using keepass2android; | ||||||
| #if KeePassUAP | #if KeePassUAP | ||||||
| using Org.BouncyCastle.Crypto; | using Org.BouncyCastle.Crypto; | ||||||
| using Org.BouncyCastle.Crypto.Engines; | using Org.BouncyCastle.Crypto.Engines; | ||||||
| @@ -144,6 +144,7 @@ namespace KeePassLib.Cryptography.KeyDerivation | |||||||
| 		public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, | 		public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, | ||||||
| 			ulong uNumRounds) | 			ulong uNumRounds) | ||||||
| 		{ | 		{ | ||||||
|  | 		    Kp2aLog.Log("Warning: transforming key managed. Expect this to be slow!"); | ||||||
| #if KeePassUAP | #if KeePassUAP | ||||||
| 			KeyParameter kp = new KeyParameter(pbKeySeed32); | 			KeyParameter kp = new KeyParameter(pbKeySeed32); | ||||||
| 			AesEngine aes = new AesEngine(); | 			AesEngine aes = new AesEngine(); | ||||||
|   | |||||||
| @@ -38,9 +38,11 @@ namespace KeePassLib.Keys | |||||||
| 			get; | 			get; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// /// <summary> |         // /// <summary> | ||||||
| 		// /// Clear the key and securely erase all security-critical information. |         // /// Clear the key and securely erase all security-critical information. | ||||||
| 		// /// </summary> |         // /// </summary> | ||||||
| 		// void Clear(); |         // void Clear(); | ||||||
|  |  | ||||||
|  |         uint GetMinKdbxVersion(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -45,7 +45,12 @@ namespace KeePassLib.Keys | |||||||
| 			get { return m_pbKey; } | 			get { return m_pbKey; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public KcpCustomKey(string strName, byte[] pbKeyData, bool bPerformHash) | 	    public uint GetMinKdbxVersion() | ||||||
|  | 	    { | ||||||
|  | 	        return 0; | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    public KcpCustomKey(string strName, byte[] pbKeyData, bool bPerformHash) | ||||||
| 		{ | 		{ | ||||||
| 			Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); | 			Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); | ||||||
| 			Debug.Assert(pbKeyData != null); if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); | 			Debug.Assert(pbKeyData != null); if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); | ||||||
|   | |||||||
| @@ -64,7 +64,12 @@ namespace KeePassLib.Keys | |||||||
| 			get { return m_pbKeyData; } | 			get { return m_pbKeyData; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public IOConnectionInfo Ioc | 	    public uint GetMinKdbxVersion() | ||||||
|  | 	    { | ||||||
|  | 	        return 0; | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    public IOConnectionInfo Ioc | ||||||
| 		{ | 		{ | ||||||
| 			get { return m_ioc; } | 			get { return m_ioc; } | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -53,7 +53,12 @@ namespace KeePassLib.Keys | |||||||
| 			get { return m_pbKeyData; } | 			get { return m_pbKeyData; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public KcpPassword(byte[] pbPasswordUtf8) | 	    public uint GetMinKdbxVersion() | ||||||
|  | 	    { | ||||||
|  | 	        return 0; | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    public KcpPassword(byte[] pbPasswordUtf8) | ||||||
| 		{ | 		{ | ||||||
| 			SetKey(pbPasswordUtf8); | 			SetKey(pbPasswordUtf8); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -60,7 +60,12 @@ namespace KeePassLib.Keys | |||||||
| 			get { return m_pbKeyData; } | 			get { return m_pbKeyData; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// <summary> | 	    public uint GetMinKdbxVersion() | ||||||
|  | 	    { | ||||||
|  | 	        return 0; | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    /// <summary> | ||||||
| 		/// Construct a user account key. | 		/// Construct a user account key. | ||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		public KcpUserAccount() | 		public KcpUserAccount() | ||||||
|   | |||||||
| @@ -85,8 +85,8 @@ namespace keepass2android | |||||||
| 		{ | 		{ | ||||||
| 			if (!File.Exists(LogFilename)) | 			if (!File.Exists(LogFilename)) | ||||||
| 			{ | 			{ | ||||||
| 				File.Create(LogFilename); | 				File.Create(LogFilename).Dispose(); | ||||||
| 				_logToFile = true; |                 _logToFile = true; | ||||||
| 			} | 			} | ||||||
| 			 | 			 | ||||||
|  |  | ||||||
| @@ -100,8 +100,7 @@ namespace keepass2android | |||||||
| 				int count = 0; | 				int count = 0; | ||||||
| 				while (File.Exists(LogFilename + "." + count)) | 				while (File.Exists(LogFilename + "." + count)) | ||||||
| 					count++; | 					count++; | ||||||
|                 if (count > 0) |                 File.Move(LogFilename, LogFilename + "." + count); | ||||||
| 				    File.Move(LogFilename, LogFilename + "." + count); |  | ||||||
| 				 | 				 | ||||||
| 			} | 			} | ||||||
| 				 | 				 | ||||||
|   | |||||||
| @@ -302,7 +302,7 @@ namespace KeePassLib.Native | |||||||
| 			} | 			} | ||||||
| 			catch (Exception e) | 			catch (Exception e) | ||||||
| 			{ | 			{ | ||||||
| 				Kp2aLog.Log(e.Message); | 				Kp2aLog.Log(e.ToString()); | ||||||
| 				return false; | 				return false; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -485,12 +485,6 @@ namespace KeePassLib | |||||||
| 			set { m_pbHashOfLastIO = value; } | 			set { m_pbHashOfLastIO = value; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public bool UseFileTransactions |  | ||||||
| 		{ |  | ||||||
| 			get { return m_bUseFileTransactions; } |  | ||||||
| 			set { m_bUseFileTransactions = value; } |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		public bool UseFileLocks | 		public bool UseFileLocks | ||||||
| 		{ | 		{ | ||||||
| 			get { return m_bUseFileLocks; } | 			get { return m_bUseFileLocks; } | ||||||
|   | |||||||
| @@ -360,5 +360,12 @@ namespace KeePassLib.Serialization | |||||||
| 				m_ioCredProtMode = IOCredProtMode.None; | 				m_ioCredProtMode = IOCredProtMode.None; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 	    public bool IsSameFileAs(IOConnectionInfo other) | ||||||
|  | 	    { | ||||||
|  | 	        if (other == null) | ||||||
|  | 	            return false; | ||||||
|  | 	        return Path == other.Path && UserName == other.UserName; | ||||||
|  | 	    } | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ using System.Diagnostics; | |||||||
| using System.Drawing; | using System.Drawing; | ||||||
| using System.Globalization; | using System.Globalization; | ||||||
| using System.IO; | using System.IO; | ||||||
|  | using System.Linq; | ||||||
| using System.Security; | using System.Security; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Xml; | using System.Xml; | ||||||
| @@ -126,8 +127,8 @@ namespace KeePassLib.Serialization | |||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		private const uint FileVersion32 = 0x00040000; | 		private const uint FileVersion32 = 0x00040000; | ||||||
|  |  | ||||||
| 		internal const uint FileVersion32_4 = 0x00040000; // First of 4.x series | 		public const uint FileVersion32_4 = 0x00040000; // First of 4.x series | ||||||
| 		internal const uint FileVersion32_3 = 0x00030001; // Old format 3.1 | 		public const uint FileVersion32_3 = 0x00030001; // Old format 3.1 | ||||||
|  |  | ||||||
| 		private const uint FileVersionCriticalMask = 0xFFFF0000; | 		private const uint FileVersionCriticalMask = 0xFFFF0000; | ||||||
|  |  | ||||||
| @@ -372,16 +373,19 @@ namespace KeePassLib.Serialization | |||||||
| 		{ | 		{ | ||||||
| 			if(m_uForceVersion != 0) return m_uForceVersion; | 			if(m_uForceVersion != 0) return m_uForceVersion; | ||||||
|  |  | ||||||
| 			// See also KeePassKdb2x3.Export (KDBX 3.1 export module) |             // See also KeePassKdb2x3.Export (KDBX 3.1 export module) | ||||||
|  | 		    uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max(); | ||||||
| 			AesKdf kdfAes = new AesKdf(); | 		     | ||||||
|  |             AesKdf kdfAes = new AesKdf(); | ||||||
| 			if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) | 			if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) | ||||||
| 				return FileVersion32; | 				return Math.Max(FileVersion32, minVersionForKeys); | ||||||
|  |  | ||||||
| 			if(m_pwDatabase.PublicCustomData.Count > 0) | 			if(m_pwDatabase.PublicCustomData.Count > 0) | ||||||
| 				return FileVersion32; | 			    return Math.Max(FileVersion32, minVersionForKeys); | ||||||
|  |  | ||||||
| 			bool bCustomData = false; |  | ||||||
|  |  | ||||||
|  |             bool bCustomData = false; | ||||||
| 			GroupHandler gh = delegate(PwGroup pg) | 			GroupHandler gh = delegate(PwGroup pg) | ||||||
| 			{ | 			{ | ||||||
| 				if(pg == null) { Debug.Assert(false); return true; } | 				if(pg == null) { Debug.Assert(false); return true; } | ||||||
| @@ -396,9 +400,10 @@ namespace KeePassLib.Serialization | |||||||
| 			}; | 			}; | ||||||
| 			gh(m_pwDatabase.RootGroup); | 			gh(m_pwDatabase.RootGroup); | ||||||
| 			m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); | 			m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); | ||||||
| 			if(bCustomData) return FileVersion32; | 			if(bCustomData) | ||||||
|  | 			    return Math.Max(FileVersion32, minVersionForKeys); | ||||||
|  |  | ||||||
| 			return FileVersion32_3; // KDBX 3.1 is sufficient |             return Math.Max(FileVersion32_3, minVersionForKeys); ; // KDBX 3.1 is sufficient | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, | 		private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								src/Kp2aBusinessLogic/ElementAndDatabaseId.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/Kp2aBusinessLogic/ElementAndDatabaseId.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
|  | using Android.App; | ||||||
|  | using Android.Content; | ||||||
|  | using Android.OS; | ||||||
|  | using Android.Runtime; | ||||||
|  | using Android.Views; | ||||||
|  | using Android.Widget; | ||||||
|  | using keepass2android.Io; | ||||||
|  | using KeePassLib; | ||||||
|  | using KeePassLib.Interfaces; | ||||||
|  | using KeePassLib.Utility; | ||||||
|  |  | ||||||
|  | namespace keepass2android | ||||||
|  | { | ||||||
|  |     public class ElementAndDatabaseId | ||||||
|  |     { | ||||||
|  |         private const char Separator = '+'; | ||||||
|  |  | ||||||
|  |         public ElementAndDatabaseId(Database db, IStructureItem element) | ||||||
|  |         { | ||||||
|  |             DatabaseId = db.IocAsHexString(); | ||||||
|  |             ElementIdString = element.Uuid.ToHexString(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public ElementAndDatabaseId(string fullId) | ||||||
|  |         { | ||||||
|  |             string[] parts = fullId.Split(Separator); | ||||||
|  |             if (parts.Length != 2) | ||||||
|  |                 throw new Exception("Invalid full id " + fullId); | ||||||
|  |             DatabaseId = parts[0]; | ||||||
|  |             ElementIdString = parts[1]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public string DatabaseId { get; set; } | ||||||
|  |         public string ElementIdString { get; set; } | ||||||
|  |         public PwUuid ElementId {  get {  return new PwUuid(MemUtil.HexStringToByteArray(ElementIdString));} } | ||||||
|  |  | ||||||
|  |         public string FullId | ||||||
|  |         { | ||||||
|  |             get { return DatabaseId + Separator + ElementIdString; } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
| using System.Net.Security; | using System.Net.Security; | ||||||
| using System.Security.Cryptography.X509Certificates; | using System.Security.Cryptography.X509Certificates; | ||||||
| using Android.App; | using Android.App; | ||||||
| @@ -9,6 +10,7 @@ using KeePassLib; | |||||||
| using KeePassLib.Keys; | using KeePassLib.Keys; | ||||||
| using KeePassLib.Serialization; | using KeePassLib.Serialization; | ||||||
| using keepass2android.Io; | using keepass2android.Io; | ||||||
|  | using KeePassLib.Interfaces; | ||||||
| #if !NoNet | #if !NoNet | ||||||
| using Keepass2android.Javafilestorage; | using Keepass2android.Javafilestorage; | ||||||
| #endif  | #endif  | ||||||
| @@ -33,31 +35,41 @@ namespace keepass2android | |||||||
| 	/// This also contains methods which are UI specific and should be replacable for testing. | 	/// This also contains methods which are UI specific and should be replacable for testing. | ||||||
| 	public interface IKp2aApp : ICertificateValidationHandler | 	public interface IKp2aApp : ICertificateValidationHandler | ||||||
| 	{ | 	{ | ||||||
| 		/// <summary> |         /// <summary> | ||||||
| 		/// Locks the currently open database, quicklocking if available (unless false is passed for allowQuickUnlock) |         /// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock) | ||||||
| 		/// </summary> |         /// </summary> | ||||||
| 		void LockDatabase(bool allowQuickUnlock = true); |         void Lock(bool allowQuickUnlock); | ||||||
|  |  | ||||||
| 		/// <summary> |  | ||||||
| 		/// Loads the specified data as the currently open database, as unlocked. |  | ||||||
| 		/// </summary> |  | ||||||
| 		void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, |  | ||||||
| 		                  ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat); |  | ||||||
|  |  | ||||||
| 		/// <summary> |         /// <summary> | ||||||
| 		/// Returns the current database |         /// Loads the specified data as the currently open database, as unlocked. | ||||||
| 		/// </summary> |         /// </summary> | ||||||
| 		Database GetDb(); |         Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent); | ||||||
|  |  | ||||||
| 		/// <summary> |  | ||||||
| 		/// Tell the app that the file from ioc was opened with keyfile. | 	    HashSet<PwGroup> DirtyGroups { get; } | ||||||
| 		/// </summary> |  | ||||||
| 		void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile, string displayName = ""); | 	    void MarkAllGroupsAsDirty(); | ||||||
|  |  | ||||||
|  |         /// <summary> | ||||||
|  |         /// Returns the current database | ||||||
|  |         /// </summary> | ||||||
|  | 	    Database CurrentDb { get; } | ||||||
|  |  | ||||||
|  | 	    IEnumerable<Database> OpenDatabases { get; } | ||||||
|  | 	    void CloseDatabase(Database db); | ||||||
|  |  | ||||||
|  | 	    Database FindDatabaseForElement(IStructureItem element); | ||||||
|  |          | ||||||
|  |         /// <summary> | ||||||
|  |         /// Tell the app that the file from ioc was opened with keyfile. | ||||||
|  |         /// </summary> | ||||||
|  |         void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile, bool updateTimestamp, string displayName = ""); | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Creates a new database and returns it | 		/// Creates a new database and returns it | ||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		Database CreateNewDatabase(); | 		Database CreateNewDatabase(bool makeCurrent); | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Returns the user-displayable string identified by stringKey | 		/// Returns the user-displayable string identified by stringKey | ||||||
| @@ -76,7 +88,8 @@ namespace keepass2android | |||||||
| 		                    EventHandler<DialogClickEventArgs> yesHandler, | 		                    EventHandler<DialogClickEventArgs> yesHandler, | ||||||
| 		                    EventHandler<DialogClickEventArgs> noHandler, | 		                    EventHandler<DialogClickEventArgs> noHandler, | ||||||
| 		                    EventHandler<DialogClickEventArgs> cancelHandler, | 		                    EventHandler<DialogClickEventArgs> cancelHandler, | ||||||
| 		                    Context ctx); | 		                    Context ctx, | ||||||
|  |                             string messageSuffix = ""); | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Asks the user the question "messageKey" with the options Yes/No/Cancel, but the yes/no strings can be selected freely, calls the handler corresponding to the answer. | 		/// Asks the user the question "messageKey" with the options Yes/No/Cancel, but the yes/no strings can be selected freely, calls the handler corresponding to the answer. | ||||||
| @@ -86,7 +99,8 @@ namespace keepass2android | |||||||
| 		                    EventHandler<DialogClickEventArgs> yesHandler, | 		                    EventHandler<DialogClickEventArgs> yesHandler, | ||||||
| 		                    EventHandler<DialogClickEventArgs> noHandler, | 		                    EventHandler<DialogClickEventArgs> noHandler, | ||||||
| 		                    EventHandler<DialogClickEventArgs> cancelHandler, | 		                    EventHandler<DialogClickEventArgs> cancelHandler, | ||||||
| 		                    Context ctx); | 		                    Context ctx, | ||||||
|  | 		                    string messageSuffix = ""); | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Returns a Handler object which can run tasks on the UI thread | 		/// Returns a Handler object which can run tasks on the UI thread | ||||||
| @@ -111,6 +125,10 @@ namespace keepass2android | |||||||
| 		bool CheckForDuplicateUuids { get; } | 		bool CheckForDuplicateUuids { get; } | ||||||
| #if !NoNet | #if !NoNet | ||||||
| 		ICertificateErrorHandler CertificateErrorHandler { get; } | 		ICertificateErrorHandler CertificateErrorHandler { get; } | ||||||
|  | 	     | ||||||
|  |  | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  | 	     | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -207,8 +207,7 @@ namespace keepass2android.Io | |||||||
|  |  | ||||||
| 		public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) | 		public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) | ||||||
| 		{ | 		{ | ||||||
| 			//TODO: required for OTP Aux file retrieval | 		    return IoUtil.GetParentPath(ioc); | ||||||
| 			throw new NotImplementedException(); |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) | 		public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) | ||||||
|   | |||||||
| @@ -255,7 +255,8 @@ namespace keepass2android.Io | |||||||
| 			if (ioc.IsLocalFile()) | 			if (ioc.IsLocalFile()) | ||||||
| 			{ | 			{ | ||||||
| 				bool requiresPermission = !(ioc.Path.StartsWith(activity.Activity.FilesDir.CanonicalPath) | 				bool requiresPermission = !(ioc.Path.StartsWith(activity.Activity.FilesDir.CanonicalPath) | ||||||
| 												|| ioc.Path.StartsWith(IoUtil.GetInternalDirectory(activity.Activity).CanonicalPath)); | 												|| ioc.Path.StartsWith(IoUtil.GetInternalDirectory(activity.Activity).CanonicalPath) | ||||||
|  | 				                            || ioc.Path.StartsWith(IoUtil.GetInternalDirectory(activity.Activity).CanonicalPath)); | ||||||
| 				 | 				 | ||||||
| 				var extDirectory = activity.Activity.GetExternalFilesDir(null); | 				var extDirectory = activity.Activity.GetExternalFilesDir(null); | ||||||
| 				if ((extDirectory != null) && (ioc.Path.StartsWith(extDirectory.CanonicalPath))) | 				if ((extDirectory != null) && (ioc.Path.StartsWith(extDirectory.CanonicalPath))) | ||||||
|   | |||||||
| @@ -65,22 +65,28 @@ namespace keepass2android.Io | |||||||
| 		 | 		 | ||||||
| 		protected readonly OfflineSwitchableFileStorage _cachedStorage; | 		protected readonly OfflineSwitchableFileStorage _cachedStorage; | ||||||
| 		private readonly ICacheSupervisor _cacheSupervisor; | 		private readonly ICacheSupervisor _cacheSupervisor; | ||||||
| 		private readonly string _streamCacheDir; | 		private readonly string _legacyCacheDir; | ||||||
|  | 	    private readonly string _cacheDir; | ||||||
|  |  | ||||||
| 		public CachingFileStorage(IFileStorage cachedStorage, string cacheDir, ICacheSupervisor cacheSupervisor) |         public CachingFileStorage(IFileStorage cachedStorage, Context cacheDirContext, ICacheSupervisor cacheSupervisor) | ||||||
| 		{ | 		{ | ||||||
| 			_cachedStorage = new OfflineSwitchableFileStorage(cachedStorage); | 			_cachedStorage = new OfflineSwitchableFileStorage(cachedStorage); | ||||||
| 			_cacheSupervisor = cacheSupervisor; | 			_cacheSupervisor = cacheSupervisor; | ||||||
| 			_streamCacheDir = cacheDir + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; | 			_legacyCacheDir = cacheDirContext.CacheDir.Path + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; | ||||||
| 			if (!Directory.Exists(_streamCacheDir)) | 			if (!Directory.Exists(_legacyCacheDir)) | ||||||
| 				Directory.CreateDirectory(_streamCacheDir); | 				Directory.CreateDirectory(_legacyCacheDir); | ||||||
| 			 |  | ||||||
| 		} | 		    _cacheDir = IoUtil.GetInternalDirectory(cacheDirContext).Path + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; | ||||||
|  | 		    if (!Directory.Exists(_cacheDir)) | ||||||
|  | 		        Directory.CreateDirectory(_cacheDir); | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
| 		public void ClearCache() | 		public void ClearCache() | ||||||
| 		{ | 		{ | ||||||
| 			IoUtil.DeleteDir(new Java.IO.File(_streamCacheDir), true); | 			IoUtil.DeleteDir(new Java.IO.File(_legacyCacheDir), true); | ||||||
| 		} | 		    IoUtil.DeleteDir(new Java.IO.File(_cacheDir), true); | ||||||
|  |         } | ||||||
|  |  | ||||||
| 		public IEnumerable<string> SupportedProtocols { get { return _cachedStorage.SupportedProtocols; } } | 		public IEnumerable<string> SupportedProtocols { get { return _cachedStorage.SupportedProtocols; } } | ||||||
|  |  | ||||||
| @@ -105,7 +111,11 @@ namespace keepass2android.Io | |||||||
| 		{ | 		{ | ||||||
| 			SHA256Managed sha256 = new SHA256Managed(); | 			SHA256Managed sha256 = new SHA256Managed(); | ||||||
| 			string iocAsHexString = MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray())))+".cache"; | 			string iocAsHexString = MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray())))+".cache"; | ||||||
| 			return _streamCacheDir + iocAsHexString; | 		    if (File.Exists(_legacyCacheDir + iocAsHexString)) | ||||||
|  | 		        return _legacyCacheDir + iocAsHexString; | ||||||
|  |  | ||||||
|  | 		    return _cacheDir + iocAsHexString; | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public bool IsCached(IOConnectionInfo ioc) | 		public bool IsCached(IOConnectionInfo ioc) | ||||||
| @@ -168,7 +178,9 @@ namespace keepass2android.Io | |||||||
| 				if (!IsCached(ioc)) | 				if (!IsCached(ioc)) | ||||||
| 					throw; | 					throw; | ||||||
|  |  | ||||||
| 				Kp2aLog.Log("couldn't open from remote " + ioc.Path); | #if DEBUG | ||||||
|  |                 Kp2aLog.Log("couldn't open from remote " + ioc.Path); | ||||||
|  | #endif | ||||||
| 				Kp2aLog.Log(ex.ToString()); | 				Kp2aLog.Log(ex.ToString()); | ||||||
|  |  | ||||||
| 				_cacheSupervisor.CouldntOpenFromRemote(ioc, ex); | 				_cacheSupervisor.CouldntOpenFromRemote(ioc, ex); | ||||||
|   | |||||||
| @@ -233,7 +233,6 @@ namespace keepass2android.Io | |||||||
|  |  | ||||||
| 		public FileDescription GetFileDescription(IOConnectionInfo ioc) | 		public FileDescription GetFileDescription(IOConnectionInfo ioc) | ||||||
| 		{ | 		{ | ||||||
| 			Kp2aLog.Log("GetFileDescription "+ioc.Path); |  | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
| 				return ConvertToFileDescription(Jfs.GetFileEntry(IocToPath(ioc))); | 				return ConvertToFileDescription(Jfs.GetFileEntry(IocToPath(ioc))); | ||||||
| @@ -302,7 +301,9 @@ namespace keepass2android.Io | |||||||
|  |  | ||||||
| 		public void OnResume(IFileStorageSetupActivity activity) | 		public void OnResume(IFileStorageSetupActivity activity) | ||||||
| 		{ | 		{ | ||||||
|  | #if DEBUG | ||||||
| 			Kp2aLog.Log("JFS/OnResume Ioc.Path=" +activity.Ioc.Path+". Path="+((IJavaFileStorageFileStorageSetupActivity)activity).Path); | 			Kp2aLog.Log("JFS/OnResume Ioc.Path=" +activity.Ioc.Path+". Path="+((IJavaFileStorageFileStorageSetupActivity)activity).Path); | ||||||
|  | #endif | ||||||
| 			_jfs.OnResume(((IJavaFileStorageFileStorageSetupActivity) activity)); | 			_jfs.OnResume(((IJavaFileStorageFileStorageSetupActivity) activity)); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -366,4 +367,4 @@ namespace keepass2android.Io | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| #endif | #endif | ||||||
| } |         } | ||||||
| @@ -91,7 +91,7 @@ | |||||||
|     <Compile Include="database\CheckDatabaseForChanges.cs" /> |     <Compile Include="database\CheckDatabaseForChanges.cs" /> | ||||||
|     <Compile Include="database\edit\AddTemplateEntries.cs" /> |     <Compile Include="database\edit\AddTemplateEntries.cs" /> | ||||||
|     <Compile Include="database\edit\CopyEntry.cs" /> |     <Compile Include="database\edit\CopyEntry.cs" /> | ||||||
|     <Compile Include="database\edit\DeleteMultipleItems.cs" /> |     <Compile Include="database\edit\DeleteMultipleItemsFromOneDatabase.cs" /> | ||||||
|     <Compile Include="database\edit\EditGroup.cs" /> |     <Compile Include="database\edit\EditGroup.cs" /> | ||||||
|     <Compile Include="database\edit\MoveElements.cs" /> |     <Compile Include="database\edit\MoveElements.cs" /> | ||||||
|     <Compile Include="database\KdbDatabaseFormat.cs" /> |     <Compile Include="database\KdbDatabaseFormat.cs" /> | ||||||
| @@ -103,6 +103,7 @@ | |||||||
|     <Compile Include="DataExchange\Formats\KeePassKdb2x.cs" /> |     <Compile Include="DataExchange\Formats\KeePassKdb2x.cs" /> | ||||||
|     <Compile Include="DataExchange\Formats\KeePassXml2x.cs" /> |     <Compile Include="DataExchange\Formats\KeePassXml2x.cs" /> | ||||||
|     <Compile Include="DataExchange\PwExportInfo.cs" /> |     <Compile Include="DataExchange\PwExportInfo.cs" /> | ||||||
|  |     <Compile Include="ElementAndDatabaseId.cs" /> | ||||||
|     <Compile Include="Io\AndroidContentStorage.cs" /> |     <Compile Include="Io\AndroidContentStorage.cs" /> | ||||||
|     <Compile Include="Io\BuiltInFileStorage.cs" /> |     <Compile Include="Io\BuiltInFileStorage.cs" /> | ||||||
|     <Compile Include="Io\CachingFileStorage.cs" /> |     <Compile Include="Io\CachingFileStorage.cs" /> | ||||||
| @@ -156,10 +157,6 @@ | |||||||
|     <Compile Include="Utils\Spr\SprEngine.PickChars.cs" /> |     <Compile Include="Utils\Spr\SprEngine.PickChars.cs" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <ProjectReference Include="..\AdalBindings\AdalBindings.csproj"> |  | ||||||
|       <Project>{0b109c0e-0514-4340-8779-5bd6a0dde84e}</Project> |  | ||||||
|       <Name>AdalBindings</Name> |  | ||||||
|     </ProjectReference> |  | ||||||
|     <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj"> |     <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj"> | ||||||
|       <Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project> |       <Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project> | ||||||
|       <Name>JavaFileStorageBindings</Name> |       <Name>JavaFileStorageBindings</Name> | ||||||
|   | |||||||
| @@ -52,7 +52,8 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 		public void UpdateMessage (String message) | 		public void UpdateMessage (String message) | ||||||
| 		{ | 		{ | ||||||
| 			_message = message; | 		    Kp2aLog.Log("status message: " + message); | ||||||
|  |             _message = message; | ||||||
| 			if ( _app!= null && _progressDialog != null && _handler != null ) { | 			if ( _app!= null && _progressDialog != null && _handler != null ) { | ||||||
| 				_handler.Post(() => {_progressDialog.SetMessage(message); } ); | 				_handler.Post(() => {_progressDialog.SetMessage(message); } ); | ||||||
| 			} | 			} | ||||||
| @@ -60,6 +61,7 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 		public void UpdateSubMessage(String submessage) | 		public void UpdateSubMessage(String submessage) | ||||||
| 		{ | 		{ | ||||||
|  | 		    Kp2aLog.Log("status submessage: " + submessage); | ||||||
| 		    _submessage = submessage; | 		    _submessage = submessage; | ||||||
| 			if (_app != null && _progressDialog != null && _handler != null) | 			if (_app != null && _progressDialog != null && _handler != null) | ||||||
| 			{ | 			{ | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ namespace keepass2android | |||||||
| 	/// <summary> | 	/// <summary> | ||||||
| 	/// EqualityComparer implementation to compare PwGroups based on their Id | 	/// EqualityComparer implementation to compare PwGroups based on their Id | ||||||
| 	/// </summary> | 	/// </summary> | ||||||
| 	class PwGroupEqualityFromIdComparer: IEqualityComparer<PwGroup> | 	public class PwGroupEqualityFromIdComparer: IEqualityComparer<PwGroup> | ||||||
| 	{ | 	{ | ||||||
| 		#region IEqualityComparer implementation			 | 		#region IEqualityComparer implementation			 | ||||||
| 		public bool Equals (PwGroup x, PwGroup y) | 		public bool Equals (PwGroup x, PwGroup y) | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ namespace keepass2android | |||||||
| 			PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) {IsVirtual = true}; | 			PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) {IsVirtual = true}; | ||||||
| 			if (String.IsNullOrWhiteSpace(host)) | 			if (String.IsNullOrWhiteSpace(host)) | ||||||
| 				return pgResults; | 				return pgResults; | ||||||
| 			foreach (PwEntry entry in database.Entries.Values) | 			foreach (PwEntry entry in database.EntriesById.Values) | ||||||
| 			{ | 			{ | ||||||
| 				string otherUrl = entry.Strings.ReadSafe(PwDefs.UrlField); | 				string otherUrl = entry.Strings.ReadSafe(PwDefs.UrlField); | ||||||
| 				otherUrl = SprEngine.Compile(otherUrl, new SprContext(entry, database.KpDatabase, SprCompileFlags.References)); | 				otherUrl = SprEngine.Compile(otherUrl, new SprContext(entry, database.KpDatabase, SprCompileFlags.References)); | ||||||
|   | |||||||
| @@ -86,6 +86,8 @@ namespace keepass2android | |||||||
| 		ReadOnlyReason_PreKitKat, | 		ReadOnlyReason_PreKitKat, | ||||||
| 		ReadOnlyReason_ReadOnlyFlag, | 		ReadOnlyReason_ReadOnlyFlag, | ||||||
| 		ReadOnlyReason_ReadOnlyKitKat, | 		ReadOnlyReason_ReadOnlyKitKat, | ||||||
|         ReadOnlyReason_LocalBackup |         ReadOnlyReason_LocalBackup, | ||||||
|  |         Ok, | ||||||
|  |         cancel | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ namespace keepass2android | |||||||
| 		{ | 		{ | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
| 				IOConnectionInfo ioc = _app.GetDb().Ioc; | 				IOConnectionInfo ioc = _app.CurrentDb.Ioc; | ||||||
| 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||||
| 				if (fileStorage is CachingFileStorage) | 				if (fileStorage is CachingFileStorage) | ||||||
| 				{ | 				{ | ||||||
| @@ -49,7 +49,7 @@ namespace keepass2android | |||||||
| 					hashingRemoteStream.CopyTo(remoteData); | 					hashingRemoteStream.CopyTo(remoteData); | ||||||
| 					hashingRemoteStream.Close(); | 					hashingRemoteStream.Close(); | ||||||
| 					 | 					 | ||||||
| 					if (!MemUtil.ArraysEqual(_app.GetDb().KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash)) | 					if (!MemUtil.ArraysEqual(_app.CurrentDb.KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash)) | ||||||
| 					{ | 					{ | ||||||
| 						_app.TriggerReload(_context); | 						_app.TriggerReload(_context); | ||||||
| 						Finish(true); | 						Finish(true); | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ using KeePassLib; | |||||||
| using KeePassLib.Keys; | using KeePassLib.Keys; | ||||||
| using KeePassLib.Serialization; | using KeePassLib.Serialization; | ||||||
| using keepass2android.Io; | using keepass2android.Io; | ||||||
|  | using KeePassLib.Interfaces; | ||||||
| using KeePassLib.Utility; | using KeePassLib.Utility; | ||||||
| using Exception = System.Exception; | using Exception = System.Exception; | ||||||
| using String = System.String; | using String = System.String; | ||||||
| @@ -33,27 +34,23 @@ using String = System.String; | |||||||
| namespace keepass2android | namespace keepass2android | ||||||
| { | { | ||||||
|  |  | ||||||
| 	public class Database { | 	public class Database | ||||||
| 		 | 	{ | ||||||
|  | 	    public HashSet<IStructureItem> Elements = new HashSet<IStructureItem>(); | ||||||
| 		public Dictionary<PwUuid, PwGroup> Groups = new Dictionary<PwUuid, PwGroup>(new PwUuidEqualityComparer()); | 		public Dictionary<PwUuid, PwGroup> GroupsById = new Dictionary<PwUuid, PwGroup>(new PwUuidEqualityComparer()); | ||||||
| 		public Dictionary<PwUuid, PwEntry> Entries = new Dictionary<PwUuid, PwEntry>(new PwUuidEqualityComparer()); | 		public Dictionary<PwUuid, PwEntry> EntriesById = new Dictionary<PwUuid, PwEntry>(new PwUuidEqualityComparer()); | ||||||
| 		public HashSet<PwGroup> Dirty = new HashSet<PwGroup>(new PwGroupEqualityFromIdComparer()); |  | ||||||
| 		public PwGroup Root; | 		public PwGroup Root; | ||||||
| 		public PwDatabase KpDatabase; | 		public PwDatabase KpDatabase; | ||||||
| 		public IOConnectionInfo Ioc  | 		public IOConnectionInfo Ioc  | ||||||
| 		{ | 		{ | ||||||
| 			get | 			get | ||||||
| 			{ | 			{ | ||||||
| 				return KpDatabase == null ? null : KpDatabase.IOConnectionInfo; |                  | ||||||
|  |                 return KpDatabase?.IOConnectionInfo; | ||||||
|  |                  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// <summary> |  | ||||||
| 		/// Information about the last opened entry. Includes the entry but also transformed fields. |  | ||||||
| 		/// </summary> |  | ||||||
| 		public PwEntryOutput LastOpenedEntry { get; set; } |  | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// if an OTP key was used, this property tells the location of the OTP auxiliary file. | 		/// if an OTP key was used, this property tells the location of the OTP auxiliary file. | ||||||
| 		/// Must be set after loading. | 		/// Must be set after loading. | ||||||
| @@ -73,31 +70,14 @@ namespace keepass2android | |||||||
|             _app = app; |             _app = app; | ||||||
| 			CanWrite = true; //default | 			CanWrite = true; //default | ||||||
|         } |         } | ||||||
| 		 |  | ||||||
| 		private bool _loaded; |  | ||||||
|  |  | ||||||
|         private bool _reloadRequested; | 	    private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default); | ||||||
| 		private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default); |  | ||||||
|  |  | ||||||
| 		public bool ReloadRequested | 		public bool ReloadRequested { get; set; } | ||||||
|         { |  | ||||||
|             get { return _reloadRequested; } |  | ||||||
|             set { _reloadRequested = value; } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
| 		public bool Loaded { | 	    public bool DidOpenFileChange() | ||||||
| 			get { return _loaded;} |  | ||||||
| 			set { _loaded = value; } |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		public bool DidOpenFileChange() |  | ||||||
| 		{ | 		{ | ||||||
| 			if (Loaded == false) |  | ||||||
| 			{ |  | ||||||
| 				return false; |  | ||||||
| 			} |  | ||||||
| 			return _app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion); | 			return _app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion); | ||||||
| 			 |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -112,32 +92,20 @@ namespace keepass2android | |||||||
| 			Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo); | 			Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo); | ||||||
| 			var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); | 			var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); | ||||||
| 			PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat); | 			PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat); | ||||||
| 			try | 		    LastFileVersion = fileVersion; | ||||||
| 			{ |  | ||||||
| 				LastFileVersion = fileVersion; |  | ||||||
|  |  | ||||||
| 				status.UpdateSubMessage(""); | 		    status.UpdateSubMessage(""); | ||||||
|  |  | ||||||
| 				Root = pwDatabase.RootGroup; | 		    Root = pwDatabase.RootGroup; | ||||||
| 				PopulateGlobals(Root); | 		    PopulateGlobals(Root); | ||||||
|  |  | ||||||
| 				 | 				 | ||||||
| 				KpDatabase = pwDatabase; | 		    KpDatabase = pwDatabase; | ||||||
| 				SearchHelper = new SearchDbHelper(app); | 		    SearchHelper = new SearchDbHelper(app); | ||||||
|  |  | ||||||
| 				_databaseFormat = databaseFormat; | 		    _databaseFormat = databaseFormat; | ||||||
|  |  | ||||||
| 				CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo); | 		    CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo); | ||||||
| 				Loaded = true; |  | ||||||
| 			} |  | ||||||
| 			catch (Exception) |  | ||||||
| 			{ |  | ||||||
| 				Clear();				 |  | ||||||
| 				throw; |  | ||||||
| 			} |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 			 |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| @@ -219,8 +187,7 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 		public void SaveData()  { | 		public void SaveData()  { | ||||||
|              |              | ||||||
| 			KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions); | 			using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions))) | ||||||
| 			using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions)) |  | ||||||
| 			{ | 			{ | ||||||
| 				DatabaseFormat.Save(KpDatabase, trans.OpenFile()); | 				DatabaseFormat.Save(KpDatabase, trans.OpenFile()); | ||||||
| 				 | 				 | ||||||
| @@ -239,14 +206,18 @@ namespace keepass2android | |||||||
| 			{ | 			{ | ||||||
| 				if (checkForDuplicateUuids) | 				if (checkForDuplicateUuids) | ||||||
| 				{ | 				{ | ||||||
| 					if (Entries.ContainsKey(e.Uuid)) | 					if (EntriesById.ContainsKey(e.Uuid)) | ||||||
| 					{ | 					{ | ||||||
| 						throw new DuplicateUuidsException("Same UUID for entries '"+Entries[e.Uuid].Strings.ReadSafe(PwDefs.TitleField)+"' and '"+e.Strings.ReadSafe(PwDefs.TitleField)+"'."); | 						throw new DuplicateUuidsException("Same UUID for entries '"+EntriesById[e.Uuid].Strings.ReadSafe(PwDefs.TitleField)+"' and '"+e.Strings.ReadSafe(PwDefs.TitleField)+"'."); | ||||||
| 					} | 					} | ||||||
| 					 | 					 | ||||||
| 				} | 				} | ||||||
| 				Entries [e.Uuid] = e; | 				EntriesById [e.Uuid] = e; | ||||||
|  | 			    Elements.Add(e); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 		    GroupsById[currentGroup.Uuid] = currentGroup; | ||||||
|  | 		    Elements.Add(currentGroup); | ||||||
| 			foreach (PwGroup g in childGroups)  | 			foreach (PwGroup g in childGroups)  | ||||||
| 			{ | 			{ | ||||||
| 				if (checkForDuplicateUuids) | 				if (checkForDuplicateUuids) | ||||||
| @@ -258,7 +229,6 @@ namespace keepass2android | |||||||
| 					} | 					} | ||||||
| 					 * */ | 					 * */ | ||||||
| 				} | 				} | ||||||
| 				Groups[g.Uuid] = g; |  | ||||||
| 				PopulateGlobals(g); | 				PopulateGlobals(g); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -266,33 +236,15 @@ namespace keepass2android | |||||||
| 		{ | 		{ | ||||||
| 			PopulateGlobals(currentGroup, _app.CheckForDuplicateUuids); | 			PopulateGlobals(currentGroup, _app.CheckForDuplicateUuids); | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		public void Clear() { |  | ||||||
| 			_loaded = false;  |  | ||||||
| 			 |  | ||||||
| 			Groups.Clear(); |  | ||||||
| 			Entries.Clear(); |  | ||||||
| 			Dirty.Clear(); |  | ||||||
| 			DrawableFactory.Clear(); |  | ||||||
| 			 |  | ||||||
| 			Root = null; |  | ||||||
| 			KpDatabase = null; |  | ||||||
| 			 |  | ||||||
| 			CanWrite = true; |  | ||||||
| 			_reloadRequested = false; |  | ||||||
| 			OtpAuxFileIoc = null; |  | ||||||
| 		    LastOpenedEntry = null; |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		public void MarkAllGroupsAsDirty() { |  | ||||||
| 			foreach ( PwGroup group in Groups.Values ) { |  | ||||||
| 				Dirty.Add(group); |  | ||||||
| 			} |  | ||||||
| 			 |  | ||||||
|  |  | ||||||
| 		} |  | ||||||
| 		 | 	    public void UpdateGlobals() | ||||||
| 		 | 	    { | ||||||
|  | 	        EntriesById.Clear(); | ||||||
|  | 	        GroupsById.Clear(); | ||||||
|  | 	        Elements.Clear(); | ||||||
|  |             PopulateGlobals(Root); | ||||||
|  | 	    } | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	[Serializable] | 	[Serializable] | ||||||
|   | |||||||
| @@ -216,9 +216,16 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 		private DateTime JavaTimeToCSharp(long javatime) | 		private DateTime JavaTimeToCSharp(long javatime) | ||||||
| 		{ | 		{ | ||||||
|  | 		    try | ||||||
|  | 		    { | ||||||
|  | 		        var utcTime = new DateTime(1970, 1, 1).AddMilliseconds(javatime); | ||||||
|  | 		        return TimeZoneInfo.ConvertTimeFromUtc(utcTime, TimeZoneInfo.Local); | ||||||
|  | 		    } | ||||||
|  | 		    catch (ArgumentOutOfRangeException) | ||||||
|  | 		    { | ||||||
|  | 		        return DateTime.MinValue; | ||||||
|  | 		    } | ||||||
| 			 | 			 | ||||||
| 			var utcTime = new DateTime(1970, 1, 1).AddMilliseconds(javatime); |  | ||||||
| 			return TimeZoneInfo.ConvertTimeFromUtc(utcTime, TimeZoneInfo.Local); |  | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -14,13 +14,13 @@ namespace keepass2android | |||||||
| 	public class PwEntryOutput | 	public class PwEntryOutput | ||||||
| 	{ | 	{ | ||||||
| 		private readonly PwEntry _entry; | 		private readonly PwEntry _entry; | ||||||
| 		private readonly PwDatabase _db; | 		private readonly Database _db; | ||||||
| 		private readonly ProtectedStringDictionary _outputStrings = new ProtectedStringDictionary(); | 		private readonly ProtectedStringDictionary _outputStrings = new ProtectedStringDictionary(); | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Constructs the PwEntryOutput by replacing the placeholders | 		/// Constructs the PwEntryOutput by replacing the placeholders | ||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		public PwEntryOutput(PwEntry entry, PwDatabase db) | 		public PwEntryOutput(PwEntry entry, Database db) | ||||||
| 		{ | 		{ | ||||||
| 			_entry = entry; | 			_entry = entry; | ||||||
| 			_db = db; | 			_db = db; | ||||||
| @@ -34,7 +34,7 @@ namespace keepass2android | |||||||
| 		string GetStringAndReplacePlaceholders(string key) | 		string GetStringAndReplacePlaceholders(string key) | ||||||
| 		{ | 		{ | ||||||
| 			String value = Entry.Strings.ReadSafe(key); | 			String value = Entry.Strings.ReadSafe(key); | ||||||
| 			value = SprEngine.Compile(value, new SprContext(Entry, _db, SprCompileFlags.All)); | 			value = SprEngine.Compile(value, new SprContext(Entry, _db.KpDatabase, SprCompileFlags.All)); | ||||||
| 			return value; | 			return value; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ namespace keepass2android | |||||||
| 		{ | 		{ | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
| 				IOConnectionInfo ioc = _app.GetDb().Ioc; | 				IOConnectionInfo ioc = _app.CurrentDb.Ioc; | ||||||
| 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||||
| 				if (!(fileStorage is CachingFileStorage)) | 				if (!(fileStorage is CachingFileStorage)) | ||||||
| 				{ | 				{ | ||||||
| @@ -70,10 +70,12 @@ namespace keepass2android | |||||||
| 									Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | 									Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||||
| 								} | 								} | ||||||
| 								_saveDb = null; | 								_saveDb = null; | ||||||
| 							}), false, remoteData); | 							}), _app.CurrentDb, false, remoteData); | ||||||
| 						_saveDb.Run(); | 						_saveDb.Run(); | ||||||
|  |  | ||||||
| 						_app.GetDb().MarkAllGroupsAsDirty(); |                         _app.CurrentDb.UpdateGlobals(); | ||||||
|  |  | ||||||
|  | 						_app.MarkAllGroupsAsDirty(); | ||||||
| 					} | 					} | ||||||
| 					else | 					else | ||||||
| 					{ | 					{ | ||||||
| @@ -103,6 +105,7 @@ namespace keepass2android | |||||||
| 			} | 			} | ||||||
| 			catch (Exception e) | 			catch (Exception e) | ||||||
| 			{ | 			{ | ||||||
|  |                 Kp2aLog.LogUnexpectedError(e); | ||||||
| 				Finish(false, e.Message); | 				Finish(false, e.Message); | ||||||
| 			} | 			} | ||||||
| 			 | 			 | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ namespace keepass2android | |||||||
| 	public class AddEntry : RunnableOnFinish { | 	public class AddEntry : RunnableOnFinish { | ||||||
| 		protected Database Db | 		protected Database Db | ||||||
| 		{ | 		{ | ||||||
| 			get { return _app.GetDb(); } | 			get { return _app.CurrentDb; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		private readonly IKp2aApp _app; | 		private readonly IKp2aApp _app; | ||||||
| @@ -37,13 +37,13 @@ namespace keepass2android | |||||||
| 			return new AddEntry(ctx, app, entry, parentGroup, finish); | 			return new AddEntry(ctx, app, entry, parentGroup, finish); | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		protected AddEntry(Activity ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(ctx, finish) { | 		public AddEntry(Activity ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(ctx, finish) { | ||||||
| 			_ctx = ctx; | 			_ctx = ctx; | ||||||
| 			_parentGroup = parentGroup; | 			_parentGroup = parentGroup; | ||||||
| 			_app = app; | 			_app = app; | ||||||
| 			_entry = entry; | 			_entry = entry; | ||||||
| 			 | 			 | ||||||
| 			_onFinishToRun = new AfterAdd(ctx, app.GetDb(), entry, OnFinishToRun); | 			_onFinishToRun = new AfterAdd(ctx, app.CurrentDb, entry, app,OnFinishToRun); | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		 | 		 | ||||||
| @@ -60,7 +60,7 @@ namespace keepass2android | |||||||
| 			 | 			 | ||||||
| 			 | 			 | ||||||
| 			// Commit to disk | 			// Commit to disk | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | 			SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 			save.SetStatusLogger(StatusLogger); | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
| 		} | 		} | ||||||
| @@ -68,12 +68,13 @@ namespace keepass2android | |||||||
| 		private class AfterAdd : OnFinish { | 		private class AfterAdd : OnFinish { | ||||||
| 			private readonly Database _db; | 			private readonly Database _db; | ||||||
| 			private readonly PwEntry _entry; | 			private readonly PwEntry _entry; | ||||||
|  | 		    private readonly IKp2aApp _app; | ||||||
|  |  | ||||||
| 			public AfterAdd(Activity activity, Database db, PwEntry entry, OnFinish finish):base(activity, finish) { | 		    public AfterAdd(Activity activity, Database db, PwEntry entry, IKp2aApp app, OnFinish finish):base(activity, finish) { | ||||||
| 				_db = db; | 				_db = db; | ||||||
| 				_entry = entry; | 				_entry = entry; | ||||||
|  | 		        _app = app; | ||||||
| 			} | 		    } | ||||||
| 			 | 			 | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -83,11 +84,12 @@ namespace keepass2android | |||||||
| 					PwGroup parent = _entry.ParentGroup;  | 					PwGroup parent = _entry.ParentGroup;  | ||||||
| 					 | 					 | ||||||
| 					// Mark parent group dirty | 					// Mark parent group dirty | ||||||
| 					_db.Dirty.Add(parent); | 					_app.DirtyGroups.Add(parent); | ||||||
| 					 | 					 | ||||||
| 					// Add entry to global | 					// Add entry to global | ||||||
| 					_db.Entries[_entry.Uuid] = _entry; | 					_db.EntriesById[_entry.Uuid] = _entry; | ||||||
| 					 | 				    _db.Elements.Add(_entry); | ||||||
|  |  | ||||||
| 				} else | 				} else | ||||||
| 				{ | 				{ | ||||||
| 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | ||||||
|   | |||||||
| @@ -26,13 +26,16 @@ namespace keepass2android | |||||||
| 	public class AddGroup : RunnableOnFinish { | 	public class AddGroup : RunnableOnFinish { | ||||||
| 		internal Database Db | 		internal Database Db | ||||||
| 		{ | 		{ | ||||||
| 			get { return _app.GetDb(); } | 			get { return _app.CurrentDb; } | ||||||
| 		} | 		} | ||||||
| 		private IKp2aApp _app; |  | ||||||
|  |         public IKp2aApp App { get => _app; } | ||||||
|  |  | ||||||
|  |         private IKp2aApp _app; | ||||||
| 		private readonly String _name; | 		private readonly String _name; | ||||||
| 		private readonly int _iconId; | 		private readonly int _iconId; | ||||||
| 		private readonly PwUuid _groupCustomIconId; | 		private readonly PwUuid _groupCustomIconId; | ||||||
| 		internal PwGroup Group; | 	    public PwGroup Group; | ||||||
| 		internal PwGroup Parent; | 		internal PwGroup Parent; | ||||||
| 		protected bool DontSave; | 		protected bool DontSave; | ||||||
| 		readonly Activity _ctx; | 		readonly Activity _ctx; | ||||||
| @@ -67,9 +70,11 @@ namespace keepass2android | |||||||
| 				Group.CustomIconUuid = _groupCustomIconId; | 				Group.CustomIconUuid = _groupCustomIconId; | ||||||
| 			} | 			} | ||||||
| 			Parent.AddGroup(Group, true); | 			Parent.AddGroup(Group, true); | ||||||
|  | 		    _app.CurrentDb.GroupsById[Group.Uuid] = Group; | ||||||
|  | 		    _app.CurrentDb.Elements.Add(Group); | ||||||
|  |  | ||||||
| 			// Commit to disk |             // Commit to disk | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, DontSave); |             SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, DontSave); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 			save.SetStatusLogger(StatusLogger); | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
| 		} | 		} | ||||||
| @@ -86,13 +91,15 @@ namespace keepass2android | |||||||
| 				 | 				 | ||||||
| 				if ( Success ) { | 				if ( Success ) { | ||||||
| 					// Mark parent group dirty | 					// Mark parent group dirty | ||||||
| 					_addGroup.Db.Dirty.Add(_addGroup.Parent); | 					_addGroup.App.DirtyGroups.Add(_addGroup.Parent); | ||||||
| 					 | 					 | ||||||
| 					// Add group to global list | 					// Add group to global list | ||||||
| 					_addGroup.Db.Groups[_addGroup.Group.Uuid] = _addGroup.Group; | 					_addGroup.Db.GroupsById[_addGroup.Group.Uuid] = _addGroup.Group; | ||||||
|  | 				    _addGroup.Db.Elements.Add(_addGroup.Group); | ||||||
| 				} else { | 				} else { | ||||||
| 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | ||||||
| 					_addGroup.Parent.Groups.Remove(_addGroup.Group); | 					_addGroup.Parent.Groups.Remove(_addGroup.Group); | ||||||
|  |  | ||||||
| 				} | 				} | ||||||
| 				 | 				 | ||||||
| 				base.Run(); | 				base.Run(); | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ namespace keepass2android | |||||||
| { | { | ||||||
| 	public class AddTemplateEntries : RunnableOnFinish { | 	public class AddTemplateEntries : RunnableOnFinish { | ||||||
|  |  | ||||||
| 		class TemplateEntry | 		public class TemplateEntry | ||||||
| 		{ | 		{ | ||||||
| 			public UiStringKey Title { get; set; } | 			public UiStringKey Title { get; set; } | ||||||
| 			public PwIcon Icon { get; set; } | 			public PwIcon Icon { get; set; } | ||||||
| @@ -47,11 +47,12 @@ namespace keepass2android | |||||||
| 				void AddToEntry(IKp2aApp app, PwEntry entry, int position); | 				void AddToEntry(IKp2aApp app, PwEntry entry, int position); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			internal enum FieldType | 		    public enum FieldType | ||||||
| 			{ | 			{ | ||||||
| 				Inline, ProtectedInline | 				Inline, ProtectedInline | ||||||
| 			} | 			} | ||||||
| 			internal enum SpecialFieldKey |  | ||||||
|  | 		    public enum SpecialFieldKey | ||||||
| 			{ | 			{ | ||||||
| 				ExpDate, OverrideUrl, Tags | 				ExpDate, OverrideUrl, Tags | ||||||
| 			} | 			} | ||||||
| @@ -125,7 +126,7 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 		protected Database Db | 		protected Database Db | ||||||
| 		{ | 		{ | ||||||
| 			get { return _app.GetDb(); } | 			get { return _app.CurrentDb; } | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		private readonly IKp2aApp _app; | 		private readonly IKp2aApp _app; | ||||||
| @@ -140,7 +141,7 @@ namespace keepass2android | |||||||
| 			//_onFinishToRun = new AfterAdd(this, OnFinishToRun); | 			//_onFinishToRun = new AfterAdd(this, OnFinishToRun); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>() | 		public static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>() | ||||||
| 			{ | 			{ | ||||||
| 				new TemplateEntry() | 				new TemplateEntry() | ||||||
| 				{ | 				{ | ||||||
| @@ -285,12 +286,23 @@ namespace keepass2android | |||||||
| 				 | 				 | ||||||
| 			}; | 			}; | ||||||
|  |  | ||||||
| 		public static bool ContainsAllTemplates(IKp2aApp app) | 		public static bool ContainsAllTemplates(Database db) | ||||||
| 		{ | 		{ | ||||||
| 			return TemplateEntries.All(t => app.GetDb().Entries.ContainsKey(t.Uuid)); | 			return TemplateEntries.All(t => | ||||||
|  | 			{ | ||||||
|  | 			    string hexId = t.Uuid.ToHexString(); | ||||||
|  |                  | ||||||
|  |                 return db.EntriesById.Any(kvp => kvp.Key.Equals(t.Uuid) || | ||||||
|  |                     kvp.Value.Strings.ReadSafe(TemplateIdStringKey) == hexId); | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public override void Run() {	 | 	    public static string TemplateIdStringKey | ||||||
|  | 	    { | ||||||
|  | 	        get { return "KP2A_TemplateId"; } | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    public override void Run() {	 | ||||||
| 			StatusLogger.UpdateMessage(UiStringKey.AddingEntry); | 			StatusLogger.UpdateMessage(UiStringKey.AddingEntry); | ||||||
|  |  | ||||||
| 			List<PwEntry> addedEntries; | 			List<PwEntry> addedEntries; | ||||||
| @@ -298,10 +310,10 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 			if (addedEntries.Any()) | 			if (addedEntries.Any()) | ||||||
| 			{ | 			{ | ||||||
| 				_app.GetDb().Dirty.Add(templateGroup); | 				_app.DirtyGroups.Add(templateGroup); | ||||||
|  |  | ||||||
| 				// Commit to disk | 				// Commit to disk | ||||||
| 				SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | 				SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); | ||||||
| 				save.SetStatusLogger(StatusLogger); | 				save.SetStatusLogger(StatusLogger); | ||||||
| 				save.Run(); | 				save.Run(); | ||||||
| 			} | 			} | ||||||
| @@ -315,26 +327,28 @@ namespace keepass2android | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			PwGroup templateGroup; | 			PwGroup templateGroup; | ||||||
| 			if (!_app.GetDb().Groups.TryGetValue(_app.GetDb().KpDatabase.EntryTemplatesGroup, out templateGroup)) | 			if (!_app.CurrentDb.GroupsById.TryGetValue(_app.CurrentDb.KpDatabase.EntryTemplatesGroup, out templateGroup)) | ||||||
| 			{ | 			{ | ||||||
| 				//create template group | 				//create template group | ||||||
| 				templateGroup = new PwGroup(true, true, _app.GetResourceString(UiStringKey.TemplateGroupName), PwIcon.Folder); | 				templateGroup = new PwGroup(true, true, _app.GetResourceString(UiStringKey.TemplateGroupName), PwIcon.Folder); | ||||||
| 				_app.GetDb().KpDatabase.RootGroup.AddGroup(templateGroup, true); | 				_app.CurrentDb.KpDatabase.RootGroup.AddGroup(templateGroup, true); | ||||||
| 				_app.GetDb().KpDatabase.EntryTemplatesGroup = templateGroup.Uuid; | 				_app.CurrentDb.KpDatabase.EntryTemplatesGroup = templateGroup.Uuid; | ||||||
| 				_app.GetDb().KpDatabase.EntryTemplatesGroupChanged = DateTime.Now; | 				_app.CurrentDb.KpDatabase.EntryTemplatesGroupChanged = DateTime.Now; | ||||||
| 				_app.GetDb().Dirty.Add(_app.GetDb().KpDatabase.RootGroup); | 				_app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup); | ||||||
| 				_app.GetDb().Groups[templateGroup.Uuid] = templateGroup; | 				_app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup; | ||||||
|  | 			    _app.CurrentDb.Elements.Add(templateGroup); | ||||||
|  |  | ||||||
| 			} | 			} | ||||||
| 			addedEntries = new List<PwEntry>(); | 			addedEntries = new List<PwEntry>(); | ||||||
|  |  | ||||||
| 			foreach (var template in TemplateEntries) | 			foreach (var template in TemplateEntries) | ||||||
| 			{ | 			{ | ||||||
| 				if (_app.GetDb().Entries.ContainsKey(template.Uuid)) | 				if (_app.CurrentDb.EntriesById.ContainsKey(template.Uuid)) | ||||||
| 					continue; | 					continue; | ||||||
| 				PwEntry entry = CreateEntry(template); | 				PwEntry entry = CreateEntry(template); | ||||||
| 				templateGroup.AddEntry(entry, true); | 				templateGroup.AddEntry(entry, true); | ||||||
| 				addedEntries.Add(entry); | 				addedEntries.Add(entry); | ||||||
| 				_app.GetDb().Entries[entry.Uuid] = entry; | 				_app.CurrentDb.EntriesById[entry.Uuid] = entry; | ||||||
| 			} | 			} | ||||||
| 			return templateGroup; | 			return templateGroup; | ||||||
| 		} | 		} | ||||||
| @@ -373,8 +387,12 @@ namespace keepass2android | |||||||
| 				base.Run(); | 				base.Run(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		 |  | ||||||
|  | 	    public static bool IsTemplateId(PwUuid pwUuid) | ||||||
|  | 	    { | ||||||
|  | 	        return TemplateEntries.Any(te => te.Uuid.Equals(pwUuid)); | ||||||
|  | 	    } | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -27,23 +27,22 @@ namespace keepass2android | |||||||
| { | { | ||||||
| 	 | 	 | ||||||
| 	public class CreateDb : RunnableOnFinish { | 	public class CreateDb : RunnableOnFinish { | ||||||
| 		 | 	    private readonly IOConnectionInfo _ioc; | ||||||
| 		private const ulong DefaultEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; |  | ||||||
| 		 |  | ||||||
| 		private readonly IOConnectionInfo _ioc; |  | ||||||
| 		private readonly bool _dontSave; | 		private readonly bool _dontSave; | ||||||
| 		private readonly Activity _ctx; | 		private readonly Activity _ctx; | ||||||
|         private readonly IKp2aApp _app; |         private readonly IKp2aApp _app; | ||||||
| 		private CompositeKey _key; | 		private CompositeKey _key; | ||||||
|  | 	    private readonly bool _makeCurrent; | ||||||
|  |  | ||||||
| 		public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave): base(ctx, finish) { | 	    public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, bool makeCurrent): base(ctx, finish) { | ||||||
| 			_ctx = ctx; | 			_ctx = ctx; | ||||||
| 			_ioc = ioc; | 			_ioc = ioc; | ||||||
| 			_dontSave = dontSave; | 			_dontSave = dontSave; | ||||||
|             _app = app; | 	        _makeCurrent = makeCurrent; | ||||||
|  | 	        _app = app; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, CompositeKey key) | 		public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, CompositeKey key, bool makeCurrent) | ||||||
| 			: base(ctx, finish) | 			: base(ctx, finish) | ||||||
| 		{ | 		{ | ||||||
| 			_ctx = ctx; | 			_ctx = ctx; | ||||||
| @@ -51,12 +50,13 @@ namespace keepass2android | |||||||
| 			_dontSave = dontSave; | 			_dontSave = dontSave; | ||||||
| 			_app = app; | 			_app = app; | ||||||
| 			_key = key; | 			_key = key; | ||||||
|  | 		    _makeCurrent = makeCurrent; | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
|  |  | ||||||
| 		public override void Run() { | 		public override void Run() { | ||||||
| 			StatusLogger.UpdateMessage(UiStringKey.progress_create); | 			StatusLogger.UpdateMessage(UiStringKey.progress_create); | ||||||
| 			Database db = _app.CreateNewDatabase(); | 			Database db = _app.CreateNewDatabase(_makeCurrent); | ||||||
|  |  | ||||||
| 			db.KpDatabase = new KeePassLib.PwDatabase(); | 			db.KpDatabase = new KeePassLib.PwDatabase(); | ||||||
| 			 | 			 | ||||||
| @@ -74,7 +74,6 @@ namespace keepass2android | |||||||
| 			 | 			 | ||||||
| 			// Set Database state | 			// Set Database state | ||||||
| 			db.Root = db.KpDatabase.RootGroup; | 			db.Root = db.KpDatabase.RootGroup; | ||||||
| 			db.Loaded = true; |  | ||||||
| 			db.SearchHelper = new SearchDbHelper(_app); | 			db.SearchHelper = new SearchDbHelper(_app); | ||||||
|  |  | ||||||
| 			// Add a couple default groups | 			// Add a couple default groups | ||||||
| @@ -88,12 +87,14 @@ namespace keepass2android | |||||||
| 			addTemplates.AddTemplates(out addedEntries); | 			addTemplates.AddTemplates(out addedEntries); | ||||||
| 			 | 			 | ||||||
| 			// Commit changes | 			// Commit changes | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave); | 			SaveDb save = new SaveDb(_ctx, _app, db, OnFinishToRun, _dontSave); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 			save.SetStatusLogger(StatusLogger); | ||||||
| 			_onFinishToRun = null; | 			_onFinishToRun = null; | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
| 			 |  | ||||||
| 			 | 		    db.UpdateGlobals(); | ||||||
|  |  | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 	    public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) { | 	    public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) { | ||||||
| 			Ctx = activiy; | 			Ctx = activiy; | ||||||
| 			Db = app.GetDb(); | 			Db = app.FindDatabaseForElement(entry); | ||||||
| 			_entry = entry; | 			_entry = entry; | ||||||
| 			 | 			 | ||||||
| 		} | 		} | ||||||
| @@ -40,7 +40,7 @@ namespace keepass2android | |||||||
| 		{ | 		{ | ||||||
| 			get | 			get | ||||||
| 			{ | 			{ | ||||||
| 				return App.GetDb().DatabaseFormat.CanRecycle && CanRecycleGroup(_entry.ParentGroup); | 				return Db.DatabaseFormat.CanRecycle && CanRecycleGroup(_entry.ParentGroup); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ namespace keepass2android | |||||||
|         */ |         */ | ||||||
|         private void SetMembers(Activity activity, IKp2aApp app, PwGroup group, bool dontSave) |         private void SetMembers(Activity activity, IKp2aApp app, PwGroup group, bool dontSave) | ||||||
|         { |         { | ||||||
| 			base.SetMembers(activity, app.GetDb()); | 			base.SetMembers(activity, app.FindDatabaseForElement(group)); | ||||||
|  |  | ||||||
| 			_group = group; | 			_group = group; | ||||||
| 	        DontSave = dontSave; | 	        DontSave = dontSave; | ||||||
| @@ -58,7 +58,7 @@ namespace keepass2android | |||||||
| 		{ | 		{ | ||||||
| 			get | 			get | ||||||
| 			{ | 			{ | ||||||
| 				return App.GetDb().DatabaseFormat.CanRecycle && CanRecycleGroup(_group); | 				return Db.DatabaseFormat.CanRecycle && CanRecycleGroup(_group); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,12 +7,12 @@ using KeePassLib.Interfaces; | |||||||
| 
 | 
 | ||||||
| namespace keepass2android | namespace keepass2android | ||||||
| { | { | ||||||
| 	public class DeleteMultipleItems : DeleteRunnable | 	public class DeleteMultipleItemsFromOneDatabase : DeleteRunnable | ||||||
| 	{ | 	{ | ||||||
| 		private readonly List<IStructureItem> _elementsToDelete; | 		private readonly List<IStructureItem> _elementsToDelete; | ||||||
| 		private readonly bool _canRecycle; | 		private readonly bool _canRecycle; | ||||||
| 
 | 
 | ||||||
| 		public DeleteMultipleItems(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnFinish finish, IKp2aApp app) | 		public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnFinish finish, IKp2aApp app) | ||||||
| 			: base(activity, finish, app) | 			: base(activity, finish, app) | ||||||
| 		{ | 		{ | ||||||
| 			_elementsToDelete = elementsToDelete; | 			_elementsToDelete = elementsToDelete; | ||||||
| @@ -21,12 +21,13 @@ namespace keepass2android | |||||||
| 			//determine once. The property is queried for each delete operation, but might return false | 			//determine once. The property is queried for each delete operation, but might return false | ||||||
| 			//after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore) | 			//after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore) | ||||||
| 			_canRecycle = DetermineCanRecycle(); | 			_canRecycle = DetermineCanRecycle(); | ||||||
|  | 		    ShowDatabaseIocInStatus = true; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private bool DetermineCanRecycle() | 		private bool DetermineCanRecycle() | ||||||
| 		{ | 		{ | ||||||
| 			Android.Util.Log.Debug("KP2A", "CanRecycle?"); | 			Android.Util.Log.Debug("KP2A", "CanRecycle?"); | ||||||
| 			if (!App.GetDb().DatabaseFormat.CanRecycle) | 			if (!Db.DatabaseFormat.CanRecycle) | ||||||
| 			{ | 			{ | ||||||
| 				Android.Util.Log.Debug("KP2A", "CanRecycle? No because of DB format."); | 				Android.Util.Log.Debug("KP2A", "CanRecycle? No because of DB format."); | ||||||
| 				return false; | 				return false; | ||||||
| @@ -100,7 +100,8 @@ namespace keepass2android | |||||||
| 						}; | 						}; | ||||||
|  |  | ||||||
| 				Db.KpDatabase.RootGroup.AddGroup(pgRecycleBin, true); | 				Db.KpDatabase.RootGroup.AddGroup(pgRecycleBin, true); | ||||||
| 				Db.Groups[pgRecycleBin.Uuid] = pgRecycleBin; | 				Db.GroupsById[pgRecycleBin.Uuid] = pgRecycleBin; | ||||||
|  | 			    Db.Elements.Add(pgRecycleBin); | ||||||
| 				Db.KpDatabase.RecycleBinUuid = pgRecycleBin.Uuid; | 				Db.KpDatabase.RecycleBinUuid = pgRecycleBin.Uuid; | ||||||
|  |  | ||||||
| 				bGroupListUpdateRequired = true; | 				bGroupListUpdateRequired = true; | ||||||
| @@ -121,24 +122,27 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 		public void Start() | 		public void Start() | ||||||
| 		{ | 		{ | ||||||
| 			if (CanRecycle) |             string messageSuffix = ShowDatabaseIocInStatus ? "(" + App.GetFileStorage(Db.Ioc).GetDisplayName(Db.Ioc) + ")" : ""; | ||||||
|  |  | ||||||
|  |             if (CanRecycle) | ||||||
| 			{ | 			{ | ||||||
| 				App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title, | 				App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title, | ||||||
| 					QuestionRecycleResourceId, | 					QuestionRecycleResourceId, | ||||||
| 					(dlgSender, dlgEvt) => | 					(dlgSender, dlgEvt) => | ||||||
| 					{ | 					{ | ||||||
| 						DeletePermanently = true; | 					    DeletePermanently = true; | ||||||
| 						ProgressTask pt = new ProgressTask(App, Ctx, this); | 					    ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||||
| 						pt.Run(); | 					    pt.Run(); | ||||||
|  |  | ||||||
| 					}, | 					}, | ||||||
| 				(dlgSender, dlgEvt) => | 				(dlgSender, dlgEvt) => | ||||||
| 				{ | 				{ | ||||||
| 					DeletePermanently = false; | 				    DeletePermanently = false; | ||||||
| 					ProgressTask pt = new ProgressTask(App, Ctx, this); | 				    ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||||
| 					pt.Run(); | 				    pt.Run(); | ||||||
| 				}, | 				}, | ||||||
| 				(dlgSender, dlgEvt) => { }, | 				(dlgSender, dlgEvt) => { }, | ||||||
| 				Ctx); | 				Ctx, messageSuffix); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -149,12 +153,12 @@ namespace keepass2android | |||||||
| 					QuestionNoRecycleResourceId, | 					QuestionNoRecycleResourceId, | ||||||
| 					(dlgSender, dlgEvt) => | 					(dlgSender, dlgEvt) => | ||||||
| 					{ | 					{ | ||||||
| 						ProgressTask pt = new ProgressTask(App, Ctx, this); | 					    ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||||
| 						pt.Run(); | 					    pt.Run(); | ||||||
| 					}, | 					}, | ||||||
| 				null, | 				null, | ||||||
| 				(dlgSender, dlgEvt) => { }, | 				(dlgSender, dlgEvt) => { }, | ||||||
| 				Ctx); | 				Ctx, messageSuffix); | ||||||
|  |  | ||||||
| 				 | 				 | ||||||
| 			} | 			} | ||||||
| @@ -182,7 +186,8 @@ namespace keepass2android | |||||||
| 					PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); | 					PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); | ||||||
| 					pd.DeletedObjects.Add(pdo); | 					pd.DeletedObjects.Add(pdo); | ||||||
| 					touchedGroups.Add(pgParent); | 					touchedGroups.Add(pgParent); | ||||||
| 				    Db.Entries.Remove(pe.Uuid); | 				    Db.EntriesById.Remove(pe.Uuid); | ||||||
|  | 				    Db.Elements.Remove(pe); | ||||||
| 				} | 				} | ||||||
| 				else // Recycle | 				else // Recycle | ||||||
| 				{ | 				{ | ||||||
| @@ -215,31 +220,41 @@ namespace keepass2android | |||||||
| 				if (success) | 				if (success) | ||||||
| 				{ | 				{ | ||||||
| 					foreach (var g in touchedGroups) | 					foreach (var g in touchedGroups) | ||||||
| 						Db.Dirty.Add(g); | 						App.DirtyGroups.Add(g); | ||||||
| 					foreach (var g in permanentlyDeletedGroups) | 					foreach (var g in permanentlyDeletedGroups) | ||||||
| 					{ | 					{ | ||||||
| 						//remove groups from global lists if present there |                         //remove groups from global lists if present there | ||||||
| 						Db.Dirty.Remove(g); | 					    App.DirtyGroups.Remove(g); | ||||||
| 						Db.Groups.Remove(g.Uuid); | 						Db.GroupsById.Remove(g.Uuid); | ||||||
|  | 					    Db.Elements.Remove(g); | ||||||
|  |  | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 				} | 				} | ||||||
| 				else | 				else | ||||||
| 				{ | 				{ | ||||||
| 					// Let's not bother recovering from a failure to save.  It is too much work. | 					// Let's not bother recovering from a failure to save.  It is too much work. | ||||||
| 					App.LockDatabase(false); | 					App.Lock(false); | ||||||
| 				} | 				} | ||||||
| 			}, OnFinishToRun); | 			}, OnFinishToRun); | ||||||
|  |  | ||||||
| 			// Commit database | 			// Commit database | ||||||
| 			SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, false); | 			SaveDb save = new SaveDb(Ctx, App, Db, OnFinishToRun, false); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 		    save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus; | ||||||
|  |  | ||||||
|  |             save.SetStatusLogger(StatusLogger); | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
|  |  | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		protected abstract void PerformDelete(List<PwGroup> touchedGroups, List<PwGroup> permanentlyDeletedGroups); | 	    public bool ShowDatabaseIocInStatus | ||||||
|  | 	    { | ||||||
|  | 	        get; | ||||||
|  | 	        set; | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    protected abstract void PerformDelete(List<PwGroup> touchedGroups, List<PwGroup> permanentlyDeletedGroups); | ||||||
|  |  | ||||||
| 		public abstract UiStringKey StatusMessage { get; } | 		public abstract UiStringKey StatusMessage { get; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,9 +26,12 @@ namespace keepass2android | |||||||
| 	public class EditGroup : RunnableOnFinish { | 	public class EditGroup : RunnableOnFinish { | ||||||
| 		internal Database Db | 		internal Database Db | ||||||
| 		{ | 		{ | ||||||
| 			get { return _app.GetDb(); } | 			get { return _app.FindDatabaseForElement(Group); } | ||||||
| 		} | 		} | ||||||
| 		private IKp2aApp _app; |  | ||||||
|  |         public IKp2aApp App { get => _app; } | ||||||
|  |  | ||||||
|  |         private IKp2aApp _app; | ||||||
| 		private readonly String _name; | 		private readonly String _name; | ||||||
| 		private readonly PwIcon _iconId; | 		private readonly PwIcon _iconId; | ||||||
| 		private readonly PwUuid _customIconId; | 		private readonly PwUuid _customIconId; | ||||||
| @@ -57,7 +60,7 @@ namespace keepass2android | |||||||
| 			Group.Touch(true); | 			Group.Touch(true); | ||||||
|  |  | ||||||
| 			// Commit to disk | 			// Commit to disk | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | 			SaveDb save = new SaveDb(_ctx, _app, Db, OnFinishToRun); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 			save.SetStatusLogger(StatusLogger); | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
| 		} | 		} | ||||||
| @@ -76,10 +79,10 @@ namespace keepass2android | |||||||
| 				 | 				 | ||||||
| 				if ( Success ) { | 				if ( Success ) { | ||||||
| 					// Mark parent group dirty | 					// Mark parent group dirty | ||||||
| 					_editGroup.Db.Dirty.Add(_editGroup.Group.ParentGroup); | 					_editGroup.App.DirtyGroups.Add(_editGroup.Group.ParentGroup); | ||||||
| 				} else | 				} else | ||||||
| 				{ | 				{ | ||||||
| 					_editGroup._app.LockDatabase(false); | 					_editGroup._app.Lock(false); | ||||||
| 				} | 				} | ||||||
| 				 | 				 | ||||||
| 				base.Run(); | 				base.Run(); | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ using System.Linq; | |||||||
| using System.Threading; | using System.Threading; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Android.App; | using Android.App; | ||||||
|  | using keepass2android.database.edit; | ||||||
| using KeePassLib; | using KeePassLib; | ||||||
| using KeePassLib.Keys; | using KeePassLib.Keys; | ||||||
| using KeePassLib.Serialization; | using KeePassLib.Serialization; | ||||||
| @@ -36,20 +37,25 @@ namespace keepass2android | |||||||
| 		private readonly bool _rememberKeyfile; | 		private readonly bool _rememberKeyfile; | ||||||
| 		IDatabaseFormat _format; | 		IDatabaseFormat _format; | ||||||
| 		 | 		 | ||||||
| 		public LoadDb(Activity activity, IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish): base(activity, finish) | 		public LoadDb(Activity activity, IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish, bool updateLastUsageTimestamp, bool makeCurrent): base(activity, finish) | ||||||
| 		{ | 		{ | ||||||
| 			_app = app; | 			_app = app; | ||||||
| 			_ioc = ioc; | 			_ioc = ioc; | ||||||
| 			_databaseData = databaseData; | 			_databaseData = databaseData; | ||||||
| 			_compositeKey = compositeKey; | 			_compositeKey = compositeKey; | ||||||
| 			_keyfileOrProvider = keyfileOrProvider; | 			_keyfileOrProvider = keyfileOrProvider; | ||||||
|  | 		    _updateLastUsageTimestamp = updateLastUsageTimestamp; | ||||||
|  | 		    _makeCurrent = makeCurrent; | ||||||
|  |  | ||||||
|  |  | ||||||
| 			_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);  | 		    _rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);  | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		 | 	    protected bool success = false; | ||||||
| 		public override void Run() | 	    private bool _updateLastUsageTimestamp; | ||||||
|  | 	    private readonly bool _makeCurrent; | ||||||
|  |  | ||||||
|  | 	    public override void Run() | ||||||
| 		{ | 		{ | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
| @@ -78,6 +84,10 @@ namespace keepass2android | |||||||
| 					//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess: | 					//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess: | ||||||
| 					_format = new KdbxDatabaseFormat(KdbpFile.GetFormatToUse(_ioc)); | 					_format = new KdbxDatabaseFormat(KdbpFile.GetFormatToUse(_ioc)); | ||||||
| 					TryLoad(databaseStream); | 					TryLoad(databaseStream); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 				    success = true; | ||||||
| 				} | 				} | ||||||
| 				catch (Exception e) | 				catch (Exception e) | ||||||
| 				{ | 				{ | ||||||
| @@ -89,7 +99,7 @@ namespace keepass2android | |||||||
| 			{ | 			{ | ||||||
| 				Kp2aLog.Log("KeyFileException"); | 				Kp2aLog.Log("KeyFileException"); | ||||||
| 				Finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/ | 				Finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/ | ||||||
| 				       _app.GetResourceString(UiStringKey.keyfile_does_not_exist), Exception); | 				       _app.GetResourceString(UiStringKey.keyfile_does_not_exist), false, Exception); | ||||||
| 			} | 			} | ||||||
| 			catch (AggregateException e) | 			catch (AggregateException e) | ||||||
| 			{ | 			{ | ||||||
| @@ -100,20 +110,20 @@ namespace keepass2android | |||||||
| 					// Override the message shown with the last (hopefully most recent) inner exception | 					// Override the message shown with the last (hopefully most recent) inner exception | ||||||
| 					Kp2aLog.LogUnexpectedError(innerException); | 					Kp2aLog.LogUnexpectedError(innerException); | ||||||
| 				} | 				} | ||||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, Exception); | 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, false, Exception); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 			catch (DuplicateUuidsException e) | 			catch (DuplicateUuidsException e) | ||||||
| 			{ | 			{ | ||||||
| 				Kp2aLog.Log(e.ToString()); | 				Kp2aLog.Log(e.ToString()); | ||||||
| 				Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), Exception); | 				Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 			catch (Exception e) | 			catch (Exception e) | ||||||
| 			{ | 			{ | ||||||
| 				if (!(e is InvalidCompositeKeyException)) | 				if (!(e is InvalidCompositeKeyException)) | ||||||
| 					Kp2aLog.LogUnexpectedError(e); | 					Kp2aLog.LogUnexpectedError(e); | ||||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, Exception); | 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, false, Exception); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
| 			 | 			 | ||||||
| @@ -125,7 +135,7 @@ namespace keepass2android | |||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		public Exception Exception { get; set; } | 		public Exception Exception { get; set; } | ||||||
|  |  | ||||||
| 		private void TryLoad(MemoryStream databaseStream) | 		Database TryLoad(MemoryStream databaseStream) | ||||||
| 		{ | 		{ | ||||||
| 			//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters | 			//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters | ||||||
| 			//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors. | 			//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors. | ||||||
| @@ -138,19 +148,16 @@ namespace keepass2android | |||||||
| 			//now let's go: | 			//now let's go: | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
|                 _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format); |                 Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent); | ||||||
| 				Kp2aLog.Log("LoadDB OK"); | 				Kp2aLog.Log("LoadDB OK"); | ||||||
|  |  | ||||||
| 			    //make sure the stored access time for the actual file is more recent than that of its backup |  | ||||||
| 			    Thread.Sleep(10); |  | ||||||
|                 SaveFileData(_ioc, _keyfileOrProvider); |  | ||||||
|  |  | ||||||
|                 Finish(true, _format.SuccessMessage); |                 Finish(true, _format.SuccessMessage); | ||||||
|  | 			    return newDb; | ||||||
| 			} | 			} | ||||||
| 			catch (OldFormatException) | 			catch (OldFormatException) | ||||||
| 			{ | 			{ | ||||||
| 				_format = new KdbDatabaseFormat(_app); | 				_format = new KdbDatabaseFormat(_app); | ||||||
| 				TryLoad(databaseStream); | 				return TryLoad(databaseStream); | ||||||
| 			} | 			} | ||||||
| 			catch (InvalidCompositeKeyException) | 			catch (InvalidCompositeKeyException) | ||||||
| 			{ | 			{ | ||||||
| @@ -162,7 +169,7 @@ namespace keepass2android | |||||||
| 					//retry without password: | 					//retry without password: | ||||||
| 					_compositeKey.RemoveUserKey(passwordKey); | 					_compositeKey.RemoveUserKey(passwordKey); | ||||||
| 					//retry: | 					//retry: | ||||||
| 					TryLoad(databaseStream); | 					return TryLoad(databaseStream); | ||||||
| 				} | 				} | ||||||
| 				else throw; | 				else throw; | ||||||
| 			} | 			} | ||||||
| @@ -175,7 +182,7 @@ namespace keepass2android | |||||||
|             { |             { | ||||||
|                 keyfileOrProvider = ""; |                 keyfileOrProvider = ""; | ||||||
|             } |             } | ||||||
|             _app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider); |             _app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider, _updateLastUsageTimestamp); | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		 | 		 | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||||
| using Android.App; | using Android.App; | ||||||
| @@ -50,10 +51,19 @@ namespace keepass2android.database.edit | |||||||
|  |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | 		    HashSet<Database> removeDatabases = new HashSet<Database>(); | ||||||
|  |             Database addDatabase = _app.FindDatabaseForElement(_targetGroup); | ||||||
|  | 		    if (addDatabase == null) | ||||||
|  | 		    { | ||||||
|  | 		        Finish(false, "Did not find target database. Did you lock it?"); | ||||||
|  | 		        return; | ||||||
|  | 		    } | ||||||
|  |  | ||||||
| 		    foreach (var elementToMove in _elementsToMove) | 		    foreach (var elementToMove in _elementsToMove) | ||||||
| 		    { | 		    { | ||||||
|  |  | ||||||
|                 _app.GetDb().Dirty.Add(elementToMove.ParentGroup); |                 _app.DirtyGroups.Add(elementToMove.ParentGroup); | ||||||
|  |                  | ||||||
|  |  | ||||||
|                 PwGroup pgParent = elementToMove.ParentGroup; |                 PwGroup pgParent = elementToMove.ParentGroup; | ||||||
|                 if (pgParent != _targetGroup) |                 if (pgParent != _targetGroup) | ||||||
| @@ -63,8 +73,14 @@ namespace keepass2android.database.edit | |||||||
|                         PwEntry entry = elementToMove as PwEntry; |                         PwEntry entry = elementToMove as PwEntry; | ||||||
|                         if (entry != null) |                         if (entry != null) | ||||||
|                         { |                         { | ||||||
|  |                             var dbRem = _app.FindDatabaseForElement(entry); | ||||||
|  |                             removeDatabases.Add(dbRem); | ||||||
|  |                             dbRem.EntriesById.Remove(entry.Uuid); | ||||||
|  |                             dbRem.Elements.Remove(entry); | ||||||
|                             pgParent.Entries.Remove(entry); |                             pgParent.Entries.Remove(entry); | ||||||
|                             _targetGroup.AddEntry(entry, true, true); |                             _targetGroup.AddEntry(entry, true, true); | ||||||
|  |                             addDatabase.EntriesById.Add(entry.Uuid, entry); | ||||||
|  |                             addDatabase.Elements.Add(entry); | ||||||
|                         } |                         } | ||||||
|                         else |                         else | ||||||
|                         { |                         { | ||||||
| @@ -74,27 +90,60 @@ namespace keepass2android.database.edit | |||||||
|                                 Finish(false, _app.GetResourceString(UiStringKey.CannotMoveGroupHere)); |                                 Finish(false, _app.GetResourceString(UiStringKey.CannotMoveGroupHere)); | ||||||
|                                 return; |                                 return; | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|  |                             var dbRem = _app.FindDatabaseForElement(@group); | ||||||
|  |                             if (dbRem == null) | ||||||
|  |                             { | ||||||
|  |                                 Finish(false, "Did not find source database. Did you lock it?"); | ||||||
|  |                                 return; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             dbRem.GroupsById.Remove(group.Uuid); | ||||||
|  |                             dbRem.Elements.Remove(group); | ||||||
|  |                             removeDatabases.Add(dbRem); | ||||||
|                             pgParent.Groups.Remove(group); |                             pgParent.Groups.Remove(group); | ||||||
|                             _targetGroup.AddGroup(group, true, true); |                             _targetGroup.AddGroup(group, true, true); | ||||||
|  |                             addDatabase.GroupsById.Add(group.Uuid, group); | ||||||
|  |                             addDatabase.Elements.Add(group); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| 		    } | 		    } | ||||||
| 			 |  | ||||||
| 			 |  | ||||||
| 			_onFinishToRun = new ActionOnFinish(ActiveActivity, (success, message, activity) => |  | ||||||
| 			{ |  | ||||||
| 				if (!success) |  | ||||||
| 				{	// Let's not bother recovering from a failure. |  | ||||||
| 					_app.LockDatabase(false); |  | ||||||
| 				} |  | ||||||
| 			}, OnFinishToRun); |  | ||||||
|  |  | ||||||
| 			// Save | 		     | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, false); |              | ||||||
| 			save.SetStatusLogger(StatusLogger); | 		     | ||||||
| 			save.Run(); | 		    //first save the database where we added the elements | ||||||
|  |             var allDatabasesToSave = new List<Database> {addDatabase}; | ||||||
|  |             //then all databases where we removed elements: | ||||||
|  | 		    removeDatabases.RemoveWhere(db => db == addDatabase); | ||||||
|  |             allDatabasesToSave.AddRange(removeDatabases); | ||||||
|  |  | ||||||
|  | 		    int indexToSave = 0; | ||||||
|  | 		    bool allSavesSuccess = true; | ||||||
|  | 		    void ContinueSave(bool success, string message, Activity activeActivity) | ||||||
|  | 		    { | ||||||
|  | 		        allSavesSuccess &= success; | ||||||
|  |                 indexToSave++; | ||||||
|  | 		        if (indexToSave == allDatabasesToSave.Count) | ||||||
|  | 		        { | ||||||
|  | 		            OnFinishToRun.SetResult(allSavesSuccess); | ||||||
|  | 		            OnFinishToRun.Run(); | ||||||
|  | 		            return; | ||||||
|  | 		        } | ||||||
|  | 		        SaveDb saveDb = new SaveDb(_ctx, _app, allDatabasesToSave[indexToSave], new ActionOnFinish(activeActivity, ContinueSave), false); | ||||||
|  | 		        saveDb.SetStatusLogger(StatusLogger); | ||||||
|  | 		        saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1; | ||||||
|  | 		        saveDb.Run(); | ||||||
|  | 		    } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		    SaveDb save = new SaveDb(_ctx, _app, allDatabasesToSave[0], new ActionOnFinish(ActiveActivity, ContinueSave), false); | ||||||
|  |             save.SetStatusLogger(StatusLogger); | ||||||
|  | 		    save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1; | ||||||
|  |             save.Run(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file | |||||||
|   */ |   */ | ||||||
|  |  | ||||||
| using System; | using System; | ||||||
|  | using Android; | ||||||
| using Android.App; | using Android.App; | ||||||
| using Android.Content; | using Android.Content; | ||||||
| using Android.OS; | using Android.OS; | ||||||
| @@ -28,8 +29,14 @@ namespace keepass2android | |||||||
| 		protected bool Success; | 		protected bool Success; | ||||||
| 		protected String Message; | 		protected String Message; | ||||||
| 		protected Exception Exception; | 		protected Exception Exception; | ||||||
| 		 |  | ||||||
| 		protected OnFinish BaseOnFinish; | 	    protected bool ImportantMessage | ||||||
|  | 	    { | ||||||
|  | 	        get; | ||||||
|  | 	        set; | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  |         protected OnFinish BaseOnFinish; | ||||||
| 		protected Handler Handler; | 		protected Handler Handler; | ||||||
| 		private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired | 		private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired | ||||||
| 	    private Activity _activeActivity; | 	    private Activity _activeActivity; | ||||||
| @@ -77,20 +84,22 @@ namespace keepass2android | |||||||
| 			Handler = null; | 			Handler = null; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public void SetResult(bool success, string message, Exception exception) { | 		public void SetResult(bool success, string message, bool importantMessage, Exception exception) { | ||||||
| 			Success = success; | 			Success = success; | ||||||
| 			Message = message; | 			Message = message; | ||||||
|  | 		    ImportantMessage = importantMessage; | ||||||
| 			Exception = exception; | 			Exception = exception; | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		public void SetResult(bool success) { |  | ||||||
|  | 	    public void SetResult(bool success) { | ||||||
| 			Success = success; | 			Success = success; | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		public virtual void Run() { | 		public virtual void Run() { | ||||||
| 			if (BaseOnFinish == null) return; | 			if (BaseOnFinish == null) return; | ||||||
| 			// Pass on result on call finish | 			// Pass on result on call finish | ||||||
| 			BaseOnFinish.SetResult(Success, Message, Exception); | 			BaseOnFinish.SetResult(Success, Message, ImportantMessage, Exception); | ||||||
| 				 | 				 | ||||||
| 			if ( Handler != null ) { | 			if ( Handler != null ) { | ||||||
| 				Handler.Post(BaseOnFinish.Run);  | 				Handler.Post(BaseOnFinish.Run);  | ||||||
| @@ -100,14 +109,31 @@ namespace keepass2android | |||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		protected void DisplayMessage(Context ctx) { | 		protected void DisplayMessage(Context ctx) { | ||||||
| 			DisplayMessage(ctx, Message); | 			DisplayMessage(ctx, Message, ImportantMessage); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public static void DisplayMessage(Context ctx, string message) | 		public static void DisplayMessage(Context ctx, string message, bool makeDialog) | ||||||
| 		{ | 		{ | ||||||
| 			if ( !String.IsNullOrEmpty(message) ) { | 			if ( !String.IsNullOrEmpty(message) ) { | ||||||
| 				Kp2aLog.Log("OnFinish message: "+message); | 			    Kp2aLog.Log("OnFinish message: " + message); | ||||||
| 				Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show(); |                 if (makeDialog && ctx != null) | ||||||
|  | 			    { | ||||||
|  | 			        try | ||||||
|  | 			        { | ||||||
|  | 			            AlertDialog.Builder builder = new AlertDialog.Builder(ctx); | ||||||
|  | 			             | ||||||
|  | 			            builder.SetMessage(message) | ||||||
|  | 			                .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) | ||||||
|  | 			                .Show(); | ||||||
|  |  | ||||||
|  |                     } | ||||||
|  |                     catch (Exception) | ||||||
|  | 			        { | ||||||
|  | 			            Toast.MakeText(ctx, message, ToastLength.Long).Show(); | ||||||
|  | 			        } | ||||||
|  | 			    } | ||||||
|  |                 else | ||||||
|  |                     Toast.MakeText(ctx ?? Application.Context, message, ToastLength.Long).Show(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -50,9 +50,9 @@ namespace keepass2android | |||||||
| 	        } | 	        } | ||||||
| 	    } | 	    } | ||||||
|  |  | ||||||
|         protected void Finish(bool result, String message, Exception exception = null) { |         protected void Finish(bool result, String message, bool importantMessage = false, Exception exception = null) { | ||||||
| 			if ( OnFinishToRun != null ) { | 			if ( OnFinishToRun != null ) { | ||||||
| 				OnFinishToRun.SetResult(result, message, exception); | 				OnFinishToRun.SetResult(result, message, importantMessage, exception); | ||||||
| 				OnFinishToRun.Run(); | 				OnFinishToRun.Run(); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -34,7 +34,8 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 	public class SaveDb : RunnableOnFinish { | 	public class SaveDb : RunnableOnFinish { | ||||||
| 		private readonly IKp2aApp _app; | 		private readonly IKp2aApp _app; | ||||||
| 		private readonly bool _dontSave; | 	    private readonly Database _db; | ||||||
|  | 	    private readonly bool _dontSave; | ||||||
|  |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync | 		/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync | ||||||
| @@ -43,9 +44,10 @@ namespace keepass2android | |||||||
| 		private readonly Context _ctx; | 		private readonly Context _ctx; | ||||||
| 		private Thread _workerThread; | 		private Thread _workerThread; | ||||||
|  |  | ||||||
| 		public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, bool dontSave) | 		public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish, bool dontSave) | ||||||
| 			: base(ctx, finish) | 			: base(ctx, finish) | ||||||
| 		{ | 		{ | ||||||
|  | 		    _db = db; | ||||||
| 			_ctx = ctx; | 			_ctx = ctx; | ||||||
| 			_app = app; | 			_app = app; | ||||||
| 			_dontSave = dontSave; | 			_dontSave = dontSave; | ||||||
| @@ -59,46 +61,55 @@ namespace keepass2android | |||||||
| 		/// <param name="finish"></param> | 		/// <param name="finish"></param> | ||||||
| 		/// <param name="dontSave"></param> | 		/// <param name="dontSave"></param> | ||||||
| 		/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param> | 		/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param> | ||||||
| 		public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, bool dontSave, Stream streamForOrigFile) | 		public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, Database db, bool dontSave, Stream streamForOrigFile) | ||||||
| 			: base(ctx, finish) | 			: base(ctx, finish) | ||||||
| 		{ | 		{ | ||||||
|  | 		    _db = db; | ||||||
| 			_ctx = ctx; | 			_ctx = ctx; | ||||||
| 			_app = app; | 			_app = app; | ||||||
| 			_dontSave = dontSave; | 			_dontSave = dontSave; | ||||||
| 			_streamForOrigFile = streamForOrigFile; | 			_streamForOrigFile = streamForOrigFile; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish) | 		public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish) | ||||||
| 			: base(ctx, finish) | 			: base(ctx, finish) | ||||||
| 		{ | 		{ | ||||||
| 			_ctx = ctx; | 			_ctx = ctx; | ||||||
| 			_app = app; | 			_app = app; | ||||||
| 			_dontSave = false; | 		    _db = db; | ||||||
|  | 		    _dontSave = false; | ||||||
| 		} | 		} | ||||||
| 		 |  | ||||||
| 		 | 	    public bool ShowDatabaseIocInStatus { get; set; } | ||||||
| 		public override void Run () | 	     | ||||||
|  | 	    public override void Run () | ||||||
| 		{ | 		{ | ||||||
|  |  | ||||||
| 			if (!_dontSave) | 			if (!_dontSave) | ||||||
| 			{ | 			{ | ||||||
| 				try | 				try | ||||||
| 				{ | 				{ | ||||||
| 					if (_app.GetDb().CanWrite == false) | 					if (_db.CanWrite == false) | ||||||
| 					{ | 					{ | ||||||
| 						//this should only happen if there is a problem in the UI so that the user sees an edit interface. | 						//this should only happen if there is a problem in the UI so that the user sees an edit interface. | ||||||
| 						Finish(false,"Cannot save changes. File is read-only!"); | 						Finish(false,"Cannot save changes. File is read-only!"); | ||||||
| 						return; | 						return; | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					StatusLogger.UpdateMessage(UiStringKey.saving_database); | 				    string message = _app.GetResourceString(UiStringKey.saving_database); | ||||||
| 					IOConnectionInfo ioc = _app.GetDb().Ioc; |  | ||||||
|  | 				    if (ShowDatabaseIocInStatus) | ||||||
|  | 				        message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")"; | ||||||
|  |  | ||||||
|  |                     StatusLogger.UpdateMessage(message); | ||||||
|  |                      | ||||||
|  | 					IOConnectionInfo ioc = _db.Ioc; | ||||||
| 					IFileStorage fileStorage = _app.GetFileStorage(ioc); | 					IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||||
|  |  | ||||||
| 					if (_streamForOrigFile == null) | 					if (_streamForOrigFile == null) | ||||||
| 					{ | 					{ | ||||||
| 						if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) | 						if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) | ||||||
| 							|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving | 							|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving | ||||||
| 						{ | 						{ | ||||||
| 							PerformSaveWithoutCheck(fileStorage, ioc); | 							PerformSaveWithoutCheck(fileStorage, ioc); | ||||||
| 							Finish(true); | 							Finish(true); | ||||||
| @@ -109,8 +120,8 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 					if ( | 					if ( | ||||||
| 						(_streamForOrigFile != null) | 						(_streamForOrigFile != null) | ||||||
| 						|| fileStorage.CheckForFileChangeFast(ioc, _app.GetDb().LastFileVersion)  //first try to use the fast change detection | 						|| fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion)  //first try to use the fast change detection | ||||||
| 						|| (FileHashChanged(ioc, _app.GetDb().KpDatabase.HashOfFileOnDisk) == FileHashChange.Changed) //if that fails, hash the file and compare: | 						|| (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) == FileHashChange.Changed) //if that fails, hash the file and compare: | ||||||
| 						) | 						) | ||||||
| 					{ | 					{ | ||||||
|  |  | ||||||
| @@ -128,6 +139,7 @@ namespace keepass2android | |||||||
| 										//small. | 										//small. | ||||||
| 										MergeIn(fileStorage, ioc); | 										MergeIn(fileStorage, ioc); | ||||||
| 										PerformSaveWithoutCheck(fileStorage, ioc); | 										PerformSaveWithoutCheck(fileStorage, ioc); | ||||||
|  |                                         _db.UpdateGlobals(); | ||||||
| 										Finish(true); | 										Finish(true); | ||||||
| 									}; | 									}; | ||||||
| 								RunInWorkerThread(runHandler); | 								RunInWorkerThread(runHandler); | ||||||
| @@ -217,13 +229,13 @@ namespace keepass2android | |||||||
| 			StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.SynchronizingDatabase)); | 			StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.SynchronizingDatabase)); | ||||||
|  |  | ||||||
| 			PwDatabase pwImp = new PwDatabase(); | 			PwDatabase pwImp = new PwDatabase(); | ||||||
| 			PwDatabase pwDatabase = _app.GetDb().KpDatabase; | 			PwDatabase pwDatabase = _db.KpDatabase; | ||||||
| 			pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); | 			pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); | ||||||
| 			pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); | 			pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); | ||||||
| 			pwImp.MasterKey = pwDatabase.MasterKey; | 			pwImp.MasterKey = pwDatabase.MasterKey; | ||||||
| 			var stream = GetStreamForBaseFile(fileStorage, ioc); | 			var stream = GetStreamForBaseFile(fileStorage, ioc); | ||||||
|  |  | ||||||
| 			_app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); | 			_db.DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); | ||||||
| 			 | 			 | ||||||
|  |  | ||||||
| 			pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);  | 			pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);  | ||||||
| @@ -249,8 +261,8 @@ namespace keepass2android | |||||||
| 		private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) | 		private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) | ||||||
| 		{ | 		{ | ||||||
| 			StatusLogger.UpdateSubMessage(""); | 			StatusLogger.UpdateSubMessage(""); | ||||||
| 			_app.GetDb().SaveData(); | 			_db.SaveData(); | ||||||
| 			_app.GetDb().LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); | 			_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		public byte[] HashOriginalFile(IOConnectionInfo iocFile) | 		public byte[] HashOriginalFile(IOConnectionInfo iocFile) | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ namespace keepass2android | |||||||
| 		public override void Run () | 		public override void Run () | ||||||
| 		{ | 		{ | ||||||
| 			StatusLogger.UpdateMessage(UiStringKey.SettingPassword); | 			StatusLogger.UpdateMessage(UiStringKey.SettingPassword); | ||||||
| 			PwDatabase pm = _app.GetDb().KpDatabase; | 			PwDatabase pm = _app.CurrentDb.KpDatabase; | ||||||
| 			CompositeKey newKey = new CompositeKey (); | 			CompositeKey newKey = new CompositeKey (); | ||||||
| 			if (String.IsNullOrEmpty (_password) == false) { | 			if (String.IsNullOrEmpty (_password) == false) { | ||||||
| 				newKey.AddUserKey (new KcpPassword (_password));  | 				newKey.AddUserKey (new KcpPassword (_password));  | ||||||
| @@ -74,7 +74,7 @@ namespace keepass2android | |||||||
|  |  | ||||||
| 			// Save Database | 			// Save Database | ||||||
| 			_onFinishToRun = new AfterSave(ActiveActivity, previousKey, previousMasterKeyChanged, pm, OnFinishToRun); | 			_onFinishToRun = new AfterSave(ActiveActivity, previousKey, previousMasterKeyChanged, pm, OnFinishToRun); | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave); | 			SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, _dontSave); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 			save.SetStatusLogger(StatusLogger); | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ namespace keepass2android | |||||||
| 		 | 		 | ||||||
| 		public override void Run() { | 		public override void Run() { | ||||||
| 			// Commit to disk | 			// Commit to disk | ||||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | 			SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); | ||||||
| 			save.SetStatusLogger(StatusLogger); | 			save.SetStatusLogger(StatusLogger); | ||||||
| 			save.Run(); | 			save.Run(); | ||||||
| 		} | 		} | ||||||
| @@ -59,7 +59,7 @@ namespace keepass2android | |||||||
| 					if ( parent != null ) { | 					if ( parent != null ) { | ||||||
|  |  | ||||||
| 						// Mark parent group dirty | 						// Mark parent group dirty | ||||||
| 						_app.GetDb().Dirty.Add(parent); | 						_app.DirtyGroups.Add(parent); | ||||||
| 							 | 							 | ||||||
| 					} | 					} | ||||||
| 					 | 					 | ||||||
|   | |||||||
| @@ -67,5 +67,4 @@ | |||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Folder Include="libs\" /> |     <Folder Include="libs\" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|    | </Project> | ||||||
| </Project> |  | ||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/PCloudBindings/Jars/pcloud-sdk-android-1.1.0.aar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/PCloudBindings/Jars/pcloud-sdk-android-1.1.0.aar
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								src/PCloudBindings/Jars/pcloud-sdk-java-core-1.1.0.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/PCloudBindings/Jars/pcloud-sdk-java-core-1.1.0.jar
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -67,7 +67,7 @@ | |||||||
|  |  | ||||||
|     <None Include="Jars\AboutJars.txt" /> |     <None Include="Jars\AboutJars.txt" /> | ||||||
|     <None Include="Additions\AboutAdditions.txt" /> |     <None Include="Additions\AboutAdditions.txt" /> | ||||||
|     <LibraryProjectZip Include="Jars\pcloud-sdk-android-1.0.1.aar" /> |     <LibraryProjectZip Include="Jars\pcloud-sdk-android-1.1.0.aar" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <TransformFile Include="Transforms\Metadata.xml" /> |     <TransformFile Include="Transforms\Metadata.xml" /> | ||||||
| @@ -84,7 +84,7 @@ | |||||||
|   </Target> |   </Target> | ||||||
|   --> |   --> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <EmbeddedReferenceJar Include="Jars\pcloud-sdk-java-core-1.0.1.jar" /> |     <EmbeddedReferenceJar Include="Jars\pcloud-sdk-java-core-1.1.0.jar" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
| </Project> | </Project> | ||||||
|   | |||||||
| @@ -26,5 +26,5 @@ using Android.App; | |||||||
| // You can specify all the values or you can default the Build and Revision Numbers  | // You can specify all the values or you can default the Build and Revision Numbers  | ||||||
| // by using the '*' as shown below: | // by using the '*' as shown below: | ||||||
| // [assembly: AssemblyVersion("1.0.*")] | // [assembly: AssemblyVersion("1.0.*")] | ||||||
| [assembly: AssemblyVersion("1.0.0.0")] | [assembly: AssemblyVersion("1.1.0.0")] | ||||||
| [assembly: AssemblyFileVersion("1.0.0.0")] | [assembly: AssemblyFileVersion("1.1.0.0")] | ||||||
|   | |||||||
| @@ -53,8 +53,8 @@ | |||||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> |     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <LibraryProjectZip Include="..\java\Keepass2AndroidPluginSDK2\app\build\outputs\aar\app-release.aar"> |     <LibraryProjectZip Include="..\java\Keepass2AndroidPluginSDK2\app\build\outputs\aar\Keepass2AndroidPluginSDK2-release.aar"> | ||||||
|       <Link>Jars\app-release.aar</Link> |       <Link>Jars\Keepass2AndroidPluginSDK2-release.aar</Link> | ||||||
|     </LibraryProjectZip> |     </LibraryProjectZip> | ||||||
|     <None Include="Jars\AboutJars.txt" /> |     <None Include="Jars\AboutJars.txt" /> | ||||||
|     <None Include="Additions\AboutAdditions.txt" /> |     <None Include="Additions\AboutAdditions.txt" /> | ||||||
|   | |||||||
| @@ -7,4 +7,7 @@ call gradlew assemble | |||||||
| cd ..\Keepass2AndroidPluginSDK2 | cd ..\Keepass2AndroidPluginSDK2 | ||||||
| call gradlew assemble | call gradlew assemble | ||||||
|  |  | ||||||
| cd ..\..\build-scripts | cd ..\PluginQR | ||||||
|  | call gradlew assemble | ||||||
|  |  | ||||||
|  | cd ..\..\build-scripts | ||||||
|   | |||||||
| @@ -15,4 +15,8 @@ pushd Keepass2AndroidPluginSDK2 | |||||||
| ./gradlew assemble | ./gradlew assemble | ||||||
| popd | popd | ||||||
|  |  | ||||||
|  | pushd PluginQR | ||||||
|  | ./gradlew assemble | ||||||
|  | popd | ||||||
|  |  | ||||||
| popd | popd | ||||||
|   | |||||||
| @@ -35,8 +35,12 @@ dependencies { | |||||||
|     compile('com.onedrive.sdk:onedrive-sdk-android:1.2.0') { |     compile('com.onedrive.sdk:onedrive-sdk-android:1.2.0') { | ||||||
|         transitive = false |         transitive = false | ||||||
|     } |     } | ||||||
|     compile 'com.pcloud.sdk:java-core:1.0.1' |     compile 'com.pcloud.sdk:java-core:1.1.0' | ||||||
|     compile 'com.pcloud.sdk:android:1.0.1' |     compile 'com.pcloud.sdk:android:1.1.0' | ||||||
|  |     compile('com.microsoft.graph:msgraph-sdk-android:1.2.+') | ||||||
|  |     compile ('com.microsoft.identity.client:msal:0.1.+') { | ||||||
|  |         exclude group: 'com.android.support', module: 'appcompat-v7' | ||||||
|  |     } | ||||||
|     compile 'com.google.code.gson:gson:2.3.1' |     compile 'com.google.code.gson:gson:2.3.1' | ||||||
|     compile 'com.microsoft.services.msa:msa-auth:0.8.6' |     compile 'com.microsoft.services.msa:msa-auth:0.8.6' | ||||||
|     compile 'com.microsoft.aad:adal:1.14.0' |     compile 'com.microsoft.aad:adal:1.14.0' | ||||||
|   | |||||||
| @@ -0,0 +1,486 @@ | |||||||
|  | package keepass2android.javafilestorage; | ||||||
|  |  | ||||||
|  | import android.app.Activity; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.util.Log; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import com.microsoft.graph.core.ClientException; | ||||||
|  | import com.microsoft.graph.core.DefaultClientConfig; | ||||||
|  | import com.microsoft.graph.core.GraphErrorCodes; | ||||||
|  | import com.microsoft.graph.extensions.DriveItem; | ||||||
|  | import com.microsoft.graph.extensions.GraphServiceClient; | ||||||
|  | import com.microsoft.graph.extensions.IDriveItemCollectionPage; | ||||||
|  | import com.microsoft.graph.extensions.IDriveItemCollectionRequestBuilder; | ||||||
|  | import com.microsoft.graph.extensions.IDriveItemRequest; | ||||||
|  | import com.microsoft.graph.extensions.IDriveItemRequestBuilder; | ||||||
|  | import com.microsoft.graph.extensions.IGraphServiceClient; | ||||||
|  | import com.microsoft.identity.client.AuthenticationCallback; | ||||||
|  | import com.microsoft.identity.client.AuthenticationResult; | ||||||
|  | import com.microsoft.identity.client.MsalException; | ||||||
|  | import com.microsoft.identity.client.PublicClientApplication; | ||||||
|  | import com.microsoft.identity.client.User; | ||||||
|  |  | ||||||
|  | import java.io.FileNotFoundException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.concurrent.CountDownLatch; | ||||||
|  |  | ||||||
|  | import keepass2android.javafilestorage.onedrive2.GraphServiceClientManager; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Created by Philipp on 20.11.2016. | ||||||
|  |  */ | ||||||
|  | public class OneDriveStorage2 extends JavaFileStorageBase | ||||||
|  | { | ||||||
|  |     PublicClientApplication mPublicClientApp; | ||||||
|  |  | ||||||
|  |     final HashMap<String /*userid*/, IGraphServiceClient> mClientByUser = new HashMap<String /*userid*/, IGraphServiceClient>(); | ||||||
|  |  | ||||||
|  |     private static final String[] scopes = {"openid","offline_access", "https://graph.microsoft.com/Files.ReadWrite","https://graph.microsoft.com/User.Read"}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     public OneDriveStorage2(final Activity context, final String clientId) { | ||||||
|  |  | ||||||
|  |         mPublicClientApp = new PublicClientApplication(context, clientId); | ||||||
|  |         initAuthenticator(context); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean requiresSetup(String path) | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         return !isConnected(null); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void startSelectFile(FileStorageSetupInitiatorActivity activity, boolean isForSave, int requestCode) { | ||||||
|  |  | ||||||
|  |         initAuthenticator((Activity)activity.getActivity()); | ||||||
|  |  | ||||||
|  |         String path = getProtocolId()+":///"; | ||||||
|  |         Log.d("KP2AJ", "startSelectFile "+path+", connected: "+path); | ||||||
|  | 		if (isConnected(null)) | ||||||
|  | 		{ | ||||||
|  | 			Intent intent = new Intent(); | ||||||
|  | 			intent.putExtra(EXTRA_IS_FOR_SAVE, isForSave); | ||||||
|  | 			intent.putExtra(EXTRA_PATH, path); | ||||||
|  | 			activity.onImmediateResult(requestCode, RESULT_FILECHOOSER_PREPARED, intent); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  |         { | ||||||
|  |             activity.startSelectFileProcess(path, isForSave, requestCode); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private boolean isConnected(String path) { | ||||||
|  |         try { | ||||||
|  |             if (tryGetMsGraphClient(path) == null) | ||||||
|  |                 try { | ||||||
|  |                     final CountDownLatch latch = new CountDownLatch(1); | ||||||
|  |  | ||||||
|  |                     Log.d("KP2AJ", "trying silent login"); | ||||||
|  |  | ||||||
|  |                     String userId = extractUserId(path); | ||||||
|  |                     final MsalException[] _exception = {null}; | ||||||
|  |                     final AuthenticationResult[] _result = {null}; | ||||||
|  |                     User user = mPublicClientApp.getUser(userId); | ||||||
|  |                     mPublicClientApp.acquireTokenSilentAsync(scopes, user, | ||||||
|  |                             new AuthenticationCallback() { | ||||||
|  |  | ||||||
|  |                                 @Override | ||||||
|  |                                 public void onSuccess(AuthenticationResult authenticationResult) { | ||||||
|  |                                     _result[0] = authenticationResult; | ||||||
|  |                                     latch.countDown(); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 @Override | ||||||
|  |                                 public void onError(MsalException exception) { | ||||||
|  |                                     _exception[0] = exception; | ||||||
|  |                                     latch.countDown(); | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 @Override | ||||||
|  |                                 public void onCancel() { | ||||||
|  |                                     latch.countDown(); | ||||||
|  |  | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                     latch.await(); | ||||||
|  |                     if (_result[0] != null) { | ||||||
|  |                         buildClient(_result[0]); | ||||||
|  |                     } else if (_exception[0] != null){ | ||||||
|  |                         _exception[0].printStackTrace(); | ||||||
|  |                     } | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             return tryGetMsGraphClient(path) != null; | ||||||
|  |         } | ||||||
|  |         catch (Exception e) | ||||||
|  |         { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private IGraphServiceClient tryGetMsGraphClient(String path) throws Exception | ||||||
|  |     { | ||||||
|  |         String userId = extractUserId(path); | ||||||
|  |         if (mClientByUser.containsKey(userId)) | ||||||
|  |             return mClientByUser.get(userId); | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private String extractUserId(String path) throws Exception { | ||||||
|  |         String pathWithoutProtocol = removeProtocol(path); | ||||||
|  |         String[] parts = pathWithoutProtocol.split("/",1); | ||||||
|  |         if (parts.length != 2 || ("".equals(parts[0]))) | ||||||
|  |         { | ||||||
|  |             throw new Exception("path does not contain user"); | ||||||
|  |         } | ||||||
|  |         return parts[0]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void initAuthenticator(Activity activity) { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void prepareFileUsage(FileStorageSetupInitiatorActivity activity, String path, int requestCode, boolean alwaysReturnSuccess) { | ||||||
|  |         initAuthenticator((Activity)activity.getActivity()); | ||||||
|  |         if (isConnected(path)) | ||||||
|  |         { | ||||||
|  |             Intent intent = new Intent(); | ||||||
|  |             intent.putExtra(EXTRA_PATH, path); | ||||||
|  |             activity.onImmediateResult(requestCode, RESULT_FILEUSAGE_PREPARED, intent); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             activity.startFileUsageProcess(path, requestCode, alwaysReturnSuccess); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getProtocolId() { | ||||||
|  |         return "onedrive"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void prepareFileUsage(Context appContext, String path) throws UserInteractionRequiredException { | ||||||
|  |         if (!isConnected(null)) | ||||||
|  |         { | ||||||
|  |             throw new UserInteractionRequiredException(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onCreate(FileStorageSetupActivity activity, Bundle savedInstanceState) { | ||||||
|  |  | ||||||
|  |         Log.d("KP2AJ", "OnCreate"); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onResume(final FileStorageSetupActivity activity) { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private IGraphServiceClient buildClient(AuthenticationResult authenticationResult) throws InterruptedException { | ||||||
|  |  | ||||||
|  |         IGraphServiceClient newClient = new GraphServiceClient.Builder() | ||||||
|  |                 .fromConfig(DefaultClientConfig.createWithAuthenticationProvider(new GraphServiceClientManager(authenticationResult.getAccessToken()))) | ||||||
|  |                 .buildClient(); | ||||||
|  |         mClientByUser.put(authenticationResult.getUser().getUserIdentifier(), newClient); | ||||||
|  |  | ||||||
|  |         return newClient; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     String removeProtocol(String path) throws Exception { | ||||||
|  |         if (path == null) | ||||||
|  |             return null; | ||||||
|  |         return path.substring(getProtocolId().length()+3); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getDisplayName(String path) { | ||||||
|  |  | ||||||
|  |         if (path == null) | ||||||
|  |             return null; | ||||||
|  |  | ||||||
|  |         return path; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getFilename(String path) throws Exception { | ||||||
|  |         return path.substring(path.lastIndexOf("/")+1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean checkForFileChangeFast(String path, String previousFileVersion) throws Exception { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String getCurrentFileVersionFast(String path) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class ClientAndPath | ||||||
|  |     { | ||||||
|  |         public IGraphServiceClient client; | ||||||
|  |         public String oneDrivePath; | ||||||
|  |         public IDriveItemRequestBuilder getPathItem() | ||||||
|  |         { | ||||||
|  |             IDriveItemRequestBuilder pathItem = client.getDrive().getRoot(); | ||||||
|  |             if ("".equals(oneDrivePath) == false) { | ||||||
|  |                 pathItem = pathItem.getItemWithPath(oneDrivePath); | ||||||
|  |             } | ||||||
|  |             return pathItem; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public InputStream openFileForRead(String path) throws Exception { | ||||||
|  |         try { | ||||||
|  |             ClientAndPath clientAndpath = getOneDriveClientAndPath(path); | ||||||
|  |             logDebug("openFileForRead. Path="+path); | ||||||
|  |             InputStream result = clientAndpath.client.getDrive() | ||||||
|  |                     .getRoot() | ||||||
|  |                     .getItemWithPath(clientAndpath.oneDrivePath) | ||||||
|  |                     .getContent() | ||||||
|  |                     .buildRequest() | ||||||
|  |                     .get(); | ||||||
|  |             logDebug("ok"); | ||||||
|  |             return result; | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |         catch (ClientException e) | ||||||
|  |         { | ||||||
|  |             throw convertException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private ClientAndPath getOneDriveClientAndPath(String path) throws Exception { | ||||||
|  |         ClientAndPath result = new ClientAndPath(); | ||||||
|  |  | ||||||
|  |         String pathWithoutProtocol = removeProtocol(path); | ||||||
|  |         String[] parts = pathWithoutProtocol.split("/",2); | ||||||
|  |         if (parts.length != 2 || ("".equals(parts[0]))) | ||||||
|  |         { | ||||||
|  |             throw new Exception("path does not contain user"); | ||||||
|  |         } | ||||||
|  |         result.client = mClientByUser.get(parts[0]); | ||||||
|  |         result.oneDrivePath = parts[1]; | ||||||
|  |         return result; | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     private Exception convertException(ClientException e) { | ||||||
|  |         if (e.isError(GraphErrorCodes.ItemNotFound)) | ||||||
|  |             return new FileNotFoundException(e.getMessage()); | ||||||
|  |         return e; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void uploadFile(String path, byte[] data, boolean writeTransactional) throws Exception { | ||||||
|  |         try { | ||||||
|  |             ClientAndPath clientAndPath = getOneDriveClientAndPath(path); | ||||||
|  |             clientAndPath.client.getDrive() | ||||||
|  |                     .getRoot() | ||||||
|  |                     .getItemWithPath(clientAndPath.oneDrivePath) | ||||||
|  |                     .getContent() | ||||||
|  |                     .buildRequest() | ||||||
|  |                     .put(data); | ||||||
|  |         } catch (ClientException e) { | ||||||
|  |             throw convertException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String createFolder(String parentPath, String newDirName) throws Exception { | ||||||
|  |         throw new Exception("not implemented."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String createFilePath(String parentPath, String newFileName) throws Exception { | ||||||
|  |         String path = parentPath; | ||||||
|  |         if (!path.endsWith("/")) | ||||||
|  |             path = path + "/"; | ||||||
|  |         path = path + newFileName; | ||||||
|  |  | ||||||
|  |         return path; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public List<FileEntry> listFiles(String parentPath) throws Exception { | ||||||
|  |         try { | ||||||
|  |             ArrayList<FileEntry> result = new ArrayList<FileEntry>(); | ||||||
|  |             ClientAndPath clientAndPath = getOneDriveClientAndPath(parentPath); | ||||||
|  |             parentPath = clientAndPath.oneDrivePath; | ||||||
|  |  | ||||||
|  |             IDriveItemCollectionPage itemsPage = clientAndPath.getPathItem() | ||||||
|  |                     .getChildren() | ||||||
|  |                     .buildRequest() | ||||||
|  |                     .get(); | ||||||
|  |             if (parentPath.endsWith("/")) | ||||||
|  |                 parentPath = parentPath.substring(0,parentPath.length()-1); | ||||||
|  |             while (true) | ||||||
|  |             { | ||||||
|  |                 List<DriveItem> items = itemsPage.getCurrentPage(); | ||||||
|  |                 if (items.isEmpty()) | ||||||
|  |                     return result; | ||||||
|  |  | ||||||
|  |                 for (DriveItem i: items) | ||||||
|  |                 { | ||||||
|  |                     FileEntry e = getFileEntry(parentPath + "/" + i.name, i); | ||||||
|  |                     Log.d("KP2AJ", e.path); | ||||||
|  |                     result.add(e); | ||||||
|  |                 } | ||||||
|  |                 IDriveItemCollectionRequestBuilder nextPageReqBuilder = itemsPage.getNextPage(); | ||||||
|  |                 if (nextPageReqBuilder == null) | ||||||
|  |                     return result; | ||||||
|  |                 itemsPage = nextPageReqBuilder.buildRequest().get(); | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |         } catch (ClientException e) { | ||||||
|  |             throw convertException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private FileEntry getFileEntry(String path, DriveItem i) { | ||||||
|  |         FileEntry e = new FileEntry(); | ||||||
|  |         if (i.size != null) | ||||||
|  |             e.sizeInBytes = i.size; | ||||||
|  |         else if ((i.remoteItem != null) && (i.remoteItem.size != null)) | ||||||
|  |             e.sizeInBytes = i.remoteItem.size; | ||||||
|  |  | ||||||
|  |         e.displayName = i.name; | ||||||
|  |         e.canRead = e.canWrite = true; | ||||||
|  |         e.path = getProtocolId() +"://"+path; | ||||||
|  |         if (i.lastModifiedDateTime != null) | ||||||
|  |             e.lastModifiedTime = i.lastModifiedDateTime.getTimeInMillis(); | ||||||
|  |         else if ((i.remoteItem != null)&&(i.remoteItem.lastModifiedDateTime != null)) | ||||||
|  |             e.lastModifiedTime = i.remoteItem.lastModifiedDateTime.getTimeInMillis(); | ||||||
|  |         e.isDirectory = (i.folder != null) || ((i.remoteItem != null) && (i.remoteItem.folder != null)); | ||||||
|  |         return e; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public FileEntry getFileEntry(String filename) throws Exception { | ||||||
|  |         try { | ||||||
|  |  | ||||||
|  |             ClientAndPath clientAndPath = getOneDriveClientAndPath(filename); | ||||||
|  |             IDriveItemRequestBuilder pathItem = clientAndPath.getPathItem(); | ||||||
|  |  | ||||||
|  |             IDriveItemRequest request = pathItem.buildRequest(); | ||||||
|  |             DriveItem item = request.get(); | ||||||
|  |             return getFileEntry(filename, item); | ||||||
|  |         } catch (ClientException e) { | ||||||
|  |             throw convertException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void delete(String path) throws Exception { | ||||||
|  |         try { | ||||||
|  |             ClientAndPath clientAndPath = getOneDriveClientAndPath(path); | ||||||
|  |             clientAndPath.client.getDrive() | ||||||
|  |                     .getRoot() | ||||||
|  |                     .getItemWithPath(clientAndPath.oneDrivePath) | ||||||
|  |                     .buildRequest() | ||||||
|  |                     .delete(); | ||||||
|  |         } catch (ClientException e) { | ||||||
|  |             throw convertException(e); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     boolean acquireTokenRunning = false; | ||||||
|  |     @Override | ||||||
|  |     public void onStart(final FileStorageSetupActivity activity) { | ||||||
|  |         Log.d("KP2AJ", "onStart " + activity.getPath()); | ||||||
|  |         if (activity.getProcessName().equals(PROCESS_NAME_SELECTFILE)) | ||||||
|  |             activity.getState().putString(EXTRA_PATH, activity.getPath()); | ||||||
|  |  | ||||||
|  |         String userId = activity.getState().getString("OneDriveUser"); | ||||||
|  |         if (mClientByUser.containsKey(userId)) { | ||||||
|  |             finishActivityWithSuccess(activity); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         JavaFileStorage.FileStorageSetupActivity storageSetupAct = activity; | ||||||
|  |  | ||||||
|  |         final CountDownLatch latch = new CountDownLatch(1); | ||||||
|  |         final AuthenticationResult[] _authenticationResult = {null}; | ||||||
|  |         MsalException _exception[] = {null}; | ||||||
|  |  | ||||||
|  |         if (!acquireTokenRunning) { | ||||||
|  |             acquireTokenRunning = true; | ||||||
|  |  | ||||||
|  |             mPublicClientApp.acquireToken((Activity) activity, scopes, new AuthenticationCallback() { | ||||||
|  |                 @Override | ||||||
|  |                 public void onSuccess(AuthenticationResult authenticationResult) { | ||||||
|  |                     Log.i(TAG, "authenticating successful"); | ||||||
|  |  | ||||||
|  |                     try { | ||||||
|  |                         buildClient(authenticationResult); | ||||||
|  |                     } catch (InterruptedException e) { | ||||||
|  |                         e.printStackTrace(); | ||||||
|  |                     } | ||||||
|  |                     activity.getState().putString(EXTRA_PATH, getProtocolPrefix() + authenticationResult.getUser().getUserIdentifier() + "/"); | ||||||
|  |  | ||||||
|  |                     finishActivityWithSuccess(activity); | ||||||
|  |                     acquireTokenRunning = false; | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 @Override | ||||||
|  |                 public void onError(MsalException exception) { | ||||||
|  |                     Log.i(TAG, "authenticating not successful"); | ||||||
|  |                     Intent data = new Intent(); | ||||||
|  |                     data.putExtra(EXTRA_ERROR_MESSAGE, "authenticating not successful"); | ||||||
|  |                     ((Activity) activity).setResult(Activity.RESULT_CANCELED, data); | ||||||
|  |                     ((Activity) activity).finish(); | ||||||
|  |                     acquireTokenRunning = false; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 @Override | ||||||
|  |                 public void onCancel() { | ||||||
|  |  | ||||||
|  |                     Log.i(TAG, "authenticating cancelled"); | ||||||
|  |                     Intent data = new Intent(); | ||||||
|  |                     data.putExtra(EXTRA_ERROR_MESSAGE, "authenticating not cancelled"); | ||||||
|  |                     ((Activity) activity).setResult(Activity.RESULT_CANCELED, data); | ||||||
|  |                     ((Activity) activity).finish(); | ||||||
|  |                     acquireTokenRunning = false; | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onActivityResult(FileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) { | ||||||
|  |         mPublicClientApp.handleInteractiveRequestRedirect(requestCode, resultCode, data); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -14,6 +14,7 @@ import java.util.ArrayList; | |||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.NoSuchElementException; | import java.util.NoSuchElementException; | ||||||
|  | import java.util.UUID; | ||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
| import com.pcloud.sdk.ApiClient; | import com.pcloud.sdk.ApiClient; | ||||||
| @@ -27,6 +28,7 @@ import com.pcloud.sdk.PCloudSdk; | |||||||
| import com.pcloud.sdk.RemoteEntry; | import com.pcloud.sdk.RemoteEntry; | ||||||
| import com.pcloud.sdk.RemoteFile; | import com.pcloud.sdk.RemoteFile; | ||||||
| import com.pcloud.sdk.RemoteFolder; | import com.pcloud.sdk.RemoteFolder; | ||||||
|  | import com.pcloud.sdk.UploadOptions; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * FileStorage implementation for PCloud provider. |  * FileStorage implementation for PCloud provider. | ||||||
| @@ -52,7 +54,7 @@ public class PCloudFileStorage extends JavaFileStorageBase | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public boolean requiresSetup(String path) { |     public boolean requiresSetup(String path) { | ||||||
|         return true; |         return !this.isConnected(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -136,7 +138,9 @@ public class PCloudFileStorage extends JavaFileStorageBase | |||||||
|         RemoteFolder remoteFolder = this.getRemoteFolderByPath(filePath); |         RemoteFolder remoteFolder = this.getRemoteFolderByPath(filePath); | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             this.apiClient.createFile(remoteFolder, filename, dataSource).execute(); |             RemoteFile remoteFile = this.apiClient.createFile( | ||||||
|  |                 remoteFolder, filename, dataSource, null, null, UploadOptions.OVERRIDE_FILE | ||||||
|  |             ).execute(); | ||||||
|         } catch (ApiError e) { |         } catch (ApiError e) { | ||||||
|             throw convertApiError(e); |             throw convertApiError(e); | ||||||
|         } |         } | ||||||
| @@ -372,7 +376,7 @@ public class PCloudFileStorage extends JavaFileStorageBase | |||||||
|  |  | ||||||
|     private Exception convertApiError(ApiError e) { |     private Exception convertApiError(ApiError e) { | ||||||
|         String strErrorCode = String.valueOf(e.errorCode()); |         String strErrorCode = String.valueOf(e.errorCode()); | ||||||
|         if (strErrorCode.startsWith("1") || "2000".equals(strErrorCode)) { |         if (strErrorCode.startsWith("1") || "2000".equals(strErrorCode) || "2095".equals(strErrorCode)) { | ||||||
|             this.clearAuthToken(); |             this.clearAuthToken(); | ||||||
|             return new UserInteractionRequiredException("Unlinked from PCloud! User must re-link.", e); |             return new UserInteractionRequiredException("Unlinked from PCloud! User must re-link.", e); | ||||||
|         } else if (strErrorCode.startsWith("2")) { |         } else if (strErrorCode.startsWith("2")) { | ||||||
|   | |||||||
| @@ -24,7 +24,6 @@ import com.jcraft.jsch.UserInfo; | |||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; |  | ||||||
|  |  | ||||||
| public class SftpStorage extends JavaFileStorageBase { | public class SftpStorage extends JavaFileStorageBase { | ||||||
|  |  | ||||||
| @@ -358,12 +357,10 @@ public class SftpStorage extends JavaFileStorageBase { | |||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@NonNull |  | ||||||
| 	private String getBaseDir() { | 	private String getBaseDir() { | ||||||
| 		return _appContext.getFilesDir().getAbsolutePath(); | 		return _appContext.getFilesDir().getAbsolutePath(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	@NonNull |  | ||||||
| 	private String getKeyFileName() { | 	private String getKeyFileName() { | ||||||
| 		return getBaseDir() + "/id_kp2a_rsa"; | 		return getBaseDir() + "/id_kp2a_rsa"; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ import java.util.Date; | |||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| import javax.net.ssl.SSLContext; | import javax.net.ssl.SSLContext; | ||||||
| import javax.net.ssl.SSLSocketFactory; | import javax.net.ssl.SSLSocketFactory; | ||||||
| @@ -70,9 +71,18 @@ public class WebDavStorage extends JavaFileStorageBase { | |||||||
|  |  | ||||||
|         String scheme = filename.substring(0, filename.indexOf("://")); |         String scheme = filename.substring(0, filename.indexOf("://")); | ||||||
|         filename = filename.substring(scheme.length() + 3); |         filename = filename.substring(scheme.length() + 3); | ||||||
|         String userPwd = filename.substring(0, filename.indexOf('@')); |         int idxAt = filename.indexOf('@'); | ||||||
|         ci.username = decode(userPwd.substring(0, userPwd.indexOf(":"))); |         if (idxAt >= 0) | ||||||
|         ci.password = decode(userPwd.substring(userPwd.indexOf(":") + 1)); |         { | ||||||
|  |             String userPwd = filename.substring(0, idxAt); | ||||||
|  |             int idxColon = userPwd.indexOf(":"); | ||||||
|  |             if (idxColon >= 0); | ||||||
|  |             { | ||||||
|  |                 ci.username = decode(userPwd.substring(0, idxColon)); | ||||||
|  |                 ci.password = decode(userPwd.substring(idxColon + 1)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         ci.URL = scheme + "://" +filename.substring(filename.indexOf('@') + 1); |         ci.URL = scheme + "://" +filename.substring(filename.indexOf('@') + 1); | ||||||
|         return ci; |         return ci; | ||||||
|     } |     } | ||||||
| @@ -149,13 +159,19 @@ public class WebDavStorage extends JavaFileStorageBase { | |||||||
|             sslContext.init(null, new TrustManager[] { trustManager }, null); |             sslContext.init(null, new TrustManager[] { trustManager }, null); | ||||||
|             SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); |             SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); | ||||||
|  |  | ||||||
|  |  | ||||||
|             builder = builder.sslSocketFactory(sslSocketFactory, trustManager) |             builder = builder.sslSocketFactory(sslSocketFactory, trustManager) | ||||||
|                              .hostnameVerifier(new DecoratedHostnameVerifier(OkHostnameVerifier.INSTANCE, mCertificateErrorHandler)); |                              .hostnameVerifier(new DecoratedHostnameVerifier(OkHostnameVerifier.INSTANCE, mCertificateErrorHandler)); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             builder.connectTimeout(25, TimeUnit.SECONDS); | ||||||
|  |             builder.readTimeout(25, TimeUnit.SECONDS); | ||||||
|  |             builder.writeTimeout(25, TimeUnit.SECONDS); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         OkHttpClient client =  builder.build(); |         OkHttpClient client =  builder.build(); | ||||||
|  |  | ||||||
|  |  | ||||||
|         return client; |         return client; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,45 @@ | |||||||
|  | package keepass2android.javafilestorage.onedrive2; | ||||||
|  |  | ||||||
|  | import com.microsoft.graph.authentication.IAuthenticationProvider; | ||||||
|  | import com.microsoft.graph.core.DefaultClientConfig; | ||||||
|  | import com.microsoft.graph.core.IClientConfig; | ||||||
|  | import com.microsoft.graph.extensions.GraphServiceClient; | ||||||
|  | import com.microsoft.graph.extensions.IGraphServiceClient; | ||||||
|  | import com.microsoft.graph.http.IHttpRequest; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Singleton class that manages a GraphServiceClient object. | ||||||
|  |  * It implements IAuthentication provider to authenticate requests using an access token. | ||||||
|  |  */ | ||||||
|  | public class GraphServiceClientManager implements IAuthenticationProvider { | ||||||
|  |     private IGraphServiceClient mGraphServiceClient; | ||||||
|  |     private String mAccessToken; | ||||||
|  |  | ||||||
|  |     public GraphServiceClientManager(String accessToken) { | ||||||
|  |         mAccessToken = accessToken; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void authenticateRequest(IHttpRequest request)  { | ||||||
|  |         try { | ||||||
|  |             request.addHeader("Authorization", "Bearer " + mAccessToken); | ||||||
|  |         } catch (NullPointerException e) { | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public synchronized IGraphServiceClient getGraphServiceClient() { | ||||||
|  |         return getGraphServiceClient(this); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public synchronized IGraphServiceClient getGraphServiceClient(IAuthenticationProvider authenticationProvider) { | ||||||
|  |         if (mGraphServiceClient == null) { | ||||||
|  |             IClientConfig clientConfig = DefaultClientConfig.createWithAuthenticationProvider( | ||||||
|  |                     authenticationProvider | ||||||
|  |             ); | ||||||
|  |             mGraphServiceClient = new GraphServiceClient.Builder().fromConfig(clientConfig).buildClient(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return mGraphServiceClient; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -10,6 +10,8 @@ android { | |||||||
|         targetSdkVersion 23 |         targetSdkVersion 23 | ||||||
|         versionCode 1 |         versionCode 1 | ||||||
|         versionName "1.0" |         versionName "1.0" | ||||||
|  |         multiDexEnabled true | ||||||
|  |  | ||||||
|  |  | ||||||
|     } |     } | ||||||
|     buildTypes { |     buildTypes { | ||||||
|   | |||||||
| @@ -59,6 +59,18 @@ | |||||||
|                 <category android:name="android.intent.category.DEFAULT" /> |                 <category android:name="android.intent.category.DEFAULT" /> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|  |  | ||||||
|  |         <activity | ||||||
|  |             android:name="com.microsoft.identity.client.BrowserTabActivity"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.VIEW" /> | ||||||
|  |                 <category android:name="android.intent.category.DEFAULT" /> | ||||||
|  |                 <category android:name="android.intent.category.BROWSABLE" /> | ||||||
|  |                 <data android:scheme="@string/msalPrefix" | ||||||
|  |                     android:host="auth" /> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity> | ||||||
|  |  | ||||||
|     </application> |     </application> | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -68,6 +80,8 @@ | |||||||
|     <uses-permission android:name="android.permission.USE_CREDENTIALS" /> |     <uses-permission android:name="android.permission.USE_CREDENTIALS" /> | ||||||
|     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/> |     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/> | ||||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||||
|  |     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| </manifest> | </manifest> | ||||||
| @@ -89,6 +89,8 @@ extends Activity implements JavaFileStorage.FileStorageSetupActivity { | |||||||
| 	@Override | 	@Override | ||||||
| 	public String getPath() { | 	public String getPath() { | ||||||
| 		// TODO Auto-generated method stub | 		// TODO Auto-generated method stub | ||||||
|  | 		if (getState().containsKey(JavaFileStorage.EXTRA_PATH)) | ||||||
|  | 			return getState().getString(JavaFileStorage.EXTRA_PATH); | ||||||
| 		return getIntent().getStringExtra(JavaFileStorage.EXTRA_PATH); | 		return getIntent().getStringExtra(JavaFileStorage.EXTRA_PATH); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -135,7 +135,6 @@ package com.crocoapps.javafilestoragetest; | |||||||
| import group.pals.android.lib.ui.filechooser.FileChooserActivity; | import group.pals.android.lib.ui.filechooser.FileChooserActivity; | ||||||
| import group.pals.android.lib.ui.filechooser.providers.BaseFileProviderUtils; | import group.pals.android.lib.ui.filechooser.providers.BaseFileProviderUtils; | ||||||
|  |  | ||||||
| import java.io.File; |  | ||||||
| import java.io.FileInputStream; | import java.io.FileInputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
| @@ -146,11 +145,9 @@ import java.util.ArrayList; | |||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| //import keepass2android.javafilestorage.DropboxCloudRailStorage; | //import keepass2android.javafilestorage.DropboxCloudRailStorage; | ||||||
| import keepass2android.javafilestorage.DropboxV2Storage; |  | ||||||
| import keepass2android.javafilestorage.ICertificateErrorHandler; |  | ||||||
| import keepass2android.javafilestorage.JavaFileStorage; | import keepass2android.javafilestorage.JavaFileStorage; | ||||||
| import keepass2android.javafilestorage.JavaFileStorage.FileEntry; | import keepass2android.javafilestorage.JavaFileStorage.FileEntry; | ||||||
| import keepass2android.javafilestorage.OneDriveStorage; | import keepass2android.javafilestorage.OneDriveStorage2; | ||||||
| import keepass2android.javafilestorage.SftpStorage; | import keepass2android.javafilestorage.SftpStorage; | ||||||
| import keepass2android.javafilestorage.UserInteractionRequiredException; | import keepass2android.javafilestorage.UserInteractionRequiredException; | ||||||
| import keepass2android.javafilestorage.WebDavStorage; | import keepass2android.javafilestorage.WebDavStorage; | ||||||
| @@ -206,10 +203,17 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag | |||||||
| 				} | 				} | ||||||
| 				catch (Exception e) | 				catch (Exception e) | ||||||
| 				{ | 				{ | ||||||
|  | 					Log.d("KP2AJ",e.toString()); | ||||||
| 					//if exception because folder exists | 					//if exception because folder exists | ||||||
| 					path = fs.createFilePath(parentPath, testPath); | 					path = fs.createFilePath(parentPath, testPath); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				String textToUpload2 = "abcdefg"; | ||||||
|  | 				String filename2 = fs.createFilePath(parentPath, "file.txt"); | ||||||
|  | 				/*if (!path.endsWith("/")) | ||||||
|  | 					path += "/"; | ||||||
|  | 				String filename = path+"file.text";*/ | ||||||
|  | 				fs.uploadFile(filename2,textToUpload2.getBytes(),true); | ||||||
|  |  | ||||||
| 				FileEntry e1 = fs.getFileEntry(parentPath); | 				FileEntry e1 = fs.getFileEntry(parentPath); | ||||||
| 				FileEntry e2 = fs.getFileEntry(path); | 				FileEntry e2 = fs.getFileEntry(path); | ||||||
| @@ -531,9 +535,11 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	static JavaFileStorage createStorageToTest(Context ctx, Context appContext, boolean simulateRestart) { | 	static JavaFileStorage createStorageToTest(Context ctx, Context appContext, boolean simulateRestart) { | ||||||
| 		storageToTest = new SftpStorage(ctx.getApplicationContext()); | 		//storageToTest = new SftpStorage(ctx.getApplicationContext()); | ||||||
| 		//storageToTest = new SkyDriveFileStorage("000000004010C234", appContext); | 		//storageToTest = new SkyDriveFileStorage("000000004010C234", appContext); | ||||||
| 		//storageToTest = new OneDriveStorage(appContext, "000000004010C234"); | 		storageToTest = new OneDriveStorage2((Activity) ctx, "8374f801-0f55-407d-80cc-9a04fe86d9b2"); | ||||||
|  |  | ||||||
|  |  | ||||||
| 		//storageToTest = new GoogleDriveFileStorage(); | 		//storageToTest = new GoogleDriveFileStorage(); | ||||||
| 		/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() { | 		/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() { | ||||||
| 			@Override | 			@Override | ||||||
| @@ -620,7 +626,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag | |||||||
| 		Toast.makeText(this, "requestCode: "+requestCode, Toast.LENGTH_LONG).show(); | 		Toast.makeText(this, "requestCode: "+requestCode, Toast.LENGTH_LONG).show(); | ||||||
| 		if (requestCode == 1) | 		if (requestCode == 1) | ||||||
| 			//new PerformTestTask().execute(path,"TestFileStorage<67>", storageToTest); //use an umlaut to see how that works | 			//new PerformTestTask().execute(path,"TestFileStorage<67>", storageToTest); //use an umlaut to see how that works | ||||||
| 			new PerformTestTask().execute(path,"TestFileStorage", storageToTest);  | 			new PerformTestTask().execute(path,"TestFileStorage", storageToTest); | ||||||
| 		else | 		else | ||||||
| 		if (requestCode == 2) | 		if (requestCode == 2) | ||||||
| 		{ | 		{ | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <resources> | <resources> | ||||||
|  |     <string name="msalPrefix">msal8374f801-0f55-407d-80cc-9a04fe86d9b2</string> | ||||||
|     <string name="action_settings">Settings</string> |     <string name="action_settings">Settings</string> | ||||||
|     <string name="hello_world">Hello world!</string> |     <string name="hello_world">Hello world!</string> | ||||||
|     <string name="title_activity_file_storage_setup">FileStorageSetupActivity</string> |     <string name="title_activity_file_storage_setup">FileStorageSetupActivity</string> | ||||||
|   | |||||||
| @@ -2,10 +2,14 @@ package keepass2android.softkeyboard; | |||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
|  | import java.nio.ByteBuffer; | ||||||
|  | import java.nio.ByteOrder; | ||||||
|  | import java.nio.channels.Channels; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.logging.Logger; | ||||||
|  |  | ||||||
| import org.xmlpull.v1.XmlPullParserException; | import org.xmlpull.v1.XmlPullParserException; | ||||||
|  |  | ||||||
| @@ -17,9 +21,11 @@ import android.content.pm.PackageManager; | |||||||
| import android.content.pm.ResolveInfo; | import android.content.pm.ResolveInfo; | ||||||
| import android.content.pm.PackageManager.NameNotFoundException; | import android.content.pm.PackageManager.NameNotFoundException; | ||||||
| import android.content.res.Resources; | import android.content.res.Resources; | ||||||
|  | import android.content.res.TypedArray; | ||||||
| import android.content.res.XmlResourceParser; | import android.content.res.XmlResourceParser; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
|  |  | ||||||
|  | //based on https://github.com/klausw/hackerskeyboard/blob/master/java/src/org/pocketworkstation/pckeyboard/PluginManager.java | ||||||
| public class PluginManager extends BroadcastReceiver { | public class PluginManager extends BroadcastReceiver { | ||||||
|     private static String TAG = "PCKeyboard"; |     private static String TAG = "PCKeyboard"; | ||||||
|     private static String HK_INTENT_DICT = "org.pocketworkstation.DICT"; |     private static String HK_INTENT_DICT = "org.pocketworkstation.DICT"; | ||||||
| @@ -120,6 +126,46 @@ public class PluginManager extends BroadcastReceiver { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     static private class DictPluginSpecResourceSoftKeyboard | ||||||
|  |             extends DictPluginSpecBase { | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         int[] resId; | ||||||
|  |         Resources pluginRes; | ||||||
|  |  | ||||||
|  |         public DictPluginSpecResourceSoftKeyboard(String pkg, int[] resId, Resources pluginRes) { | ||||||
|  |             mPackageName = pkg; | ||||||
|  |             this.resId = resId; | ||||||
|  |             this.pluginRes = pluginRes; | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         InputStream[] getStreams(Resources res) { | ||||||
|  |             final InputStream[] is = new InputStream[resId.length]; | ||||||
|  |  | ||||||
|  |             try { | ||||||
|  |                 // merging separated dictionary into one if dictionary is separated | ||||||
|  |                 int total = 0; | ||||||
|  |                 for (int i = 0; i < resId.length; i++) { | ||||||
|  |                      | ||||||
|  |                     // http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/ | ||||||
|  |                     // NOTE: the resource file can not be larger than 1MB | ||||||
|  |                     is[i] = pluginRes.openRawResource(resId[i]); | ||||||
|  |                     final int dictSize = is[i].available(); | ||||||
|  |                     Log.d(TAG, "Will load a resource dictionary id " + resId[i] + " whose size is " + dictSize + " bytes."); | ||||||
|  |                     total += dictSize; | ||||||
|  |                 } | ||||||
|  |                 return is; | ||||||
|  |  | ||||||
|  |             } catch (IOException e) { | ||||||
|  |                 Log.w(TAG, "No available memory for binary dictionary: " + e.getMessage()); | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|     public void onReceive(Context context, Intent intent) { |     public void onReceive(Context context, Intent intent) { | ||||||
| @@ -129,21 +175,59 @@ public class PluginManager extends BroadcastReceiver { | |||||||
|         mIME.toggleLanguage(true, true); |         mIME.toggleLanguage(true, true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public interface MemRelatedOperation { | ||||||
|  |         void operation(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static final int GC_TRY_LOOP_MAX = 5; | ||||||
|  |  | ||||||
|  |     static void doGarbageCollection(final String tag) { | ||||||
|  |         System.gc(); | ||||||
|  |         try { | ||||||
|  |             Thread.sleep(1000 /*ms*/); | ||||||
|  |         } catch (InterruptedException e) { | ||||||
|  |             Log.e(tag, "Sleep was interrupted."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static public void performOperationWithMemRetry(final String tag, MemRelatedOperation operation) { | ||||||
|  |         int retryCount = GC_TRY_LOOP_MAX; | ||||||
|  |         while (true) { | ||||||
|  |             try { | ||||||
|  |                 operation.operation(); | ||||||
|  |                 return; | ||||||
|  |             } catch (OutOfMemoryError e) { | ||||||
|  |                 if (retryCount == 0) throw e; | ||||||
|  |  | ||||||
|  |                 retryCount--; | ||||||
|  |                 Log.w(tag, "WOW! No memory for operation... I'll try to release some."); | ||||||
|  |                 doGarbageCollection(tag); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     static void getSoftKeyboardDictionaries(PackageManager packageManager) { |     static void getSoftKeyboardDictionaries(PackageManager packageManager) { | ||||||
|         Intent dictIntent = new Intent(SOFTKEYBOARD_INTENT_DICT); |         Intent dictIntent = new Intent(SOFTKEYBOARD_INTENT_DICT); | ||||||
|         List<ResolveInfo> dictPacks = packageManager.queryBroadcastReceivers( |         List<ResolveInfo> dictPacks = packageManager.queryBroadcastReceivers( | ||||||
|         		dictIntent, PackageManager.GET_RECEIVERS); |         		dictIntent, PackageManager.GET_RECEIVERS); | ||||||
|         for (ResolveInfo ri : dictPacks) { |         for (ResolveInfo ri : dictPacks) { | ||||||
|             ApplicationInfo appInfo = ri.activityInfo.applicationInfo; |             ApplicationInfo appInfo = ri.activityInfo.applicationInfo; | ||||||
|             String pkgName = appInfo.packageName; |             final String pkgName = appInfo.packageName; | ||||||
|             boolean success = false; |             final boolean[] success = {false}; | ||||||
|             try { |             try { | ||||||
|                 Resources res = packageManager.getResourcesForApplication(appInfo); |                 final Resources res = packageManager.getResourcesForApplication(appInfo); | ||||||
|                 Log.i("KP2AK", "Found dictionary plugin package: " + pkgName); |                 Log.i("KP2AK", "Found dictionary plugin package: " + pkgName); | ||||||
|                 int dictId = res.getIdentifier("dictionaries", "xml", pkgName); |                 int dictId = res.getIdentifier("dictionaries", "xml", pkgName); | ||||||
|                 if (dictId == 0) continue; |  | ||||||
|  |                 if (dictId == 0) | ||||||
|  |                 { | ||||||
|  |                     Log.i("KP2AK", "dictId == 0"); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|                 XmlResourceParser xrp = res.getXml(dictId); |                 XmlResourceParser xrp = res.getXml(dictId); | ||||||
|  |  | ||||||
|  |                 int dictResourceId = 0; | ||||||
|                 String assetName = null; |                 String assetName = null; | ||||||
|                 String lang = null; |                 String lang = null; | ||||||
|                 try { |                 try { | ||||||
| @@ -157,12 +241,21 @@ public class PluginManager extends BroadcastReceiver { | |||||||
|                                     String convLang = SOFTKEYBOARD_LANG_MAP.get(lang); |                                     String convLang = SOFTKEYBOARD_LANG_MAP.get(lang); | ||||||
|                                     if (convLang != null) lang = convLang; |                                     if (convLang != null) lang = convLang; | ||||||
|                                     String type = xrp.getAttributeValue(null, "type"); |                                     String type = xrp.getAttributeValue(null, "type"); | ||||||
|                                     if (type == null || type.equals("raw") || type.equals("binary")) { |  | ||||||
|  |                                     if (type == null || type.equals("raw") || type.equals("binary")) | ||||||
|  |                                     { | ||||||
|                                         assetName = xrp.getAttributeValue(null, "dictionaryAssertName"); // sic |                                         assetName = xrp.getAttributeValue(null, "dictionaryAssertName"); // sic | ||||||
|                                     } else { |                                         if (assetName != null) { | ||||||
|  |                                             Log.i(TAG, "asset=" + assetName + " lang=" + lang); | ||||||
|  |                                         } | ||||||
|  |                                     } else if (type.equals("binary_resource")) | ||||||
|  |                                     { | ||||||
|  |                                         dictResourceId = xrp.getAttributeResourceValue(null, "dictionaryResourceId",0); | ||||||
|  |                                     } | ||||||
|  |                                     else { | ||||||
|                                         Log.w(TAG, "Unsupported AnySoftKeyboard dict type " + type); |                                         Log.w(TAG, "Unsupported AnySoftKeyboard dict type " + type); | ||||||
|                                     } |                                     } | ||||||
|                                     //Log.i(TAG, "asset=" + assetName + " lang=" + lang); |  | ||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| @@ -175,15 +268,55 @@ public class PluginManager extends BroadcastReceiver { | |||||||
|                     Log.e(TAG, "Dictionary XML IOException"); |                     Log.e(TAG, "Dictionary XML IOException"); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (assetName == null || lang == null) continue; |                 if (lang == null) | ||||||
|                 DictPluginSpec spec = new DictPluginSpecSoftKeyboard(pkgName, assetName); |                     continue; | ||||||
|                 mPluginDicts.put(lang, spec); |                 if (assetName != null) { | ||||||
|                 Log.i("KP2AK", "Found plugin dictionary: lang=" + lang + ", pkg=" + pkgName); |                     DictPluginSpec spec = new DictPluginSpecSoftKeyboard(pkgName, assetName); | ||||||
|                 success = true; |                     mPluginDicts.put(lang, spec); | ||||||
|  |                     Log.i("KP2AK", "Found plugin dictionary: lang=" + lang + ", pkg=" + pkgName); | ||||||
|  |                     success[0] = true; | ||||||
|  |                 } | ||||||
|  |                 else if (dictResourceId != 0) | ||||||
|  |                 { | ||||||
|  |  | ||||||
|  |                     Resources pkgRes = packageManager.getResourcesForApplication(appInfo); | ||||||
|  |                     final int[] resId; | ||||||
|  |                     // is it an array of dictionaries? Or a ref to raw? | ||||||
|  |                     final String dictResType = pkgRes.getResourceTypeName(dictResourceId); | ||||||
|  |                     if (dictResType.equalsIgnoreCase("raw")) { | ||||||
|  |                         resId = new int[]{dictResourceId}; | ||||||
|  |                     } else { | ||||||
|  |                         TypedArray a = pkgRes.obtainTypedArray(dictResourceId); | ||||||
|  |                         resId = new int[a.length()]; | ||||||
|  |                         for (int index = 0; index < a.length(); index++) | ||||||
|  |                             resId[index] = a.getResourceId(index, 0); | ||||||
|  |  | ||||||
|  |                         a.recycle(); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     final String finalLang = lang; | ||||||
|  |                     performOperationWithMemRetry(TAG, new MemRelatedOperation() { | ||||||
|  |                         @Override | ||||||
|  |                         public void operation() { | ||||||
|  |                             // The try-catch is for issue 878: | ||||||
|  |                             // http://code.google.com/p/softkeyboard/issues/detail?id=878 | ||||||
|  |                             try { | ||||||
|  |                                 DictPluginSpec spec = new DictPluginSpecResourceSoftKeyboard(pkgName, resId, res); | ||||||
|  |                                 mPluginDicts.put(finalLang, spec); | ||||||
|  |                                 Log.i("KP2AK", "Found plugin dictionary: lang=" + finalLang + ", pkg=" + pkgName); | ||||||
|  |                                 success[0] = true; | ||||||
|  |                             } catch (UnsatisfiedLinkError ex) { | ||||||
|  |                                 Log.w(TAG, "Failed to load binary JNI connection! Error: " + ex.getMessage()); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |  | ||||||
|             } catch (NameNotFoundException e) { |             } catch (NameNotFoundException e) { | ||||||
|                 Log.i("KP2AK", "bad"); |                 Log.i("KP2AK", "bad"); | ||||||
|             } finally { |             } finally { | ||||||
|                 if (!success) { |                 if (!success[0]) { | ||||||
|                     Log.i("KP2AK", "failed to load plugin dictionary spec from " + pkgName); |                     Log.i("KP2AK", "failed to load plugin dictionary spec from " + pkgName); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -193,6 +326,7 @@ public class PluginManager extends BroadcastReceiver { | |||||||
|     static void getHKDictionaries(PackageManager packageManager) { |     static void getHKDictionaries(PackageManager packageManager) { | ||||||
|         Intent dictIntent = new Intent(HK_INTENT_DICT); |         Intent dictIntent = new Intent(HK_INTENT_DICT); | ||||||
|         List<ResolveInfo> dictPacks = packageManager.queryIntentActivities(dictIntent, 0); |         List<ResolveInfo> dictPacks = packageManager.queryIntentActivities(dictIntent, 0); | ||||||
|  |         Log.i("KP2AK", "Searching for HK dictionaries. Found " + dictPacks.size() + " packages"); | ||||||
|         for (ResolveInfo ri : dictPacks) { |         for (ResolveInfo ri : dictPacks) { | ||||||
|             ApplicationInfo appInfo = ri.activityInfo.applicationInfo; |             ApplicationInfo appInfo = ri.activityInfo.applicationInfo; | ||||||
|             String pkgName = appInfo.packageName; |             String pkgName = appInfo.packageName; | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import java.util.Iterator; | |||||||
|  |  | ||||||
| import org.json.JSONException; | import org.json.JSONException; | ||||||
| import org.json.JSONObject; | import org.json.JSONObject; | ||||||
|  | import org.json.JSONArray; | ||||||
|  |  | ||||||
| import android.content.BroadcastReceiver; | import android.content.BroadcastReceiver; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| @@ -65,7 +66,16 @@ public abstract class PluginActionBroadcastReceiver extends BroadcastReceiver { | |||||||
| 		 | 		 | ||||||
| 		protected String[] getProtectedFieldsListFromIntent() | 		protected String[] getProtectedFieldsListFromIntent() | ||||||
| 		{ | 		{ | ||||||
| 			return _intent.getStringArrayExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST); | 			try { | ||||||
|  | 				JSONArray json = new JSONArray(_intent.getStringExtra(Strings.EXTRA_PROTECTED_FIELDS_LIST)); | ||||||
|  | 				String[] res = new String[json.length()]; | ||||||
|  | 				for(int i = 0; i < json.length(); i++) | ||||||
|  | 					res[i] = json.getString(i); | ||||||
|  | 				return res; | ||||||
|  | 			} catch (JSONException e) { | ||||||
|  | 				e.printStackTrace(); | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/java/PluginQR/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/java/PluginQR/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | /build | ||||||
							
								
								
									
										1
									
								
								src/java/PluginQR/app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/java/PluginQR/app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | /build | ||||||
							
								
								
									
										37
									
								
								src/java/PluginQR/app/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/java/PluginQR/app/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | apply plugin: 'com.android.application' | ||||||
|  |  | ||||||
|  | android { | ||||||
|  |     compileSdkVersion 26 | ||||||
|  |     buildToolsVersion '26.0.2' | ||||||
|  |  | ||||||
|  |     defaultConfig { | ||||||
|  |         applicationId "keepass2android.plugin.qr" | ||||||
|  |         minSdkVersion 14 | ||||||
|  |         targetSdkVersion 19 | ||||||
|  |         versionCode 3 | ||||||
|  |         versionName "1.0.2" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildTypes { | ||||||
|  |         release { | ||||||
|  |             minifyEnabled true | ||||||
|  |             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     compileOptions { | ||||||
|  |         sourceCompatibility 1.8 | ||||||
|  |         targetCompatibility 1.8 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     lintOptions { | ||||||
|  |         disable 'ExtraTranslation' | ||||||
|  |         disable 'MissingTranslation' | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     compile project(':Keepass2AndroidPluginSDK2') | ||||||
|  |     compile 'com.google.zxing:core:2.3.0' | ||||||
|  | } | ||||||
| @@ -1,8 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     package="keepass2android.plugin.qr" |     package="keepass2android.plugin.qr" > | ||||||
|     android:versionCode="2" |  | ||||||
|     android:versionName="1.0.1" > |  | ||||||
|      |      | ||||||
|     <uses-permission android:name="android.permission.CAMERA"/> |     <uses-permission android:name="android.permission.CAMERA"/> | ||||||
|      <uses-permission |      <uses-permission | ||||||
| @@ -15,10 +13,6 @@ | |||||||
| 	<uses-feature android:name="android.hardware.camera.flash" android:required="false"/> | 	<uses-feature android:name="android.hardware.camera.flash" android:required="false"/> | ||||||
| 	<uses-feature android:name="android.hardware.screen.portrait" android:required="false"/> | 	<uses-feature android:name="android.hardware.screen.portrait" android:required="false"/> | ||||||
| 
 | 
 | ||||||
|     <uses-sdk |  | ||||||
|         android:minSdkVersion="14" |  | ||||||
|         android:targetSdkVersion="19" /> |  | ||||||
| 
 |  | ||||||
|     <application |     <application | ||||||
|         android:allowBackup="true" |         android:allowBackup="true" | ||||||
|         android:icon="@drawable/ic_launcher" |         android:icon="@drawable/ic_launcher" | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user