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% | ||||
|     translate_attributes: '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: | ||||
|  | ||||
| {{ | ||||
| ``` | ||||
| Mono for Android		 | ||||
| 			.net dlls			5.0 MB | ||||
| 			Runtime				2.5 MB				 | ||||
| @@ -22,4 +22,4 @@ Java/Mono bindings					0.5 MB | ||||
| rest							0.3 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? | ||||
| 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. | ||||
|   | ||||
| @@ -81,10 +81,6 @@ | ||||
|     </XamarinComponentReference> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\AdalBindings\AdalBindings.csproj"> | ||||
|       <Project>{0b109c0e-0514-4340-8779-5bd6a0dde84e}</Project> | ||||
|       <Name>AdalBindings</Name> | ||||
|     </ProjectReference> | ||||
|     <ProjectReference Include="..\PCloudBindings\PCloudBindings.csproj" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -29,8 +29,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamsungPass", "SamsungPass\ | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCloudBindings", "PCloudBindings\PCloudBindings.csproj", "{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdalBindings", "AdalBindings\AdalBindings.csproj", "{0B109C0E-0514-4340-8779-5BD6A0DDE84E}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		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|x64.ActiveCfg = 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 | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
|   | ||||
| @@ -21,7 +21,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using keepass2android; | ||||
| #if KeePassUAP | ||||
| using Org.BouncyCastle.Crypto; | ||||
| using Org.BouncyCastle.Crypto.Engines; | ||||
| @@ -144,6 +144,7 @@ namespace KeePassLib.Cryptography.KeyDerivation | ||||
| 		public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, | ||||
| 			ulong uNumRounds) | ||||
| 		{ | ||||
| 		    Kp2aLog.Log("Warning: transforming key managed. Expect this to be slow!"); | ||||
| #if KeePassUAP | ||||
| 			KeyParameter kp = new KeyParameter(pbKeySeed32); | ||||
| 			AesEngine aes = new AesEngine(); | ||||
|   | ||||
| @@ -42,5 +42,7 @@ namespace KeePassLib.Keys | ||||
|         // /// Clear the key and securely erase all security-critical information. | ||||
|         // /// </summary> | ||||
|         // void Clear(); | ||||
|  | ||||
|         uint GetMinKdbxVersion(); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -45,6 +45,11 @@ namespace KeePassLib.Keys | ||||
| 			get { return m_pbKey; } | ||||
| 		} | ||||
|  | ||||
| 	    public uint GetMinKdbxVersion() | ||||
| 	    { | ||||
| 	        return 0; | ||||
| 	    } | ||||
|  | ||||
| 	    public KcpCustomKey(string strName, byte[] pbKeyData, bool bPerformHash) | ||||
| 		{ | ||||
| 			Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); | ||||
|   | ||||
| @@ -64,6 +64,11 @@ namespace KeePassLib.Keys | ||||
| 			get { return m_pbKeyData; } | ||||
| 		} | ||||
|  | ||||
| 	    public uint GetMinKdbxVersion() | ||||
| 	    { | ||||
| 	        return 0; | ||||
| 	    } | ||||
|  | ||||
| 	    public IOConnectionInfo Ioc | ||||
| 		{ | ||||
| 			get { return m_ioc; } | ||||
|   | ||||
| @@ -53,6 +53,11 @@ namespace KeePassLib.Keys | ||||
| 			get { return m_pbKeyData; } | ||||
| 		} | ||||
|  | ||||
| 	    public uint GetMinKdbxVersion() | ||||
| 	    { | ||||
| 	        return 0; | ||||
| 	    } | ||||
|  | ||||
| 	    public KcpPassword(byte[] pbPasswordUtf8) | ||||
| 		{ | ||||
| 			SetKey(pbPasswordUtf8); | ||||
|   | ||||
| @@ -60,6 +60,11 @@ namespace KeePassLib.Keys | ||||
| 			get { return m_pbKeyData; } | ||||
| 		} | ||||
|  | ||||
| 	    public uint GetMinKdbxVersion() | ||||
| 	    { | ||||
| 	        return 0; | ||||
| 	    } | ||||
|  | ||||
| 	    /// <summary> | ||||
| 		/// Construct a user account key. | ||||
| 		/// </summary> | ||||
|   | ||||
| @@ -85,7 +85,7 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			if (!File.Exists(LogFilename)) | ||||
| 			{ | ||||
| 				File.Create(LogFilename); | ||||
| 				File.Create(LogFilename).Dispose(); | ||||
|                 _logToFile = true; | ||||
| 			} | ||||
| 			 | ||||
| @@ -100,7 +100,6 @@ namespace keepass2android | ||||
| 				int count = 0; | ||||
| 				while (File.Exists(LogFilename + "." + count)) | ||||
| 					count++; | ||||
|                 if (count > 0) | ||||
|                 File.Move(LogFilename, LogFilename + "." + count); | ||||
| 				 | ||||
| 			} | ||||
|   | ||||
| @@ -302,7 +302,7 @@ namespace KeePassLib.Native | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				Kp2aLog.Log(e.Message); | ||||
| 				Kp2aLog.Log(e.ToString()); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
|   | ||||
| @@ -485,12 +485,6 @@ namespace KeePassLib | ||||
| 			set { m_pbHashOfLastIO = value; } | ||||
| 		} | ||||
|  | ||||
| 		public bool UseFileTransactions | ||||
| 		{ | ||||
| 			get { return m_bUseFileTransactions; } | ||||
| 			set { m_bUseFileTransactions = value; } | ||||
| 		} | ||||
|  | ||||
| 		public bool UseFileLocks | ||||
| 		{ | ||||
| 			get { return m_bUseFileLocks; } | ||||
|   | ||||
| @@ -360,5 +360,12 @@ namespace KeePassLib.Serialization | ||||
| 				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.Globalization; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Security; | ||||
| using System.Text; | ||||
| using System.Xml; | ||||
| @@ -126,8 +127,8 @@ namespace KeePassLib.Serialization | ||||
| 		/// </summary> | ||||
| 		private const uint FileVersion32 = 0x00040000; | ||||
|  | ||||
| 		internal const uint FileVersion32_4 = 0x00040000; // First of 4.x series | ||||
| 		internal const uint FileVersion32_3 = 0x00030001; // Old format 3.1 | ||||
| 		public const uint FileVersion32_4 = 0x00040000; // First of 4.x series | ||||
| 		public const uint FileVersion32_3 = 0x00030001; // Old format 3.1 | ||||
|  | ||||
| 		private const uint FileVersionCriticalMask = 0xFFFF0000; | ||||
|  | ||||
| @@ -373,13 +374,16 @@ namespace KeePassLib.Serialization | ||||
| 			if(m_uForceVersion != 0) return m_uForceVersion; | ||||
|  | ||||
|             // See also KeePassKdb2x3.Export (KDBX 3.1 export module) | ||||
| 		    uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max(); | ||||
| 		     | ||||
|             AesKdf kdfAes = new AesKdf(); | ||||
| 			if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) | ||||
| 				return FileVersion32; | ||||
| 				return Math.Max(FileVersion32, minVersionForKeys); | ||||
|  | ||||
| 			if(m_pwDatabase.PublicCustomData.Count > 0) | ||||
| 				return FileVersion32; | ||||
| 			    return Math.Max(FileVersion32, minVersionForKeys); | ||||
|  | ||||
|  | ||||
|  | ||||
|             bool bCustomData = false; | ||||
| 			GroupHandler gh = delegate(PwGroup pg) | ||||
| @@ -396,9 +400,10 @@ namespace KeePassLib.Serialization | ||||
| 			}; | ||||
| 			gh(m_pwDatabase.RootGroup); | ||||
| 			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, | ||||
|   | ||||
							
								
								
									
										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.Collections.Generic; | ||||
| using System.Net.Security; | ||||
| using System.Security.Cryptography.X509Certificates; | ||||
| using Android.App; | ||||
| @@ -9,6 +10,7 @@ using KeePassLib; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
| using KeePassLib.Interfaces; | ||||
| #if !NoNet | ||||
| using Keepass2android.Javafilestorage; | ||||
| #endif  | ||||
| @@ -34,30 +36,40 @@ namespace keepass2android | ||||
| 	public interface IKp2aApp : ICertificateValidationHandler | ||||
| 	{ | ||||
|         /// <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> | ||||
| 		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); | ||||
|         Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent); | ||||
|  | ||||
|  | ||||
| 	    HashSet<PwGroup> DirtyGroups { get; } | ||||
|  | ||||
| 	    void MarkAllGroupsAsDirty(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Returns the current database | ||||
|         /// </summary> | ||||
| 		Database GetDb(); | ||||
| 	    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, string displayName = ""); | ||||
|         void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile, bool updateTimestamp, string displayName = ""); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Creates a new database and returns it | ||||
| 		/// </summary> | ||||
| 		Database CreateNewDatabase(); | ||||
| 		Database CreateNewDatabase(bool makeCurrent); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Returns the user-displayable string identified by stringKey | ||||
| @@ -76,7 +88,8 @@ namespace keepass2android | ||||
| 		                    EventHandler<DialogClickEventArgs> yesHandler, | ||||
| 		                    EventHandler<DialogClickEventArgs> noHandler, | ||||
| 		                    EventHandler<DialogClickEventArgs> cancelHandler, | ||||
| 		                    Context ctx); | ||||
| 		                    Context ctx, | ||||
|                             string messageSuffix = ""); | ||||
|  | ||||
| 		/// <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. | ||||
| @@ -86,7 +99,8 @@ namespace keepass2android | ||||
| 		                    EventHandler<DialogClickEventArgs> yesHandler, | ||||
| 		                    EventHandler<DialogClickEventArgs> noHandler, | ||||
| 		                    EventHandler<DialogClickEventArgs> cancelHandler, | ||||
| 		                    Context ctx); | ||||
| 		                    Context ctx, | ||||
| 		                    string messageSuffix = ""); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Returns a Handler object which can run tasks on the UI thread | ||||
| @@ -111,6 +125,10 @@ namespace keepass2android | ||||
| 		bool CheckForDuplicateUuids { get; } | ||||
| #if !NoNet | ||||
| 		ICertificateErrorHandler CertificateErrorHandler { get; } | ||||
| 	     | ||||
|  | ||||
|  | ||||
| #endif | ||||
| 	     | ||||
| 	} | ||||
| } | ||||
| @@ -207,8 +207,7 @@ namespace keepass2android.Io | ||||
|  | ||||
| 		public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			//TODO: required for OTP Aux file retrieval | ||||
| 			throw new NotImplementedException(); | ||||
| 		    return IoUtil.GetParentPath(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) | ||||
|   | ||||
| @@ -255,6 +255,7 @@ namespace keepass2android.Io | ||||
| 			if (ioc.IsLocalFile()) | ||||
| 			{ | ||||
| 				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)); | ||||
| 				 | ||||
| 				var extDirectory = activity.Activity.GetExternalFilesDir(null); | ||||
|   | ||||
| @@ -65,21 +65,27 @@ namespace keepass2android.Io | ||||
| 		 | ||||
| 		protected readonly OfflineSwitchableFileStorage _cachedStorage; | ||||
| 		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); | ||||
| 			_cacheSupervisor = cacheSupervisor; | ||||
| 			_streamCacheDir = cacheDir + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; | ||||
| 			if (!Directory.Exists(_streamCacheDir)) | ||||
| 				Directory.CreateDirectory(_streamCacheDir); | ||||
| 			_legacyCacheDir = cacheDirContext.CacheDir.Path + Java.IO.File.Separator + "OfflineCache" + Java.IO.File.Separator; | ||||
| 			if (!Directory.Exists(_legacyCacheDir)) | ||||
| 				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() | ||||
| 		{ | ||||
| 			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; } } | ||||
| @@ -105,7 +111,11 @@ namespace keepass2android.Io | ||||
| 		{ | ||||
| 			SHA256Managed sha256 = new SHA256Managed(); | ||||
| 			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) | ||||
| @@ -168,7 +178,9 @@ namespace keepass2android.Io | ||||
| 				if (!IsCached(ioc)) | ||||
| 					throw; | ||||
|  | ||||
| #if DEBUG | ||||
|                 Kp2aLog.Log("couldn't open from remote " + ioc.Path); | ||||
| #endif | ||||
| 				Kp2aLog.Log(ex.ToString()); | ||||
|  | ||||
| 				_cacheSupervisor.CouldntOpenFromRemote(ioc, ex); | ||||
|   | ||||
| @@ -233,7 +233,6 @@ namespace keepass2android.Io | ||||
|  | ||||
| 		public FileDescription GetFileDescription(IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			Kp2aLog.Log("GetFileDescription "+ioc.Path); | ||||
| 			try | ||||
| 			{ | ||||
| 				return ConvertToFileDescription(Jfs.GetFileEntry(IocToPath(ioc))); | ||||
| @@ -302,7 +301,9 @@ namespace keepass2android.Io | ||||
|  | ||||
| 		public void OnResume(IFileStorageSetupActivity activity) | ||||
| 		{ | ||||
| #if DEBUG | ||||
| 			Kp2aLog.Log("JFS/OnResume Ioc.Path=" +activity.Ioc.Path+". Path="+((IJavaFileStorageFileStorageSetupActivity)activity).Path); | ||||
| #endif | ||||
| 			_jfs.OnResume(((IJavaFileStorageFileStorageSetupActivity) activity)); | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -91,7 +91,7 @@ | ||||
|     <Compile Include="database\CheckDatabaseForChanges.cs" /> | ||||
|     <Compile Include="database\edit\AddTemplateEntries.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\MoveElements.cs" /> | ||||
|     <Compile Include="database\KdbDatabaseFormat.cs" /> | ||||
| @@ -103,6 +103,7 @@ | ||||
|     <Compile Include="DataExchange\Formats\KeePassKdb2x.cs" /> | ||||
|     <Compile Include="DataExchange\Formats\KeePassXml2x.cs" /> | ||||
|     <Compile Include="DataExchange\PwExportInfo.cs" /> | ||||
|     <Compile Include="ElementAndDatabaseId.cs" /> | ||||
|     <Compile Include="Io\AndroidContentStorage.cs" /> | ||||
|     <Compile Include="Io\BuiltInFileStorage.cs" /> | ||||
|     <Compile Include="Io\CachingFileStorage.cs" /> | ||||
| @@ -156,10 +157,6 @@ | ||||
|     <Compile Include="Utils\Spr\SprEngine.PickChars.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\AdalBindings\AdalBindings.csproj"> | ||||
|       <Project>{0b109c0e-0514-4340-8779-5bd6a0dde84e}</Project> | ||||
|       <Name>AdalBindings</Name> | ||||
|     </ProjectReference> | ||||
|     <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj"> | ||||
|       <Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project> | ||||
|       <Name>JavaFileStorageBindings</Name> | ||||
|   | ||||
| @@ -52,6 +52,7 @@ namespace keepass2android | ||||
|  | ||||
| 		public void UpdateMessage (String message) | ||||
| 		{ | ||||
| 		    Kp2aLog.Log("status message: " + message); | ||||
|             _message = message; | ||||
| 			if ( _app!= null && _progressDialog != null && _handler != null ) { | ||||
| 				_handler.Post(() => {_progressDialog.SetMessage(message); } ); | ||||
| @@ -60,6 +61,7 @@ namespace keepass2android | ||||
|  | ||||
| 		public void UpdateSubMessage(String submessage) | ||||
| 		{ | ||||
| 		    Kp2aLog.Log("status submessage: " + submessage); | ||||
| 		    _submessage = submessage; | ||||
| 			if (_app != null && _progressDialog != null && _handler != null) | ||||
| 			{ | ||||
|   | ||||
| @@ -23,7 +23,7 @@ namespace keepass2android | ||||
| 	/// <summary> | ||||
| 	/// EqualityComparer implementation to compare PwGroups based on their Id | ||||
| 	/// </summary> | ||||
| 	class PwGroupEqualityFromIdComparer: IEqualityComparer<PwGroup> | ||||
| 	public class PwGroupEqualityFromIdComparer: IEqualityComparer<PwGroup> | ||||
| 	{ | ||||
| 		#region IEqualityComparer implementation			 | ||||
| 		public bool Equals (PwGroup x, PwGroup y) | ||||
|   | ||||
| @@ -103,7 +103,7 @@ namespace keepass2android | ||||
| 			PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) {IsVirtual = true}; | ||||
| 			if (String.IsNullOrWhiteSpace(host)) | ||||
| 				return pgResults; | ||||
| 			foreach (PwEntry entry in database.Entries.Values) | ||||
| 			foreach (PwEntry entry in database.EntriesById.Values) | ||||
| 			{ | ||||
| 				string otherUrl = entry.Strings.ReadSafe(PwDefs.UrlField); | ||||
| 				otherUrl = SprEngine.Compile(otherUrl, new SprContext(entry, database.KpDatabase, SprCompileFlags.References)); | ||||
|   | ||||
| @@ -86,6 +86,8 @@ namespace keepass2android | ||||
| 		ReadOnlyReason_PreKitKat, | ||||
| 		ReadOnlyReason_ReadOnlyFlag, | ||||
| 		ReadOnlyReason_ReadOnlyKitKat, | ||||
|         ReadOnlyReason_LocalBackup | ||||
|         ReadOnlyReason_LocalBackup, | ||||
|         Ok, | ||||
|         cancel | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -29,7 +29,7 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				IOConnectionInfo ioc = _app.GetDb().Ioc; | ||||
| 				IOConnectionInfo ioc = _app.CurrentDb.Ioc; | ||||
| 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
| 				if (fileStorage is CachingFileStorage) | ||||
| 				{ | ||||
| @@ -49,7 +49,7 @@ namespace keepass2android | ||||
| 					hashingRemoteStream.CopyTo(remoteData); | ||||
| 					hashingRemoteStream.Close(); | ||||
| 					 | ||||
| 					if (!MemUtil.ArraysEqual(_app.GetDb().KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash)) | ||||
| 					if (!MemUtil.ArraysEqual(_app.CurrentDb.KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash)) | ||||
| 					{ | ||||
| 						_app.TriggerReload(_context); | ||||
| 						Finish(true); | ||||
|   | ||||
| @@ -26,6 +26,7 @@ using KeePassLib; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| using keepass2android.Io; | ||||
| using KeePassLib.Interfaces; | ||||
| using KeePassLib.Utility; | ||||
| using Exception = System.Exception; | ||||
| using String = System.String; | ||||
| @@ -33,26 +34,22 @@ using String = System.String; | ||||
| namespace keepass2android | ||||
| { | ||||
|  | ||||
| 	public class Database { | ||||
| 		 | ||||
|  | ||||
| 		public Dictionary<PwUuid, PwGroup> Groups = new Dictionary<PwUuid, PwGroup>(new PwUuidEqualityComparer()); | ||||
| 		public Dictionary<PwUuid, PwEntry> Entries = new Dictionary<PwUuid, PwEntry>(new PwUuidEqualityComparer()); | ||||
| 		public HashSet<PwGroup> Dirty = new HashSet<PwGroup>(new PwGroupEqualityFromIdComparer()); | ||||
| 	public class Database | ||||
| 	{ | ||||
| 	    public HashSet<IStructureItem> Elements = new HashSet<IStructureItem>(); | ||||
| 		public Dictionary<PwUuid, PwGroup> GroupsById = new Dictionary<PwUuid, PwGroup>(new PwUuidEqualityComparer()); | ||||
| 		public Dictionary<PwUuid, PwEntry> EntriesById = new Dictionary<PwUuid, PwEntry>(new PwUuidEqualityComparer()); | ||||
| 		public PwGroup Root; | ||||
| 		public PwDatabase KpDatabase; | ||||
| 		public IOConnectionInfo Ioc  | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return KpDatabase == null ? null : KpDatabase.IOConnectionInfo; | ||||
| 			} | ||||
| 		} | ||||
|                  | ||||
| 		/// <summary> | ||||
| 		/// Information about the last opened entry. Includes the entry but also transformed fields. | ||||
| 		/// </summary> | ||||
| 		public PwEntryOutput LastOpenedEntry { get; set; } | ||||
|                 return KpDatabase?.IOConnectionInfo; | ||||
|                  | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// if an OTP key was used, this property tells the location of the OTP auxiliary file. | ||||
| @@ -74,30 +71,13 @@ namespace keepass2android | ||||
| 			CanWrite = true; //default | ||||
|         } | ||||
|  | ||||
| 		private bool _loaded; | ||||
|  | ||||
|         private bool _reloadRequested; | ||||
| 	    private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default); | ||||
|  | ||||
| 		public bool ReloadRequested | ||||
|         { | ||||
|             get { return _reloadRequested; } | ||||
|             set { _reloadRequested = value; } | ||||
|         } | ||||
|  | ||||
| 		public bool Loaded { | ||||
| 			get { return _loaded;} | ||||
| 			set { _loaded = value; } | ||||
| 		} | ||||
| 		public bool ReloadRequested { get; set; } | ||||
|  | ||||
| 	    public bool DidOpenFileChange() | ||||
| 		{ | ||||
| 			if (Loaded == false) | ||||
| 			{ | ||||
| 				return false; | ||||
| 			} | ||||
| 			return _app.GetFileStorage(Ioc).CheckForFileChangeFast(Ioc, LastFileVersion); | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
|  | ||||
| @@ -112,8 +92,6 @@ namespace keepass2android | ||||
| 			Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo); | ||||
| 			var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo); | ||||
| 			PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat); | ||||
| 			try | ||||
| 			{ | ||||
| 		    LastFileVersion = fileVersion; | ||||
|  | ||||
| 		    status.UpdateSubMessage(""); | ||||
| @@ -128,16 +106,6 @@ namespace keepass2android | ||||
| 		    _databaseFormat = databaseFormat; | ||||
|  | ||||
| 		    CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo); | ||||
| 				Loaded = true; | ||||
| 			} | ||||
| 			catch (Exception) | ||||
| 			{ | ||||
| 				Clear();				 | ||||
| 				throw; | ||||
| 			} | ||||
| 			 | ||||
|  | ||||
| 			 | ||||
| 		} | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -219,8 +187,7 @@ namespace keepass2android | ||||
|  | ||||
| 		public void SaveData()  { | ||||
|              | ||||
| 			KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions); | ||||
| 			using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions)) | ||||
| 			using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions))) | ||||
| 			{ | ||||
| 				DatabaseFormat.Save(KpDatabase, trans.OpenFile()); | ||||
| 				 | ||||
| @@ -239,14 +206,18 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				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)  | ||||
| 			{ | ||||
| 				if (checkForDuplicateUuids) | ||||
| @@ -258,7 +229,6 @@ namespace keepass2android | ||||
| 					} | ||||
| 					 * */ | ||||
| 				} | ||||
| 				Groups[g.Uuid] = g; | ||||
| 				PopulateGlobals(g); | ||||
| 			} | ||||
| 		} | ||||
| @@ -267,32 +237,14 @@ namespace keepass2android | ||||
| 			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 UpdateGlobals() | ||||
| 	    { | ||||
| 	        EntriesById.Clear(); | ||||
| 	        GroupsById.Clear(); | ||||
| 	        Elements.Clear(); | ||||
|             PopulateGlobals(Root); | ||||
| 	    } | ||||
| 		 | ||||
| 		public void MarkAllGroupsAsDirty() { | ||||
| 			foreach ( PwGroup group in Groups.Values ) { | ||||
| 				Dirty.Add(group); | ||||
| 			} | ||||
| 			 | ||||
|  | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| 	[Serializable] | ||||
|   | ||||
| @@ -216,9 +216,16 @@ namespace keepass2android | ||||
|  | ||||
| 		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; | ||||
| 		    } | ||||
| 			 | ||||
|  | ||||
| 		} | ||||
| #endif | ||||
|   | ||||
| @@ -14,13 +14,13 @@ namespace keepass2android | ||||
| 	public class PwEntryOutput | ||||
| 	{ | ||||
| 		private readonly PwEntry _entry; | ||||
| 		private readonly PwDatabase _db; | ||||
| 		private readonly Database _db; | ||||
| 		private readonly ProtectedStringDictionary _outputStrings = new ProtectedStringDictionary(); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Constructs the PwEntryOutput by replacing the placeholders | ||||
| 		/// </summary> | ||||
| 		public PwEntryOutput(PwEntry entry, PwDatabase db) | ||||
| 		public PwEntryOutput(PwEntry entry, Database db) | ||||
| 		{ | ||||
| 			_entry = entry; | ||||
| 			_db = db; | ||||
| @@ -34,7 +34,7 @@ namespace keepass2android | ||||
| 		string GetStringAndReplacePlaceholders(string 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; | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -26,7 +26,7 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				IOConnectionInfo ioc = _app.GetDb().Ioc; | ||||
| 				IOConnectionInfo ioc = _app.CurrentDb.Ioc; | ||||
| 				IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
| 				if (!(fileStorage is CachingFileStorage)) | ||||
| 				{ | ||||
| @@ -70,10 +70,12 @@ namespace keepass2android | ||||
| 									Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); | ||||
| 								} | ||||
| 								_saveDb = null; | ||||
| 							}), false, remoteData); | ||||
| 							}), _app.CurrentDb, false, remoteData); | ||||
| 						_saveDb.Run(); | ||||
|  | ||||
| 						_app.GetDb().MarkAllGroupsAsDirty(); | ||||
|                         _app.CurrentDb.UpdateGlobals(); | ||||
|  | ||||
| 						_app.MarkAllGroupsAsDirty(); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| @@ -103,6 +105,7 @@ namespace keepass2android | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
|                 Kp2aLog.LogUnexpectedError(e); | ||||
| 				Finish(false, e.Message); | ||||
| 			} | ||||
| 			 | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace keepass2android | ||||
| 	public class AddEntry : RunnableOnFinish { | ||||
| 		protected Database Db | ||||
| 		{ | ||||
| 			get { return _app.GetDb(); } | ||||
| 			get { return _app.CurrentDb; } | ||||
| 		} | ||||
|  | ||||
| 		private readonly IKp2aApp _app; | ||||
| @@ -37,13 +37,13 @@ namespace keepass2android | ||||
| 			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; | ||||
| 			_parentGroup = parentGroup; | ||||
| 			_app = app; | ||||
| 			_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 | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| @@ -68,11 +68,12 @@ namespace keepass2android | ||||
| 		private class AfterAdd : OnFinish { | ||||
| 			private readonly Database _db; | ||||
| 			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; | ||||
| 				_entry = entry; | ||||
|  | ||||
| 		        _app = app; | ||||
| 		    } | ||||
| 			 | ||||
|  | ||||
| @@ -83,10 +84,11 @@ namespace keepass2android | ||||
| 					PwGroup parent = _entry.ParentGroup;  | ||||
| 					 | ||||
| 					// Mark parent group dirty | ||||
| 					_db.Dirty.Add(parent); | ||||
| 					_app.DirtyGroups.Add(parent); | ||||
| 					 | ||||
| 					// Add entry to global | ||||
| 					_db.Entries[_entry.Uuid] = _entry; | ||||
| 					_db.EntriesById[_entry.Uuid] = _entry; | ||||
| 				    _db.Elements.Add(_entry); | ||||
|  | ||||
| 				} else | ||||
| 				{ | ||||
|   | ||||
| @@ -26,13 +26,16 @@ namespace keepass2android | ||||
| 	public class AddGroup : RunnableOnFinish { | ||||
| 		internal Database Db | ||||
| 		{ | ||||
| 			get { return _app.GetDb(); } | ||||
| 			get { return _app.CurrentDb; } | ||||
| 		} | ||||
|  | ||||
|         public IKp2aApp App { get => _app; } | ||||
|  | ||||
|         private IKp2aApp _app; | ||||
| 		private readonly String _name; | ||||
| 		private readonly int _iconId; | ||||
| 		private readonly PwUuid _groupCustomIconId; | ||||
| 		internal PwGroup Group; | ||||
| 	    public PwGroup Group; | ||||
| 		internal PwGroup Parent; | ||||
| 		protected bool DontSave; | ||||
| 		readonly Activity _ctx; | ||||
| @@ -67,9 +70,11 @@ namespace keepass2android | ||||
| 				Group.CustomIconUuid = _groupCustomIconId; | ||||
| 			} | ||||
| 			Parent.AddGroup(Group, true); | ||||
| 		    _app.CurrentDb.GroupsById[Group.Uuid] = Group; | ||||
| 		    _app.CurrentDb.Elements.Add(Group); | ||||
|  | ||||
|             // 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.Run(); | ||||
| 		} | ||||
| @@ -86,13 +91,15 @@ namespace keepass2android | ||||
| 				 | ||||
| 				if ( Success ) { | ||||
| 					// Mark parent group dirty | ||||
| 					_addGroup.Db.Dirty.Add(_addGroup.Parent); | ||||
| 					_addGroup.App.DirtyGroups.Add(_addGroup.Parent); | ||||
| 					 | ||||
| 					// 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 { | ||||
| 					StatusLogger.UpdateMessage(UiStringKey.UndoingChanges); | ||||
| 					_addGroup.Parent.Groups.Remove(_addGroup.Group); | ||||
|  | ||||
| 				} | ||||
| 				 | ||||
| 				base.Run(); | ||||
|   | ||||
| @@ -28,7 +28,7 @@ namespace keepass2android | ||||
| { | ||||
| 	public class AddTemplateEntries : RunnableOnFinish { | ||||
|  | ||||
| 		class TemplateEntry | ||||
| 		public class TemplateEntry | ||||
| 		{ | ||||
| 			public UiStringKey Title { get; set; } | ||||
| 			public PwIcon Icon { get; set; } | ||||
| @@ -47,11 +47,12 @@ namespace keepass2android | ||||
| 				void AddToEntry(IKp2aApp app, PwEntry entry, int position); | ||||
| 			} | ||||
|  | ||||
| 			internal enum FieldType | ||||
| 		    public enum FieldType | ||||
| 			{ | ||||
| 				Inline, ProtectedInline | ||||
| 			} | ||||
| 			internal enum SpecialFieldKey | ||||
|  | ||||
| 		    public enum SpecialFieldKey | ||||
| 			{ | ||||
| 				ExpDate, OverrideUrl, Tags | ||||
| 			} | ||||
| @@ -125,7 +126,7 @@ namespace keepass2android | ||||
|  | ||||
| 		protected Database Db | ||||
| 		{ | ||||
| 			get { return _app.GetDb(); } | ||||
| 			get { return _app.CurrentDb; } | ||||
| 		} | ||||
|  | ||||
| 		private readonly IKp2aApp _app; | ||||
| @@ -140,7 +141,7 @@ namespace keepass2android | ||||
| 			//_onFinishToRun = new AfterAdd(this, OnFinishToRun); | ||||
| 		} | ||||
|  | ||||
| 		static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>() | ||||
| 		public static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>() | ||||
| 			{ | ||||
| 				new TemplateEntry() | ||||
| 				{ | ||||
| @@ -285,9 +286,20 @@ 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 static string TemplateIdStringKey | ||||
| 	    { | ||||
| 	        get { return "KP2A_TemplateId"; } | ||||
| 	    } | ||||
|  | ||||
| 	    public override void Run() {	 | ||||
| @@ -298,10 +310,10 @@ namespace keepass2android | ||||
|  | ||||
| 			if (addedEntries.Any()) | ||||
| 			{ | ||||
| 				_app.GetDb().Dirty.Add(templateGroup); | ||||
| 				_app.DirtyGroups.Add(templateGroup); | ||||
|  | ||||
| 				// Commit to disk | ||||
| 				SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | ||||
| 				SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); | ||||
| 				save.SetStatusLogger(StatusLogger); | ||||
| 				save.Run(); | ||||
| 			} | ||||
| @@ -315,26 +327,28 @@ namespace keepass2android | ||||
| 			} | ||||
|  | ||||
| 			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 | ||||
| 				templateGroup = new PwGroup(true, true, _app.GetResourceString(UiStringKey.TemplateGroupName), PwIcon.Folder); | ||||
| 				_app.GetDb().KpDatabase.RootGroup.AddGroup(templateGroup, true); | ||||
| 				_app.GetDb().KpDatabase.EntryTemplatesGroup = templateGroup.Uuid; | ||||
| 				_app.GetDb().KpDatabase.EntryTemplatesGroupChanged = DateTime.Now; | ||||
| 				_app.GetDb().Dirty.Add(_app.GetDb().KpDatabase.RootGroup); | ||||
| 				_app.GetDb().Groups[templateGroup.Uuid] = templateGroup; | ||||
| 				_app.CurrentDb.KpDatabase.RootGroup.AddGroup(templateGroup, true); | ||||
| 				_app.CurrentDb.KpDatabase.EntryTemplatesGroup = templateGroup.Uuid; | ||||
| 				_app.CurrentDb.KpDatabase.EntryTemplatesGroupChanged = DateTime.Now; | ||||
| 				_app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup); | ||||
| 				_app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup; | ||||
| 			    _app.CurrentDb.Elements.Add(templateGroup); | ||||
|  | ||||
| 			} | ||||
| 			addedEntries = new List<PwEntry>(); | ||||
|  | ||||
| 			foreach (var template in TemplateEntries) | ||||
| 			{ | ||||
| 				if (_app.GetDb().Entries.ContainsKey(template.Uuid)) | ||||
| 				if (_app.CurrentDb.EntriesById.ContainsKey(template.Uuid)) | ||||
| 					continue; | ||||
| 				PwEntry entry = CreateEntry(template); | ||||
| 				templateGroup.AddEntry(entry, true); | ||||
| 				addedEntries.Add(entry); | ||||
| 				_app.GetDb().Entries[entry.Uuid] = entry; | ||||
| 				_app.CurrentDb.EntriesById[entry.Uuid] = entry; | ||||
| 			} | ||||
| 			return templateGroup; | ||||
| 		} | ||||
| @@ -375,6 +389,10 @@ namespace keepass2android | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 	    public static bool IsTemplateId(PwUuid pwUuid) | ||||
| 	    { | ||||
| 	        return TemplateEntries.Any(te => te.Uuid.Equals(pwUuid)); | ||||
| 	    } | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -27,23 +27,22 @@ namespace keepass2android | ||||
| { | ||||
| 	 | ||||
| 	public class CreateDb : RunnableOnFinish { | ||||
| 		 | ||||
| 		private const ulong DefaultEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; | ||||
| 		 | ||||
| 	    private readonly IOConnectionInfo _ioc; | ||||
| 		private readonly bool _dontSave; | ||||
| 		private readonly Activity _ctx; | ||||
|         private readonly IKp2aApp _app; | ||||
| 		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; | ||||
| 			_ioc = ioc; | ||||
| 			_dontSave = dontSave; | ||||
| 	        _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) | ||||
| 		{ | ||||
| 			_ctx = ctx; | ||||
| @@ -51,12 +50,13 @@ namespace keepass2android | ||||
| 			_dontSave = dontSave; | ||||
| 			_app = app; | ||||
| 			_key = key; | ||||
| 		    _makeCurrent = makeCurrent; | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		public override void Run() { | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.progress_create); | ||||
| 			Database db = _app.CreateNewDatabase(); | ||||
| 			Database db = _app.CreateNewDatabase(_makeCurrent); | ||||
|  | ||||
| 			db.KpDatabase = new KeePassLib.PwDatabase(); | ||||
| 			 | ||||
| @@ -74,7 +74,6 @@ namespace keepass2android | ||||
| 			 | ||||
| 			// Set Database state | ||||
| 			db.Root = db.KpDatabase.RootGroup; | ||||
| 			db.Loaded = true; | ||||
| 			db.SearchHelper = new SearchDbHelper(_app); | ||||
|  | ||||
| 			// Add a couple default groups | ||||
| @@ -88,11 +87,13 @@ namespace keepass2android | ||||
| 			addTemplates.AddTemplates(out addedEntries); | ||||
| 			 | ||||
| 			// Commit changes | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, _dontSave); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, db, OnFinishToRun, _dontSave); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			_onFinishToRun = null; | ||||
| 			save.Run(); | ||||
|  | ||||
| 		    db.UpdateGlobals(); | ||||
|  | ||||
|  | ||||
| 		} | ||||
| 		 | ||||
|   | ||||
| @@ -31,7 +31,7 @@ namespace keepass2android | ||||
|  | ||||
| 	    public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) { | ||||
| 			Ctx = activiy; | ||||
| 			Db = app.GetDb(); | ||||
| 			Db = app.FindDatabaseForElement(entry); | ||||
| 			_entry = entry; | ||||
| 			 | ||||
| 		} | ||||
| @@ -40,7 +40,7 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			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) | ||||
|         { | ||||
| 			base.SetMembers(activity, app.GetDb()); | ||||
| 			base.SetMembers(activity, app.FindDatabaseForElement(group)); | ||||
|  | ||||
| 			_group = group; | ||||
| 	        DontSave = dontSave; | ||||
| @@ -58,7 +58,7 @@ namespace keepass2android | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				return App.GetDb().DatabaseFormat.CanRecycle && CanRecycleGroup(_group); | ||||
| 				return Db.DatabaseFormat.CanRecycle && CanRecycleGroup(_group); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -7,12 +7,12 @@ using KeePassLib.Interfaces; | ||||
| 
 | ||||
| namespace keepass2android | ||||
| { | ||||
| 	public class DeleteMultipleItems : DeleteRunnable | ||||
| 	public class DeleteMultipleItemsFromOneDatabase : DeleteRunnable | ||||
| 	{ | ||||
| 		private readonly List<IStructureItem> _elementsToDelete; | ||||
| 		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) | ||||
| 		{ | ||||
| 			_elementsToDelete = elementsToDelete; | ||||
| @@ -21,12 +21,13 @@ namespace keepass2android | ||||
| 			//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) | ||||
| 			_canRecycle = DetermineCanRecycle(); | ||||
| 		    ShowDatabaseIocInStatus = true; | ||||
| 		} | ||||
| 
 | ||||
| 		private bool DetermineCanRecycle() | ||||
| 		{ | ||||
| 			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."); | ||||
| 				return false; | ||||
| @@ -100,7 +100,8 @@ namespace keepass2android | ||||
| 						}; | ||||
|  | ||||
| 				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; | ||||
|  | ||||
| 				bGroupListUpdateRequired = true; | ||||
| @@ -121,6 +122,8 @@ namespace keepass2android | ||||
|  | ||||
| 		public void Start() | ||||
| 		{ | ||||
|             string messageSuffix = ShowDatabaseIocInStatus ? "(" + App.GetFileStorage(Db.Ioc).GetDisplayName(Db.Ioc) + ")" : ""; | ||||
|  | ||||
|             if (CanRecycle) | ||||
| 			{ | ||||
| 				App.AskYesNoCancel(UiStringKey.AskDeletePermanently_title, | ||||
| @@ -130,6 +133,7 @@ namespace keepass2android | ||||
| 					    DeletePermanently = true; | ||||
| 					    ProgressTask pt = new ProgressTask(App, Ctx, this); | ||||
| 					    pt.Run(); | ||||
|  | ||||
| 					}, | ||||
| 				(dlgSender, dlgEvt) => | ||||
| 				{ | ||||
| @@ -138,7 +142,7 @@ namespace keepass2android | ||||
| 				    pt.Run(); | ||||
| 				}, | ||||
| 				(dlgSender, dlgEvt) => { }, | ||||
| 				Ctx); | ||||
| 				Ctx, messageSuffix); | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -154,7 +158,7 @@ namespace keepass2android | ||||
| 					}, | ||||
| 				null, | ||||
| 				(dlgSender, dlgEvt) => { }, | ||||
| 				Ctx); | ||||
| 				Ctx, messageSuffix); | ||||
|  | ||||
| 				 | ||||
| 			} | ||||
| @@ -182,7 +186,8 @@ namespace keepass2android | ||||
| 					PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); | ||||
| 					pd.DeletedObjects.Add(pdo); | ||||
| 					touchedGroups.Add(pgParent); | ||||
| 				    Db.Entries.Remove(pe.Uuid); | ||||
| 				    Db.EntriesById.Remove(pe.Uuid); | ||||
| 				    Db.Elements.Remove(pe); | ||||
| 				} | ||||
| 				else // Recycle | ||||
| 				{ | ||||
| @@ -215,30 +220,40 @@ namespace keepass2android | ||||
| 				if (success) | ||||
| 				{ | ||||
| 					foreach (var g in touchedGroups) | ||||
| 						Db.Dirty.Add(g); | ||||
| 						App.DirtyGroups.Add(g); | ||||
| 					foreach (var g in permanentlyDeletedGroups) | ||||
| 					{ | ||||
|                         //remove groups from global lists if present there | ||||
| 						Db.Dirty.Remove(g); | ||||
| 						Db.Groups.Remove(g.Uuid); | ||||
| 					    App.DirtyGroups.Remove(g); | ||||
| 						Db.GroupsById.Remove(g.Uuid); | ||||
| 					    Db.Elements.Remove(g); | ||||
|  | ||||
| 					} | ||||
|  | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					// Let's not bother recovering from a failure to save.  It is too much work. | ||||
| 					App.LockDatabase(false); | ||||
| 					App.Lock(false); | ||||
| 				} | ||||
| 			}, OnFinishToRun); | ||||
|  | ||||
| 			// Commit database | ||||
| 			SaveDb save = new SaveDb(Ctx, App, OnFinishToRun, false); | ||||
| 			SaveDb save = new SaveDb(Ctx, App, Db, OnFinishToRun, false); | ||||
| 		    save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus; | ||||
|  | ||||
|             save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
|  | ||||
|  | ||||
| 		} | ||||
|  | ||||
| 	    public bool ShowDatabaseIocInStatus | ||||
| 	    { | ||||
| 	        get; | ||||
| 	        set; | ||||
| 	    } | ||||
|  | ||||
| 	    protected abstract void PerformDelete(List<PwGroup> touchedGroups, List<PwGroup> permanentlyDeletedGroups); | ||||
|  | ||||
| 		public abstract UiStringKey StatusMessage { get; } | ||||
|   | ||||
| @@ -26,8 +26,11 @@ namespace keepass2android | ||||
| 	public class EditGroup : RunnableOnFinish { | ||||
| 		internal Database Db | ||||
| 		{ | ||||
| 			get { return _app.GetDb(); } | ||||
| 			get { return _app.FindDatabaseForElement(Group); } | ||||
| 		} | ||||
|  | ||||
|         public IKp2aApp App { get => _app; } | ||||
|  | ||||
|         private IKp2aApp _app; | ||||
| 		private readonly String _name; | ||||
| 		private readonly PwIcon _iconId; | ||||
| @@ -57,7 +60,7 @@ namespace keepass2android | ||||
| 			Group.Touch(true); | ||||
|  | ||||
| 			// Commit to disk | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, Db, OnFinishToRun); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| @@ -76,10 +79,10 @@ namespace keepass2android | ||||
| 				 | ||||
| 				if ( Success ) { | ||||
| 					// Mark parent group dirty | ||||
| 					_editGroup.Db.Dirty.Add(_editGroup.Group.ParentGroup); | ||||
| 					_editGroup.App.DirtyGroups.Add(_editGroup.Group.ParentGroup); | ||||
| 				} else | ||||
| 				{ | ||||
| 					_editGroup._app.LockDatabase(false); | ||||
| 					_editGroup._app.Lock(false); | ||||
| 				} | ||||
| 				 | ||||
| 				base.Run(); | ||||
|   | ||||
| @@ -21,6 +21,7 @@ using System.Linq; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using Android.App; | ||||
| using keepass2android.database.edit; | ||||
| using KeePassLib; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Serialization; | ||||
| @@ -36,18 +37,23 @@ namespace keepass2android | ||||
| 		private readonly bool _rememberKeyfile; | ||||
| 		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; | ||||
| 			_ioc = ioc; | ||||
| 			_databaseData = databaseData; | ||||
| 			_compositeKey = compositeKey; | ||||
| 			_keyfileOrProvider = keyfileOrProvider; | ||||
| 		    _updateLastUsageTimestamp = updateLastUsageTimestamp; | ||||
| 		    _makeCurrent = makeCurrent; | ||||
|  | ||||
|  | ||||
| 		    _rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);  | ||||
| 		} | ||||
|  | ||||
| 	    protected bool success = false; | ||||
| 	    private bool _updateLastUsageTimestamp; | ||||
| 	    private readonly bool _makeCurrent; | ||||
|  | ||||
| 	    public override void Run() | ||||
| 		{ | ||||
| @@ -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: | ||||
| 					_format = new KdbxDatabaseFormat(KdbpFile.GetFormatToUse(_ioc)); | ||||
| 					TryLoad(databaseStream); | ||||
|  | ||||
|  | ||||
|  | ||||
| 				    success = true; | ||||
| 				} | ||||
| 				catch (Exception e) | ||||
| 				{ | ||||
| @@ -89,7 +99,7 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				Kp2aLog.Log("KeyFileException"); | ||||
| 				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) | ||||
| 			{ | ||||
| @@ -100,20 +110,20 @@ namespace keepass2android | ||||
| 					// Override the message shown with the last (hopefully most recent) inner exception | ||||
| 					Kp2aLog.LogUnexpectedError(innerException); | ||||
| 				} | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, Exception); | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, false, Exception); | ||||
| 				return; | ||||
| 			} | ||||
| 			catch (DuplicateUuidsException e) | ||||
| 			{ | ||||
| 				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; | ||||
| 			} | ||||
| 			catch (Exception e) | ||||
| 			{ | ||||
| 				if (!(e is InvalidCompositeKeyException)) | ||||
| 					Kp2aLog.LogUnexpectedError(e); | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, Exception); | ||||
| 				Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, false, Exception); | ||||
| 				return; | ||||
| 			} | ||||
| 			 | ||||
| @@ -125,7 +135,7 @@ namespace keepass2android | ||||
| 		/// </summary> | ||||
| 		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 | ||||
| 			//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: | ||||
| 			try | ||||
| 			{ | ||||
|                 _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format); | ||||
|                 Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent); | ||||
| 				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); | ||||
| 			    return newDb; | ||||
| 			} | ||||
| 			catch (OldFormatException) | ||||
| 			{ | ||||
| 				_format = new KdbDatabaseFormat(_app); | ||||
| 				TryLoad(databaseStream); | ||||
| 				return TryLoad(databaseStream); | ||||
| 			} | ||||
| 			catch (InvalidCompositeKeyException) | ||||
| 			{ | ||||
| @@ -162,7 +169,7 @@ namespace keepass2android | ||||
| 					//retry without password: | ||||
| 					_compositeKey.RemoveUserKey(passwordKey); | ||||
| 					//retry: | ||||
| 					TryLoad(databaseStream); | ||||
| 					return TryLoad(databaseStream); | ||||
| 				} | ||||
| 				else throw; | ||||
| 			} | ||||
| @@ -175,7 +182,7 @@ namespace keepass2android | ||||
|             { | ||||
|                 keyfileOrProvider = ""; | ||||
|             } | ||||
|             _app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider); | ||||
|             _app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider, _updateLastUsageTimestamp); | ||||
| 		} | ||||
| 		 | ||||
| 		 | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Text.RegularExpressions; | ||||
| 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) | ||||
| 		    { | ||||
|  | ||||
|                 _app.GetDb().Dirty.Add(elementToMove.ParentGroup); | ||||
|                 _app.DirtyGroups.Add(elementToMove.ParentGroup); | ||||
|                  | ||||
|  | ||||
|                 PwGroup pgParent = elementToMove.ParentGroup; | ||||
|                 if (pgParent != _targetGroup) | ||||
| @@ -63,8 +73,14 @@ namespace keepass2android.database.edit | ||||
|                         PwEntry entry = elementToMove as PwEntry; | ||||
|                         if (entry != null) | ||||
|                         { | ||||
|                             var dbRem = _app.FindDatabaseForElement(entry); | ||||
|                             removeDatabases.Add(dbRem); | ||||
|                             dbRem.EntriesById.Remove(entry.Uuid); | ||||
|                             dbRem.Elements.Remove(entry); | ||||
|                             pgParent.Entries.Remove(entry); | ||||
|                             _targetGroup.AddEntry(entry, true, true); | ||||
|                             addDatabase.EntriesById.Add(entry.Uuid, entry); | ||||
|                             addDatabase.Elements.Add(entry); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
| @@ -74,26 +90,59 @@ namespace keepass2android.database.edit | ||||
|                                 Finish(false, _app.GetResourceString(UiStringKey.CannotMoveGroupHere)); | ||||
|                                 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); | ||||
|                             _targetGroup.AddGroup(group, true, true); | ||||
|                         } | ||||
|                             addDatabase.GroupsById.Add(group.Uuid, group); | ||||
|                             addDatabase.Elements.Add(group); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|  | ||||
| 		    } | ||||
|  | ||||
| 			_onFinishToRun = new ActionOnFinish(ActiveActivity, (success, message, activity) => | ||||
| 		     | ||||
|              | ||||
| 		     | ||||
| 		    //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) | ||||
| 		    { | ||||
| 				if (!success) | ||||
| 				{	// Let's not bother recovering from a failure. | ||||
| 					_app.LockDatabase(false); | ||||
| 		        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(); | ||||
| 		    } | ||||
| 			}, OnFinishToRun); | ||||
|  | ||||
| 			// Save | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun, false); | ||||
|  | ||||
| 		    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 Android; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.OS; | ||||
| @@ -29,6 +30,12 @@ namespace keepass2android | ||||
| 		protected String Message; | ||||
| 		protected Exception Exception; | ||||
|  | ||||
| 	    protected bool ImportantMessage | ||||
| 	    { | ||||
| 	        get; | ||||
| 	        set; | ||||
| 	    } | ||||
|  | ||||
|         protected OnFinish BaseOnFinish; | ||||
| 		protected Handler Handler; | ||||
| 		private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired | ||||
| @@ -77,12 +84,14 @@ namespace keepass2android | ||||
| 			Handler = null; | ||||
| 		} | ||||
|  | ||||
| 		public void SetResult(bool success, string message, Exception exception) { | ||||
| 		public void SetResult(bool success, string message, bool importantMessage, Exception exception) { | ||||
| 			Success = success; | ||||
| 			Message = message; | ||||
| 		    ImportantMessage = importantMessage; | ||||
| 			Exception = exception; | ||||
| 		} | ||||
|  | ||||
|  | ||||
| 	    public void SetResult(bool success) { | ||||
| 			Success = success; | ||||
| 		} | ||||
| @@ -90,7 +99,7 @@ namespace keepass2android | ||||
| 		public virtual void Run() { | ||||
| 			if (BaseOnFinish == null) return; | ||||
| 			// Pass on result on call finish | ||||
| 			BaseOnFinish.SetResult(Success, Message, Exception); | ||||
| 			BaseOnFinish.SetResult(Success, Message, ImportantMessage, Exception); | ||||
| 				 | ||||
| 			if ( Handler != null ) { | ||||
| 				Handler.Post(BaseOnFinish.Run);  | ||||
| @@ -100,13 +109,30 @@ namespace keepass2android | ||||
| 		} | ||||
| 		 | ||||
| 		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) ) { | ||||
| 			    Kp2aLog.Log("OnFinish message: " + message); | ||||
|                 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 ) { | ||||
| 				OnFinishToRun.SetResult(result, message, exception); | ||||
| 				OnFinishToRun.SetResult(result, message, importantMessage, exception); | ||||
| 				OnFinishToRun.Run(); | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -34,6 +34,7 @@ namespace keepass2android | ||||
|  | ||||
| 	public class SaveDb : RunnableOnFinish { | ||||
| 		private readonly IKp2aApp _app; | ||||
| 	    private readonly Database _db; | ||||
| 	    private readonly bool _dontSave; | ||||
|  | ||||
| 		/// <summary> | ||||
| @@ -43,9 +44,10 @@ namespace keepass2android | ||||
| 		private readonly Context _ctx; | ||||
| 		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) | ||||
| 		{ | ||||
| 		    _db = db; | ||||
| 			_ctx = ctx; | ||||
| 			_app = app; | ||||
| 			_dontSave = dontSave; | ||||
| @@ -59,23 +61,26 @@ namespace keepass2android | ||||
| 		/// <param name="finish"></param> | ||||
| 		/// <param name="dontSave"></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) | ||||
| 		{ | ||||
| 		    _db = db; | ||||
| 			_ctx = ctx; | ||||
| 			_app = app; | ||||
| 			_dontSave = dontSave; | ||||
| 			_streamForOrigFile = streamForOrigFile; | ||||
| 		} | ||||
|  | ||||
| 		public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish) | ||||
| 		public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish) | ||||
| 			: base(ctx, finish) | ||||
| 		{ | ||||
| 			_ctx = ctx; | ||||
| 			_app = app; | ||||
| 		    _db = db; | ||||
| 		    _dontSave = false; | ||||
| 		} | ||||
|  | ||||
| 	    public bool ShowDatabaseIocInStatus { get; set; } | ||||
| 	     | ||||
| 	    public override void Run () | ||||
| 		{ | ||||
| @@ -84,21 +89,27 @@ namespace keepass2android | ||||
| 			{ | ||||
| 				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. | ||||
| 						Finish(false,"Cannot save changes. File is read-only!"); | ||||
| 						return; | ||||
| 					} | ||||
|  | ||||
| 					StatusLogger.UpdateMessage(UiStringKey.saving_database); | ||||
| 					IOConnectionInfo ioc = _app.GetDb().Ioc; | ||||
| 				    string message = _app.GetResourceString(UiStringKey.saving_database); | ||||
|  | ||||
| 				    if (ShowDatabaseIocInStatus) | ||||
| 				        message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")"; | ||||
|  | ||||
|                     StatusLogger.UpdateMessage(message); | ||||
|                      | ||||
| 					IOConnectionInfo ioc = _db.Ioc; | ||||
| 					IFileStorage fileStorage = _app.GetFileStorage(ioc); | ||||
|  | ||||
| 					if (_streamForOrigFile == null) | ||||
| 					{ | ||||
| 						if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave)) | ||||
| 							|| (_app.GetDb().KpDatabase.HashOfFileOnDisk == null)) //first time saving | ||||
| 							|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving | ||||
| 						{ | ||||
| 							PerformSaveWithoutCheck(fileStorage, ioc); | ||||
| 							Finish(true); | ||||
| @@ -109,8 +120,8 @@ namespace keepass2android | ||||
|  | ||||
| 					if ( | ||||
| 						(_streamForOrigFile != null) | ||||
| 						|| fileStorage.CheckForFileChangeFast(ioc, _app.GetDb().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: | ||||
| 						|| fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion)  //first try to use the fast change detection | ||||
| 						|| (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) == FileHashChange.Changed) //if that fails, hash the file and compare: | ||||
| 						) | ||||
| 					{ | ||||
|  | ||||
| @@ -128,6 +139,7 @@ namespace keepass2android | ||||
| 										//small. | ||||
| 										MergeIn(fileStorage, ioc); | ||||
| 										PerformSaveWithoutCheck(fileStorage, ioc); | ||||
|                                         _db.UpdateGlobals(); | ||||
| 										Finish(true); | ||||
| 									}; | ||||
| 								RunInWorkerThread(runHandler); | ||||
| @@ -217,13 +229,13 @@ namespace keepass2android | ||||
| 			StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.SynchronizingDatabase)); | ||||
|  | ||||
| 			PwDatabase pwImp = new PwDatabase(); | ||||
| 			PwDatabase pwDatabase = _app.GetDb().KpDatabase; | ||||
| 			PwDatabase pwDatabase = _db.KpDatabase; | ||||
| 			pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); | ||||
| 			pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); | ||||
| 			pwImp.MasterKey = pwDatabase.MasterKey; | ||||
| 			var stream = GetStreamForBaseFile(fileStorage, ioc); | ||||
|  | ||||
| 			_app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); | ||||
| 			_db.DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); | ||||
| 			 | ||||
|  | ||||
| 			pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);  | ||||
| @@ -249,8 +261,8 @@ namespace keepass2android | ||||
| 		private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc) | ||||
| 		{ | ||||
| 			StatusLogger.UpdateSubMessage(""); | ||||
| 			_app.GetDb().SaveData(); | ||||
| 			_app.GetDb().LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); | ||||
| 			_db.SaveData(); | ||||
| 			_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc); | ||||
| 		} | ||||
|  | ||||
| 		public byte[] HashOriginalFile(IOConnectionInfo iocFile) | ||||
|   | ||||
| @@ -52,7 +52,7 @@ namespace keepass2android | ||||
| 		public override void Run () | ||||
| 		{ | ||||
| 			StatusLogger.UpdateMessage(UiStringKey.SettingPassword); | ||||
| 			PwDatabase pm = _app.GetDb().KpDatabase; | ||||
| 			PwDatabase pm = _app.CurrentDb.KpDatabase; | ||||
| 			CompositeKey newKey = new CompositeKey (); | ||||
| 			if (String.IsNullOrEmpty (_password) == false) { | ||||
| 				newKey.AddUserKey (new KcpPassword (_password));  | ||||
| @@ -74,7 +74,7 @@ namespace keepass2android | ||||
|  | ||||
| 			// Save Database | ||||
| 			_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.Run(); | ||||
| 		} | ||||
|   | ||||
| @@ -36,7 +36,7 @@ namespace keepass2android | ||||
| 		 | ||||
| 		public override void Run() { | ||||
| 			// Commit to disk | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); | ||||
| 			SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun); | ||||
| 			save.SetStatusLogger(StatusLogger); | ||||
| 			save.Run(); | ||||
| 		} | ||||
| @@ -59,7 +59,7 @@ namespace keepass2android | ||||
| 					if ( parent != null ) { | ||||
|  | ||||
| 						// Mark parent group dirty | ||||
| 						_app.GetDb().Dirty.Add(parent); | ||||
| 						_app.DirtyGroups.Add(parent); | ||||
| 							 | ||||
| 					} | ||||
| 					 | ||||
|   | ||||
| @@ -67,5 +67,4 @@ | ||||
|   <ItemGroup> | ||||
|     <Folder Include="libs\" /> | ||||
|   </ItemGroup> | ||||
|    | ||||
| </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="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> | ||||
|     <TransformFile Include="Transforms\Metadata.xml" /> | ||||
| @@ -84,7 +84,7 @@ | ||||
|   </Target> | ||||
|   --> | ||||
|   <ItemGroup> | ||||
|     <EmbeddedReferenceJar Include="Jars\pcloud-sdk-java-core-1.0.1.jar" /> | ||||
|     <EmbeddedReferenceJar Include="Jars\pcloud-sdk-java-core-1.1.0.jar" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -26,5 +26,5 @@ using Android.App; | ||||
| // You can specify all the values or you can default the Build and Revision Numbers  | ||||
| // by using the '*' as shown below: | ||||
| // [assembly: AssemblyVersion("1.0.*")] | ||||
| [assembly: AssemblyVersion("1.0.0.0")] | ||||
| [assembly: AssemblyFileVersion("1.0.0.0")] | ||||
| [assembly: AssemblyVersion("1.1.0.0")] | ||||
| [assembly: AssemblyFileVersion("1.1.0.0")] | ||||
|   | ||||
| @@ -53,8 +53,8 @@ | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <LibraryProjectZip Include="..\java\Keepass2AndroidPluginSDK2\app\build\outputs\aar\app-release.aar"> | ||||
|       <Link>Jars\app-release.aar</Link> | ||||
|     <LibraryProjectZip Include="..\java\Keepass2AndroidPluginSDK2\app\build\outputs\aar\Keepass2AndroidPluginSDK2-release.aar"> | ||||
|       <Link>Jars\Keepass2AndroidPluginSDK2-release.aar</Link> | ||||
|     </LibraryProjectZip> | ||||
|     <None Include="Jars\AboutJars.txt" /> | ||||
|     <None Include="Additions\AboutAdditions.txt" /> | ||||
|   | ||||
| @@ -7,4 +7,7 @@ call gradlew assemble | ||||
| cd ..\Keepass2AndroidPluginSDK2 | ||||
| call gradlew assemble | ||||
|  | ||||
| cd ..\PluginQR | ||||
| call gradlew assemble | ||||
|  | ||||
| cd ..\..\build-scripts | ||||
| @@ -15,4 +15,8 @@ pushd Keepass2AndroidPluginSDK2 | ||||
| ./gradlew assemble | ||||
| popd | ||||
|  | ||||
| pushd PluginQR | ||||
| ./gradlew assemble | ||||
| popd | ||||
|  | ||||
| popd | ||||
|   | ||||
| @@ -35,8 +35,12 @@ dependencies { | ||||
|     compile('com.onedrive.sdk:onedrive-sdk-android:1.2.0') { | ||||
|         transitive = false | ||||
|     } | ||||
|     compile 'com.pcloud.sdk:java-core:1.0.1' | ||||
|     compile 'com.pcloud.sdk:android:1.0.1' | ||||
|     compile 'com.pcloud.sdk:java-core:1.1.0' | ||||
|     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.microsoft.services.msa:msa-auth:0.8.6' | ||||
|     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.List; | ||||
| import java.util.NoSuchElementException; | ||||
| import java.util.UUID; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| import com.pcloud.sdk.ApiClient; | ||||
| @@ -27,6 +28,7 @@ import com.pcloud.sdk.PCloudSdk; | ||||
| import com.pcloud.sdk.RemoteEntry; | ||||
| import com.pcloud.sdk.RemoteFile; | ||||
| import com.pcloud.sdk.RemoteFolder; | ||||
| import com.pcloud.sdk.UploadOptions; | ||||
|  | ||||
| /** | ||||
|  * FileStorage implementation for PCloud provider. | ||||
| @@ -52,7 +54,7 @@ public class PCloudFileStorage extends JavaFileStorageBase | ||||
|  | ||||
|     @Override | ||||
|     public boolean requiresSetup(String path) { | ||||
|         return true; | ||||
|         return !this.isConnected(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -136,7 +138,9 @@ public class PCloudFileStorage extends JavaFileStorageBase | ||||
|         RemoteFolder remoteFolder = this.getRemoteFolderByPath(filePath); | ||||
|  | ||||
|         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) { | ||||
|             throw convertApiError(e); | ||||
|         } | ||||
| @@ -372,7 +376,7 @@ public class PCloudFileStorage extends JavaFileStorageBase | ||||
|  | ||||
|     private Exception convertApiError(ApiError e) { | ||||
|         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(); | ||||
|             return new UserInteractionRequiredException("Unlinked from PCloud! User must re-link.", e); | ||||
|         } else if (strErrorCode.startsWith("2")) { | ||||
|   | ||||
| @@ -24,7 +24,6 @@ import com.jcraft.jsch.UserInfo; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.support.annotation.NonNull; | ||||
|  | ||||
| public class SftpStorage extends JavaFileStorageBase { | ||||
|  | ||||
| @@ -358,12 +357,10 @@ public class SftpStorage extends JavaFileStorageBase { | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	@NonNull | ||||
| 	private String getBaseDir() { | ||||
| 		return _appContext.getFilesDir().getAbsolutePath(); | ||||
| 	} | ||||
|  | ||||
| 	@NonNull | ||||
| 	private String getKeyFileName() { | ||||
| 		return getBaseDir() + "/id_kp2a_rsa"; | ||||
| 	} | ||||
|   | ||||
| @@ -29,6 +29,7 @@ import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| import javax.net.ssl.SSLContext; | ||||
| import javax.net.ssl.SSLSocketFactory; | ||||
| @@ -70,9 +71,18 @@ public class WebDavStorage extends JavaFileStorageBase { | ||||
|  | ||||
|         String scheme = filename.substring(0, filename.indexOf("://")); | ||||
|         filename = filename.substring(scheme.length() + 3); | ||||
|         String userPwd = filename.substring(0, filename.indexOf('@')); | ||||
|         ci.username = decode(userPwd.substring(0, userPwd.indexOf(":"))); | ||||
|         ci.password = decode(userPwd.substring(userPwd.indexOf(":") + 1)); | ||||
|         int idxAt = filename.indexOf('@'); | ||||
|         if (idxAt >= 0) | ||||
|         { | ||||
|             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); | ||||
|         return ci; | ||||
|     } | ||||
| @@ -149,13 +159,19 @@ public class WebDavStorage extends JavaFileStorageBase { | ||||
|             sslContext.init(null, new TrustManager[] { trustManager }, null); | ||||
|             SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); | ||||
|  | ||||
|  | ||||
|             builder = builder.sslSocketFactory(sslSocketFactory, trustManager) | ||||
|                              .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(); | ||||
|  | ||||
|  | ||||
|         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 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
|         multiDexEnabled true | ||||
|  | ||||
|  | ||||
|     } | ||||
|     buildTypes { | ||||
|   | ||||
| @@ -59,6 +59,18 @@ | ||||
|                 <category android:name="android.intent.category.DEFAULT" /> | ||||
|             </intent-filter> | ||||
|         </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> | ||||
|  | ||||
|  | ||||
| @@ -68,6 +80,8 @@ | ||||
|     <uses-permission android:name="android.permission.USE_CREDENTIALS" /> | ||||
|     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/> | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
|  | ||||
|  | ||||
|  | ||||
| </manifest> | ||||
| @@ -89,6 +89,8 @@ extends Activity implements JavaFileStorage.FileStorageSetupActivity { | ||||
| 	@Override | ||||
| 	public String getPath() { | ||||
| 		// TODO Auto-generated method stub | ||||
| 		if (getState().containsKey(JavaFileStorage.EXTRA_PATH)) | ||||
| 			return getState().getString(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.providers.BaseFileProviderUtils; | ||||
|  | ||||
| import java.io.File; | ||||
| import java.io.FileInputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| @@ -146,11 +145,9 @@ import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| //import keepass2android.javafilestorage.DropboxCloudRailStorage; | ||||
| import keepass2android.javafilestorage.DropboxV2Storage; | ||||
| import keepass2android.javafilestorage.ICertificateErrorHandler; | ||||
| import keepass2android.javafilestorage.JavaFileStorage; | ||||
| import keepass2android.javafilestorage.JavaFileStorage.FileEntry; | ||||
| import keepass2android.javafilestorage.OneDriveStorage; | ||||
| import keepass2android.javafilestorage.OneDriveStorage2; | ||||
| import keepass2android.javafilestorage.SftpStorage; | ||||
| import keepass2android.javafilestorage.UserInteractionRequiredException; | ||||
| import keepass2android.javafilestorage.WebDavStorage; | ||||
| @@ -206,10 +203,17 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag | ||||
| 				} | ||||
| 				catch (Exception e) | ||||
| 				{ | ||||
| 					Log.d("KP2AJ",e.toString()); | ||||
| 					//if exception because folder exists | ||||
| 					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 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) { | ||||
| 		storageToTest = new SftpStorage(ctx.getApplicationContext()); | ||||
| 		//storageToTest = new SftpStorage(ctx.getApplicationContext()); | ||||
| 		//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 WebDavStorage(new ICertificateErrorHandler() { | ||||
| 			@Override | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|  | ||||
|     <string name="msalPrefix">msal8374f801-0f55-407d-80cc-9a04fe86d9b2</string> | ||||
|     <string name="action_settings">Settings</string> | ||||
|     <string name="hello_world">Hello world!</string> | ||||
|     <string name="title_activity_file_storage_setup">FileStorageSetupActivity</string> | ||||
|   | ||||
| @@ -2,10 +2,14 @@ package keepass2android.softkeyboard; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.nio.ByteBuffer; | ||||
| import java.nio.ByteOrder; | ||||
| import java.nio.channels.Channels; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import org.xmlpull.v1.XmlPullParserException; | ||||
|  | ||||
| @@ -17,9 +21,11 @@ import android.content.pm.PackageManager; | ||||
| import android.content.pm.ResolveInfo; | ||||
| import android.content.pm.PackageManager.NameNotFoundException; | ||||
| import android.content.res.Resources; | ||||
| import android.content.res.TypedArray; | ||||
| import android.content.res.XmlResourceParser; | ||||
| 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 { | ||||
|     private static String TAG = "PCKeyboard"; | ||||
|     private static String HK_INTENT_DICT = "org.pocketworkstation.DICT"; | ||||
| @@ -121,6 +127,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 | ||||
|     public void onReceive(Context context, Intent intent) { | ||||
|         Log.i(TAG, "Package information changed, updating dictionaries."); | ||||
| @@ -129,21 +175,59 @@ public class PluginManager extends BroadcastReceiver { | ||||
|         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) { | ||||
|         Intent dictIntent = new Intent(SOFTKEYBOARD_INTENT_DICT); | ||||
|         List<ResolveInfo> dictPacks = packageManager.queryBroadcastReceivers( | ||||
|         		dictIntent, PackageManager.GET_RECEIVERS); | ||||
|         for (ResolveInfo ri : dictPacks) { | ||||
|             ApplicationInfo appInfo = ri.activityInfo.applicationInfo; | ||||
|             String pkgName = appInfo.packageName; | ||||
|             boolean success = false; | ||||
|             final String pkgName = appInfo.packageName; | ||||
|             final boolean[] success = {false}; | ||||
|             try { | ||||
|                 Resources res = packageManager.getResourcesForApplication(appInfo); | ||||
|                 final Resources res = packageManager.getResourcesForApplication(appInfo); | ||||
|                 Log.i("KP2AK", "Found dictionary plugin package: " + 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); | ||||
|  | ||||
|                 int dictResourceId = 0; | ||||
|                 String assetName = null; | ||||
|                 String lang = null; | ||||
|                 try { | ||||
| @@ -157,12 +241,21 @@ public class PluginManager extends BroadcastReceiver { | ||||
|                                     String convLang = SOFTKEYBOARD_LANG_MAP.get(lang); | ||||
|                                     if (convLang != null) lang = convLang; | ||||
|                                     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 | ||||
|                                     } 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.i(TAG, "asset=" + assetName + " lang=" + lang); | ||||
|  | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
| @@ -175,15 +268,55 @@ public class PluginManager extends BroadcastReceiver { | ||||
|                     Log.e(TAG, "Dictionary XML IOException"); | ||||
|                 } | ||||
|  | ||||
|                 if (assetName == null || lang == null) continue; | ||||
|                 if (lang == null) | ||||
|                     continue; | ||||
|                 if (assetName != null) { | ||||
|                     DictPluginSpec spec = new DictPluginSpecSoftKeyboard(pkgName, assetName); | ||||
|                     mPluginDicts.put(lang, spec); | ||||
|                     Log.i("KP2AK", "Found plugin dictionary: lang=" + lang + ", pkg=" + pkgName); | ||||
|                 success = true; | ||||
|                     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) { | ||||
|                 Log.i("KP2AK", "bad"); | ||||
|             } finally { | ||||
|                 if (!success) { | ||||
|                 if (!success[0]) { | ||||
|                     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) { | ||||
|         Intent dictIntent = new Intent(HK_INTENT_DICT); | ||||
|         List<ResolveInfo> dictPacks = packageManager.queryIntentActivities(dictIntent, 0); | ||||
|         Log.i("KP2AK", "Searching for HK dictionaries. Found " + dictPacks.size() + " packages"); | ||||
|         for (ResolveInfo ri : dictPacks) { | ||||
|             ApplicationInfo appInfo = ri.activityInfo.applicationInfo; | ||||
|             String pkgName = appInfo.packageName; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import java.util.Iterator; | ||||
|  | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| import org.json.JSONArray; | ||||
|  | ||||
| import android.content.BroadcastReceiver; | ||||
| import android.content.Context; | ||||
| @@ -65,7 +66,16 @@ public abstract class PluginActionBroadcastReceiver extends BroadcastReceiver { | ||||
| 		 | ||||
| 		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"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="keepass2android.plugin.qr" | ||||
|     android:versionCode="2" | ||||
|     android:versionName="1.0.1" > | ||||
|     package="keepass2android.plugin.qr" > | ||||
|      | ||||
|     <uses-permission android:name="android.permission.CAMERA"/> | ||||
|      <uses-permission | ||||
| @@ -15,10 +13,6 @@ | ||||
| 	<uses-feature android:name="android.hardware.camera.flash" android:required="false"/> | ||||
| 	<uses-feature android:name="android.hardware.screen.portrait" android:required="false"/> | ||||
| 
 | ||||
|     <uses-sdk | ||||
|         android:minSdkVersion="14" | ||||
|         android:targetSdkVersion="19" /> | ||||
| 
 | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         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