Compare commits
899 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5a42854ef | |||
| 784c84a166 | |||
| f5d78a4d04 | |||
| b334e0ccb2 | |||
| 0a2e5badd0 | |||
| 90b0bf9d82 | |||
| 6bcd1e44f9 | |||
| 4c06ead056 | |||
| 031340a2ac | |||
| 501cef665f | |||
| 8b65968ac9 | |||
| 16e9bf265d | |||
| 9bdd080cfb | |||
| a3e7153bca | |||
| 408232c9ac | |||
| 56bc47302c | |||
| 417e004b23 | |||
| b08e38f9ed | |||
| c7b505fd90 | |||
| c1fcb37642 | |||
| 0bba68a7f5 | |||
| 47e4c12395 | |||
| 25822a7564 | |||
| 072f0d1520 | |||
| c5f7ac18fd | |||
| 10b8fd9fb9 | |||
| 24c053d671 | |||
| 44fc8be49e | |||
| 2a4e73c650 | |||
| f0a540eba2 | |||
| 12922f8413 | |||
| 85f054e3c1 | |||
| b8734e5eea | |||
| eefd13547c | |||
| 29d86b8675 | |||
| 470bf86ec0 | |||
| 9367f61952 | |||
| 3cabd8c318 | |||
| 4119380af5 | |||
| fc3b08e374 | |||
| a57abce96e | |||
| b260f6c6de | |||
| 736cf94155 | |||
| 86ffd66c8a | |||
| c96e017e65 | |||
| 4f596d20a9 | |||
| 343e462473 | |||
| db414e8045 | |||
| 1fd98dba30 | |||
| 40ea54007f | |||
| fc5bf81991 | |||
| 54e460cf5b | |||
| ba64768d8d | |||
| 6710ac8c85 | |||
| 1f98b76da0 | |||
| fd1da492cc | |||
| 519eb9d641 | |||
| 698f0e975a | |||
| c6ed4eaff6 | |||
| 2964f9a1c7 | |||
| 4612183b13 | |||
| 4fe12d8930 | |||
| a54f4aeffd | |||
| 931db6f0b1 | |||
| 01f50a243b | |||
| f24c56d1ba | |||
| 907afa11ad | |||
| 44608c3122 | |||
| 44732e5f52 | |||
| 90f2fc4cd9 | |||
| 24c7dee5d0 | |||
| 12417c0c48 | |||
| d4919ff8c4 | |||
| b2acb97639 | |||
| 28debbda1b | |||
| 256ebe7c14 | |||
| 038b155aea | |||
| deaa2d939a | |||
| bf4ad25cad | |||
| c58957dc7b | |||
| 7b616cf719 | |||
| cca0f4c293 | |||
| bbe68cc224 | |||
| c0a2a78f3d | |||
| 3bdc2e63ab | |||
| 3f7bfe0b3a | |||
| e0d281bb4b | |||
| b7d37af933 | |||
| d24541fce4 | |||
| 7a0ac625ff | |||
| 198edec4f4 | |||
| d2837b9649 | |||
| 3f9cfbb422 | |||
| 2f3d926c08 | |||
| 594c3b8a85 | |||
| 3ad729824e | |||
| 41244485b1 | |||
| ede342feba | |||
| a15bdba5d7 | |||
| 361f5093c7 | |||
| 99f66fe6a3 | |||
| b413a6eeae | |||
| 9b9db23461 | |||
| 4fa36cd424 | |||
| 7d4a9998ca | |||
| eba8181d0c | |||
| 21714f0eac | |||
| 8e79e15c71 | |||
| 0c709d144b | |||
| 4e7d2f668b | |||
| bde439d102 | |||
| ba501dbbb8 | |||
| 5041a459d4 | |||
| 04f79ffaf1 | |||
| 32abebd0a0 | |||
| 34b06dc350 | |||
| 16d8c55fa7 | |||
| 2085063231 | |||
| 73f77df5ad | |||
| 8a11b0f7a8 | |||
| 5a2836eb6d | |||
| cfd8d39878 | |||
| e8e0b10559 | |||
| 98279052e6 | |||
| 0b7f60c0f8 | |||
| d563eb07a2 | |||
| f036bc90c3 | |||
| 0a53f7636c | |||
| 6e6d552441 | |||
| 905eed518f | |||
| f75e0cf4ba | |||
| c46f38c73b | |||
| 9718324e6e | |||
| 4c461a733b | |||
| 82ab7c4a01 | |||
| 0dc3bff01a | |||
| 358dd7f32b | |||
| 77d47f0b28 | |||
| d66c57cfd0 | |||
| 6531d027fc | |||
| d11e787624 | |||
| 055ae0d126 | |||
| 2074affd5e | |||
| 17c9d19ebb | |||
| cfd8435d68 | |||
| 77969960d9 | |||
| 52b1a08912 | |||
| 844d6c9972 | |||
| 5e6f1a92ca | |||
| 1a9f336a59 | |||
| b2815d5e63 | |||
| 5b88c57d94 | |||
| 9ba08f275e | |||
| aadd0c9129 | |||
| b3b79a792f | |||
| 59cd4b5c5c | |||
| f5f9ff29ce | |||
| a10b59d03b | |||
| 7ebf51abb7 | |||
| e6b1ecb851 | |||
| 12e48d069b | |||
| 69d6609a3b | |||
| 2196ab3713 | |||
| 89be1cf5d2 | |||
| 2041e85b9e | |||
| 6c2f0e404f | |||
| 770f96b6d5 | |||
| d5e66252dc | |||
| dbdb1cbd06 | |||
| 8b479b57e5 | |||
| c2359a96f8 | |||
| 1df65bc3ce | |||
| 530a80d7ea | |||
| 8be0f68390 | |||
| 05cb30d79c | |||
| c472d4975e | |||
| 663b0f3931 | |||
| 0e31fcf3db | |||
| 1300764770 | |||
| 13ed3303f5 | |||
| 18d8223d7e | |||
| 1a215f3176 | |||
| f200ab423d | |||
| 42841824bf | |||
| ddad642a88 | |||
| 339957bd91 | |||
| d889d3e385 | |||
| 0bf27fa319 | |||
| 466a37aab8 | |||
| 4ab2130d7d | |||
| 256ceef875 | |||
| 2acde54880 | |||
| a92d255e4b | |||
| dc62272e6e | |||
| 60f6a60ca9 | |||
| eef8d21112 | |||
| 6a374cbfa6 | |||
| ccb8ac4cfc | |||
| 051801f337 | |||
| 5de21734c0 | |||
| 18deb8ad99 | |||
| d866f1d74a | |||
| 3426ae3722 | |||
| d04510f5f2 | |||
| 84c0682f16 | |||
| 53bbc5f922 | |||
| d8b70d79e8 | |||
| 56d2230251 | |||
| 62f21d3cee | |||
| b9c90af8be | |||
| 7a661c1b48 | |||
| c871a711dd | |||
| 46922dd259 | |||
| e2adb25da0 | |||
| d671caf3ab | |||
| 6b038186be | |||
| 95187becaf | |||
| d412d90b17 | |||
| 790e30defd | |||
| 2f74181262 | |||
| 45fd23ff02 | |||
| 4e2fb8e5dc | |||
| afbceca184 | |||
| 1b8c68af02 | |||
| 5146658e0f | |||
| dece680221 | |||
| 48ee9d6b0e | |||
| ee925692e5 | |||
| 0832220d61 | |||
| 98430fd573 | |||
| 3e82a48f4d | |||
| bd448ef05a | |||
| 82efa14b7a | |||
| 61f25edafc | |||
| 436875e8ec | |||
| ac087a87d3 | |||
| 903ce09426 | |||
| 02dc510078 | |||
| ffefe417db | |||
| 00adffc8b3 | |||
| 2ec3278637 | |||
| 1f52a0c186 | |||
| 40f7d820a6 | |||
| 4a547ba1c9 | |||
| 19f7e6bd88 | |||
| dea3553885 | |||
| 59f39fd535 | |||
| b4ca04596c | |||
| d83d23803a | |||
| fae5dd517e | |||
| 247c96ac6e | |||
| 360e06bbc5 | |||
| 2d94ac7a19 | |||
| ef48baf7ae | |||
| 8e06a88a2c | |||
| 6898d9b270 | |||
| 0aa8a5c243 | |||
| c5326fc5c9 | |||
| 4c97c689e9 | |||
| 9431e878f3 | |||
| 3ac71a37e8 | |||
| 0ac5e0daf4 | |||
| f3a7ada586 | |||
| d835008c67 | |||
| bd4f3ad0d9 | |||
| 8083edddb9 | |||
| 886942c4e8 | |||
| 7576c8138e | |||
| 296adf70b4 | |||
| b3a6c70ae7 | |||
| ef28173511 | |||
| b25cb9995f | |||
| 38e8e1cf9c | |||
| f25641b2a7 | |||
| 4101a1cb17 | |||
| 4911c2718c | |||
| aa5ad746eb | |||
| c88ee55e8d | |||
| f2f4c23c80 | |||
| 01340e0316 | |||
| 67ea15706e | |||
| 5cc75b8cc3 | |||
| 653ac66ff3 | |||
| 3f7e593bc2 | |||
| 39505cd868 | |||
| 0daaa3e785 | |||
| 4050a375bc | |||
| da27108d0d | |||
| a058f26462 | |||
| c8f0f05570 | |||
| 347ed2896a | |||
| 5c40a1f9e9 | |||
| afa39866ad | |||
| 219f855d86 | |||
| 46c4f9d3a1 | |||
| 17011a1e76 | |||
| 9ceb5305d5 | |||
| c8a92434af | |||
| 9aea19cee5 | |||
| 0c0c60b83f | |||
| 7254db40eb | |||
| 04481c33e7 | |||
| 4c107470c3 | |||
| df10358088 | |||
| 7cbfe01c0b | |||
| 4863052a08 | |||
| e97e8caab8 | |||
| cb09681cb0 | |||
| b9b7c9f965 | |||
| 4b39f490f3 | |||
| c607ceda48 | |||
| d4acd4629a | |||
| b413b7b5af | |||
| 5bc66c5b7f | |||
| 0415e910dc | |||
| 960040e51e | |||
| 5936f72263 | |||
| ba27c7e8e8 | |||
| 90136c524c | |||
| b4ef37a61a | |||
| 6c4185dc01 | |||
| f51a086137 | |||
| b8ff4a5906 | |||
| f48ba93a01 | |||
| 4823235bf5 | |||
| 90f30c3f3c | |||
| f79e63a13b | |||
| b0aae7248c | |||
| 467b12491c | |||
| 22397ded9b | |||
| de1da4b060 | |||
| b7240f2048 | |||
| 13f7f96101 | |||
| 006ee00527 | |||
| 3e8f85a4b4 | |||
| 286907b89e | |||
| 8d7f496f7c | |||
| f62a9ed199 | |||
| bd0c1cd932 | |||
| daaa2bf911 | |||
| bdb6b48e3f | |||
| 8305a1acd3 | |||
| 309081cccf | |||
| f97bc0fbb6 | |||
| f7aee1c12f | |||
| 1221c0e416 | |||
| c505cc689b | |||
| 48dbd5c570 | |||
| 51397d7c28 | |||
| 69a8cb71ea | |||
| 184a01b64d | |||
| 86cc3f69aa | |||
| 3a216c055e | |||
| ee3a4614c3 | |||
| bb9bc73daa | |||
| 5b5e1847eb | |||
| eeb94e1668 | |||
| 14bf4bbd32 | |||
| 0e61d18b9e | |||
| 531fa9f25a | |||
| a44a670f50 | |||
| 81750c41bd | |||
| 672568d1f5 | |||
| 0179540023 | |||
| ed37b84b3b | |||
| 0689bf4f31 | |||
| 47c8e6854e | |||
| 3300dbbe5e | |||
| 5784ea96e7 | |||
| a77763ee89 | |||
| 1def61432e | |||
| e9b2175506 | |||
| a2a4ad3947 | |||
| d95f818e43 | |||
| 64978dbe89 | |||
| bc8b8a69b8 | |||
| 77e9643a09 | |||
| ef409298e8 | |||
| 17316363f1 | |||
| 0e57d558f6 | |||
| f23247cd85 | |||
| 93ee850bcc | |||
| 8ba6cb3abf | |||
| bad0d15c1c | |||
| a266a22daf | |||
| c4d36f06f8 | |||
| 35aac2cdec | |||
| 6101b4edde | |||
| 13ed3757da | |||
| d086244820 | |||
| 78a2a4a55b | |||
| a70f37a53e | |||
| 9de3acfae2 | |||
| 597a10fc5d | |||
| 1a80e94bca | |||
| 0a883ae109 | |||
| 568a4987e3 | |||
| 02bfe3ad72 | |||
| 27eaa8d4af | |||
| 3eba4f462f | |||
| 55f3d72cc8 | |||
| e0eb8473ee | |||
| 6c5d2c8057 | |||
| c3fc21a2b3 | |||
| 2596043a94 | |||
| 2fa2cc1574 | |||
| 7ea156d422 | |||
| 95f5773046 | |||
| 28fe2f7d22 | |||
| 289d7a01d8 | |||
| 47dbb648fb | |||
| 0478f01113 | |||
| ddf29c5875 | |||
| ad82270dcc | |||
| 6c8a033a56 | |||
| e499f2d148 | |||
| 62325f0a27 | |||
| ff499bff04 | |||
| 26a04ca7e9 | |||
| 6f39439e3d | |||
| 34d4b284fe | |||
| 44ff0e16e6 | |||
| c00120bb9a | |||
| d800a4198d | |||
| 1f3d305a55 | |||
| addad5b7dc | |||
| 22204a62d4 | |||
| 9d2fb0dd03 | |||
| a5695b71eb | |||
| 2b9ffdb6b9 | |||
| 539e7c80bf | |||
| cfc1704c64 | |||
| bbe1cb1813 | |||
| feb120cbeb | |||
| 2bc07ad4cc | |||
| 1c036b1a11 | |||
| f5fe07ffb7 | |||
| 213f02cbc6 | |||
| 2de465e8f3 | |||
| 5b714572e3 | |||
| 8e95acb4c5 | |||
| 0439e5782e | |||
| 740abb1426 | |||
| df12f19c53 | |||
| 9c40218f48 | |||
| 714b7019c5 | |||
| 935d010dd2 | |||
| 199c502396 | |||
| 9470892118 | |||
| 91b925f95c | |||
| b3e9617aff | |||
| 19b24b5109 | |||
| 3aaa33ebe9 | |||
| 4ae7a32e29 | |||
| d7aeae3d26 | |||
| f6d8a980a6 | |||
| f2c406f168 | |||
| 2115367353 | |||
| 87814fa43c | |||
| 9e24b694b3 | |||
| 4f5bc9eb89 | |||
| f9d8754d88 | |||
| 07c2a14321 | |||
| e77bddd0f2 | |||
| 42522f5af3 | |||
| 5c3d605d34 | |||
| 3deda7efab | |||
| b441c73604 | |||
| df59596775 | |||
| cdb0c57416 | |||
| 0749c6865d | |||
| a80bb1e219 | |||
| ff2805c428 | |||
| 69c45a4b05 | |||
| 737244a65e | |||
| 7018969d14 | |||
| d1ba7f31f8 | |||
| cbfd04e950 | |||
| 981c0b83d3 | |||
| 16ae5cf634 | |||
| 4a42bfa9bb | |||
| 3e32652b24 | |||
| 957096a70a | |||
| 8bb069e996 | |||
| a0c672eb91 | |||
| eddd996ed6 | |||
| c0a2326a8d | |||
| e429f8611e | |||
| fcca95787b | |||
| 8944870d4f | |||
| b93c3d9caa | |||
| 4d2aba2577 | |||
| ce780ee4f4 | |||
| a6134bfacd | |||
| a96f48d0a8 | |||
| 8fa1499a2f | |||
| 74e0a4d999 | |||
| 7085f198a9 | |||
| 4f211aaf48 | |||
| 4d2bf19891 | |||
| dfbadb1550 | |||
| 0eeb481d85 | |||
| 66bce71a8b | |||
| b198ecbc02 | |||
| ecbe058d00 | |||
| 22f37d02dc | |||
| c8b23195f6 | |||
| 02840965d7 | |||
| 5b13ab255a | |||
| ae659e3c84 | |||
| c1ffefc6af | |||
| 1b2d75ee52 | |||
| d5b2ce9c09 | |||
| 9162575b35 | |||
| 0ff26aa442 | |||
| a06b920785 | |||
| 0441827578 | |||
| 85c2ec68ac | |||
| bf50c25854 | |||
| 24402388b4 | |||
| 7b345d6cb0 | |||
| 018f9f1776 | |||
| 71f3948de2 | |||
| ea67e21796 | |||
| a83739c269 | |||
| cc5f127dcf | |||
| e015e73abe | |||
| 0624d224a9 | |||
| 030708194a | |||
| a5b0fbb83d | |||
| 5a5be024e4 | |||
| b1b42b5262 | |||
| 07f4da9b00 | |||
| 59ace3d881 | |||
| 0426f1f17d | |||
| c2d3d057a5 | |||
| 5426c570fc | |||
| 3a6e2e6735 | |||
| ef38808ba5 | |||
| 7fe5f0c099 | |||
| 5f615d30b2 | |||
| 7ce2bbe9b1 | |||
| 4c408eb1b7 | |||
| 82a2f50d03 | |||
| e8237f6190 | |||
| 4d13622432 | |||
| c77174059d | |||
| 69b442a31a | |||
| e9724c8785 | |||
| 0a49d54a85 | |||
| 958392f09b | |||
| cb79bcd876 | |||
| c500f98ad8 | |||
| 41a8dd10de | |||
| 9283e57429 | |||
| 3bdb3f8c1e | |||
| 3aee8b069d | |||
| 3e79b65539 | |||
| e2d8a28087 | |||
| ba0ef3676d | |||
| 3f609fdf90 | |||
| 9717d03a75 | |||
| 21e2c35ab0 | |||
| 563f8fbdca | |||
| 221354a7d8 | |||
| 7495d89a79 | |||
| 15ad07f5db | |||
| c95c4c359a | |||
| 1a1dd99a89 | |||
| 71afe908b4 | |||
| e3c3acfca9 | |||
| 7e81768abc | |||
| 6d0bca471e | |||
| 8e4e7e5b19 | |||
| c667627d2f | |||
| 8b411c3d46 | |||
| db7c20d99a | |||
| f153332ad5 | |||
| 876d78b4ad | |||
| 68e871d175 | |||
| d7d4b26a66 | |||
| f1d5ccb1e3 | |||
| bf67cd5acd | |||
| e330d40005 | |||
| 1a6986cfcf | |||
| b617b9e391 | |||
| dadcbc8b31 | |||
| 07bb57fac8 | |||
| c58cffe4a2 | |||
| 95af144ae9 | |||
| 92f5ad220a | |||
| 1a8f53d1de | |||
| 4f77caee02 | |||
| d64757f3b0 | |||
| 74a5bf5503 | |||
| ee8efbb1ab | |||
| 7ab2703388 | |||
| f753dfa2a7 | |||
| 1aade7c27c | |||
| 8fa02913be | |||
| e8538cc035 | |||
| 65ef97d9ba | |||
| 21426610ad | |||
| 20fc78419e | |||
| 40b0a62cc3 | |||
| 3949b78c61 | |||
| 55179d7400 | |||
| 517147346e | |||
| e1e63d079f | |||
| 86744046e3 | |||
| a5f1c3e564 | |||
| 2b2734cd66 | |||
| f199b0a651 | |||
| c2d33f3301 | |||
| 8fc999f771 | |||
| be0fb51193 | |||
| 097f5355e2 | |||
| 843fad9963 | |||
| 7b5670f275 | |||
| f4410891e4 | |||
| 9da95ce28f | |||
| dda988f0ea | |||
| 33e05625ed | |||
| b8575d04ad | |||
| b64b67ce81 | |||
| 1b6a2c7d5c | |||
| a1a90d5bff | |||
| 08733d7351 | |||
| 5d0d65c624 | |||
| 6c7c333d8f | |||
| b039fbfb13 | |||
| 428bc7fbad | |||
| fe9686f215 | |||
| ac42c8ae26 | |||
| e4a472f902 | |||
| c5c974e996 | |||
| 900f8970f0 | |||
| dd6c24d3b8 | |||
| fb83b9dca3 | |||
| 8175e68ecd | |||
| ac0159f67e | |||
| f1c8fb7b17 | |||
| 3b43979a06 | |||
| c839285f90 | |||
| 21378a07c8 | |||
| 74e6633f2a | |||
| 8693b13e68 | |||
| a0a861370b | |||
| 7282626c0f | |||
| cf604255fc | |||
| b275850aca | |||
| 8aa36a82c4 | |||
| 06132fcdaf | |||
| f1e5b3fcfa | |||
| fba4b91bec | |||
| 0ef987207b | |||
| 2d7b5b959e | |||
| 2837861d9a | |||
| 212222c98e | |||
| 15237a6d91 | |||
| 95b2ef0f3c | |||
| c452d31f25 | |||
| 7361d1f16f | |||
| 7ffb22a40f | |||
| a6948d492a | |||
| f78e496e69 | |||
| b3e6f0fd22 | |||
| 48ed511ba5 | |||
| 3cff6fdd6c | |||
| 81da2e49f7 | |||
| 2cd6427958 | |||
| 061f1ae0b1 | |||
| ca60b1e24d | |||
| a279be1b47 | |||
| f503cd41de | |||
| 4428877158 | |||
| 876f61ac27 | |||
| 2cc541e084 | |||
| ddd3c78fa6 | |||
| 62dffedc02 | |||
| 13e5409ab2 | |||
| 198fff508d | |||
| 3fd918d381 | |||
| 35c7fb60fd | |||
| 927e9cf14a | |||
| f0db4824ec | |||
| 79623a8a67 | |||
| 7611829f2e | |||
| d34dcb0b27 | |||
| 37d8b71d5d | |||
| 81f44024f7 | |||
| e477c8b0b3 | |||
| 1ff5d171e3 | |||
| b8aaebf51a | |||
| 2c9bc5135c | |||
| 248177f518 | |||
| d34901fd13 | |||
| 94b12dfb02 | |||
| 0cc88a1706 | |||
| bd3ad153d2 | |||
| 78524398b7 | |||
| 76461e866b | |||
| 3f22e727f6 | |||
| c606314c0f | |||
| 37e12cd0be | |||
| b063928732 | |||
| 111a3ca6ac | |||
| f97123015a | |||
| b1e6559f4d | |||
| 2d0725fbad | |||
| 1a0ced4f15 | |||
| 7176c99264 | |||
| 30c32da5e6 | |||
| 9d9c36b253 | |||
| 6d9a747b34 | |||
| a28cd8a54f | |||
| 44ad842542 | |||
| 917e05a16c | |||
| 23608ca70c | |||
| 3ab01a2164 | |||
| d9e608329d | |||
| 367ad70be7 | |||
| e60d5c8f2c | |||
| 03f16dfb78 | |||
| c9106065fd | |||
| 2c9e96406b | |||
| 21c7d6a667 | |||
| e0f95f18d3 | |||
| b9ffbe9a57 | |||
| dc9b2b3ca8 | |||
| baafa604fe | |||
| e79bfebb76 | |||
| 131fdab675 | |||
| efc2a8e2ea | |||
| d725f1362f | |||
| e79427619b | |||
| 4887302b51 | |||
| aea735f20e | |||
| e868a57979 | |||
| 7a34b490c7 | |||
| 8091d9a098 | |||
| e2e6440109 | |||
| caffb36ec2 | |||
| 0d054de6ac | |||
| 5397f2c53a | |||
| 61b11025bd | |||
| ab3b04c165 | |||
| 8c8a0160a1 | |||
| cbd980357b | |||
| 5cd7cba4fb | |||
| 6b0377bb71 | |||
| 1589f9105c | |||
| 597b9bbfcb | |||
| 8b37f1dc6c | |||
| 6ec7028fe8 | |||
| 14c0055899 | |||
| d84cfe001f | |||
| 6cccf84043 | |||
| abfa20ec31 | |||
| e0981f1c44 | |||
| 2ca7304215 | |||
| 88f8052573 | |||
| b40a1fa613 | |||
| 6407c24977 | |||
| fb8d71fc63 | |||
| 8ee98ebdf9 | |||
| 3f65ec3318 | |||
| b67855b7a3 | |||
| 0299de4fdd | |||
| ba09e945f0 | |||
| d74a30e4e4 | |||
| 2eb050e663 | |||
| 7499c9a19c | |||
| e309afa1a0 | |||
| 5d71df36bd | |||
| 7be76f10d6 | |||
| 22a62421f0 | |||
| be3032192a | |||
| 37dc9989c4 | |||
| eb7a0cb85d | |||
| 5afeb315ba | |||
| c2eeff6e47 | |||
| 893c2a99be | |||
| 36188f00b0 | |||
| 9da18d0697 | |||
| 3bb991edd3 | |||
| dd0373b855 | |||
| 48786fcae1 | |||
| 28b4945d36 | |||
| 4a5314d8b9 | |||
| c67adcc43f | |||
| 0a43fea1c0 | |||
| a20b7f9303 | |||
| 3fc76f61d0 | |||
| 5b9b6be383 | |||
| fce0a4b0eb | |||
| 381de750e7 | |||
| 40f6de950c | |||
| 916c350352 | |||
| 4186791d9a | |||
| 02cef43815 | |||
| 8d291efedd | |||
| a5c8052439 | |||
| 704169abab | |||
| 8ee8b52832 | |||
| 81819aa3b7 | |||
| 90ffae5526 | |||
| a3c99531bf | |||
| 1b6bf2f331 | |||
| f5d18157bf | |||
| 7c7dd0736e | |||
| 0a5cd9f113 | |||
| 9b498b8471 | |||
| fee217e8d7 | |||
| c05a28a901 | |||
| fa45cd1a88 | |||
| 6cb5331918 | |||
| fca865a743 | |||
| 1f313b9487 | |||
| 0e7ac68c24 | |||
| 0d3a413708 | |||
| 85d243508c | |||
| 365da75d1b | |||
| a74feab9eb | |||
| ae591d4883 | |||
| 3489bf22bb | |||
| 9e82b8d693 | |||
| 71482f0fc9 | |||
| 8216fa1d88 | |||
| 4133574db0 | |||
| 41fafaf7ae | |||
| 0789295701 | |||
| bb81298e84 | |||
| bc9a6abccf | |||
| bd6e2477b5 | |||
| acae05d287 | |||
| 9f5a9f179f | |||
| c81488c3fe | |||
| 2688e281a7 | |||
| b853b8e89f | |||
| 0558fbd7ff | |||
| 8471339578 | |||
| 3d95c3a744 | |||
| 523a093776 | |||
| 49d5e9a29e | |||
| 221438c5fc | |||
| a29de2f910 | |||
| 8de155c1a4 | |||
| 846da6763a | |||
| 40c9357adb | |||
| fba91b203c | |||
| 93891a6a71 | |||
| 5091f60bb2 | |||
| 1bbcf47d07 | |||
| 7ef98207cb | |||
| 646b3c6efb | |||
| aef659cc92 | |||
| a0660c6696 | |||
| eb0166ae7a | |||
| 44cde95b47 | |||
| 746072a65d | |||
| f17aadccda | |||
| b2ea92940e | |||
| e18a668e13 | |||
| db2dafd40c | |||
| a3614fee0d | |||
| 8a66943f4a | |||
| 2368ea7cf8 | |||
| ebd2bf149c | |||
| 7dfc288f93 | |||
| 01990394dd | |||
| bec9923c6a | |||
| 52f29a5fa1 | |||
| 179f36e2a2 | |||
| 378e8f5cd9 | |||
| 523283acd0 | |||
| 4e9578bed4 | |||
| 3e46105ba8 | |||
| d8eaab2aa4 | |||
| ce897509ba | |||
| 330cd7a409 | |||
| 56492cd1b2 | |||
| 9427979b1c | |||
| 9d47529d20 | |||
| eec651c9d3 | |||
| 3921f88e30 | |||
| 14adc2a3b3 | |||
| 7117414fb1 | |||
| d37cbe6309 | |||
| ebae2ceb73 | |||
| f4c2437e53 | |||
| a05c71ec85 | |||
| 02fd789f20 | |||
| e68dda81d2 | |||
| d3faf1d10a | |||
| 3e8ba73af2 | |||
| ee08552ad6 | |||
| 5772bcc7f2 | |||
| b6c62af015 |
@@ -141,3 +141,5 @@ build/docs.zip
|
||||
build/ui-docs.zip
|
||||
build/csharp-docs.zip
|
||||
build/msbuild.log
|
||||
.vs/
|
||||
src/packages/
|
||||
@@ -1,42 +1,52 @@
|
||||
Umbraco CMS
|
||||
===========
|
||||
Umbraco is a free open source Content Management System built on the ASP.NET platform.
|
||||
The friendliest, most flexible and fastest growing ASP.NET CMS used by more than 390,000 websites worldwide: [https://umbraco.com](https://umbraco.com)
|
||||
|
||||
[](https://vimeo.com/172382998/)
|
||||
|
||||
## Umbraco CMS ##
|
||||
Umbraco is a free open source Content Management System built on the ASP.NET platform. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
|
||||
|
||||
|
||||
## Building Umbraco from source ##
|
||||
|
||||
The easiest way to get started is to run `build/build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `grunt vs` in `src\Umbraco.Web.UI.Client`.
|
||||
|
||||
If you're interested in making changes to Belle without running Visual Studio make sure to read the [Belle ReadMe file](src/Umbraco.Web.UI.Client/README.md).
|
||||
|
||||
Note that you can always [download a nightly build](http://nightly.umbraco.org/?container=umbraco-750) so you don't have to build the code yourself.
|
||||
|
||||
## Watch a introduction video ##
|
||||
## Watch an introduction video ##
|
||||
|
||||
[](https://umbraco.tv/videos/umbraco-v7/content-editor/basics/introduction/cms-explanation/)
|
||||
|
||||
## Umbraco - the simple, flexible and friendly ASP.NET CMS ##
|
||||
## Umbraco - The Friendly CMS ##
|
||||
|
||||
**More than 350,000 sites trust Umbraco**
|
||||
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box.
|
||||
|
||||
For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developer's dream and your users will love it too.
|
||||
Umbraco is not only loved by developers, but is a content editors dream. Enjoy intuitive editing tools, media management, responsive views and approval workflows to send your content live.
|
||||
|
||||
Used by more than 350,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales.
|
||||
Used by more than 350,000 active websites including Carlsberg, Segway, Amazon and Heinz and **The Official ASP.NET and IIS.NET website from Microsoft** ([https://asp.net](https://asp.net) / [https://iis.net](https://iis.net)), you can be sure that the technology is proven, stable and scales. Backed by the team at Umbraco HQ, and supported by a dedicated community of over 200,000 craftspeople globally, you can trust that Umbraco is a safe choice and is here to stay.
|
||||
|
||||
To view more examples, please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies)
|
||||
To view more examples, please visit [https://umbraco.com/why-umbraco/#caseStudies](https://umbraco.com/why-umbraco/#caseStudies)
|
||||
|
||||
## Why Open Source? ##
|
||||
As an Open Source platform, Umbraco is more than just a CMS. We are transparent with our roadmap for future versions, our incremental sprint planning notes are publicly accessible and community contributions and packages are available for all to use.
|
||||
|
||||
## Downloading ##
|
||||
|
||||
The downloadable Umbraco releases live at [http://our.umbraco.org/download](http://our.umbraco.org/download).
|
||||
The downloadable Umbraco releases live at [https://our.umbraco.org/download](https://our.umbraco.org/download).
|
||||
|
||||
## Forums ##
|
||||
|
||||
We have a forum running on [http://our.umbraco.org](http://our.umbraco.org). The discussions group on [Google Groups](https://groups.google.com/forum/#!forum/umbraco-dev) is for discussions on developing the core, and not on Umbraco-implementations or extensions in general. For those topics, please use [http://our.umbraco.org](http://our.umbraco.org).
|
||||
Peer-to-peer support is available 24/7 at the community forum on [https://our.umbraco.org](https://our.umbraco.org).
|
||||
|
||||
## Contribute to Umbraco ##
|
||||
|
||||
If you want to contribute back to Umbraco you should check out our [guide to contributing](http://our.umbraco.org/contribute).
|
||||
Umbraco is contribution focused and community driven. If you want to contribute back to Umbraco please check out our [guide to contributing](https://our.umbraco.org/contribute).
|
||||
|
||||
## Found a bug? ##
|
||||
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](http://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](https://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
|
||||
|
||||
To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
|
||||
+15
-14
@@ -2,21 +2,23 @@ version: '{build}'
|
||||
shallow_clone: true
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd build
|
||||
SET SLN=%CD%
|
||||
|
||||
SET SRC=%SLN%\src
|
||||
|
||||
SET PACKAGES=%SRC%\packages
|
||||
|
||||
CD build
|
||||
|
||||
SET "release="
|
||||
|
||||
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i"
|
||||
|
||||
SET nuGetFolder=C:\Users\appveyor\.nuget\packages
|
||||
|
||||
..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
|
||||
|
||||
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
|
||||
|
||||
IF EXIST ..\src\umbraco.businesslogic\packages.config ..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
|
||||
|
||||
..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
|
||||
ECHO "Restoring NuGet into %PACKAGES%"
|
||||
|
||||
%SRC%\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
|
||||
|
||||
%SRC%\.nuget\NuGet.exe restore %SRC%\umbraco.sln -Verbosity Quiet -NonInteractive -PackagesDirectory %PACKAGES%
|
||||
|
||||
ECHO Building Release %release% build%APPVEYOR_BUILD_NUMBER%
|
||||
|
||||
@@ -24,13 +26,12 @@ build_script:
|
||||
|
||||
SET MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe"
|
||||
|
||||
XCOPY "..\src\Umbraco.Tests\unit-test-log4net.CI.config" "..\src\Umbraco.Tests\unit-test-log4net.config" /Y
|
||||
XCOPY "%SRC%\Umbraco.Tests\unit-test-log4net.CI.config" "%SRC%\Umbraco.Tests\unit-test-log4net.config" /Y
|
||||
|
||||
%MSBUILD% "..\src\Umbraco.Tests\Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly
|
||||
%MSBUILD% "%SLN%/src/Umbraco.Tests/Umbraco.Tests.csproj" /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /p:NugetPackagesDirectory=%PACKAGES%
|
||||
|
||||
build.bat nopause %release% build%APPVEYOR_BUILD_NUMBER%
|
||||
build.bat -integration -release:%release% -comment:build%APPVEYOR_BUILD_NUMBER% -nugetfolder:%PACKAGES%
|
||||
|
||||
ECHO %PATH%
|
||||
test:
|
||||
assemblies: src\Umbraco.Tests\bin\Debug\Umbraco.Tests.dll
|
||||
artifacts:
|
||||
|
||||
+109
-30
@@ -1,31 +1,95 @@
|
||||
@ECHO OFF
|
||||
|
||||
:: UMBRACO BUILD FILE
|
||||
|
||||
|
||||
:: ensure we have UmbracoVersion.txt
|
||||
IF NOT EXIST UmbracoVersion.txt (
|
||||
ECHO UmbracoVersion.txt missing!
|
||||
GOTO :showerror
|
||||
ECHO UmbracoVersion.txt is missing!
|
||||
GOTO error
|
||||
)
|
||||
|
||||
REM Get the version and comment from UmbracoVersion.txt lines 2 and 3
|
||||
SET "release="
|
||||
SET "comment="
|
||||
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED release SET "release=%%i"
|
||||
FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED comment SET "comment=%%i"
|
||||
SET RELEASE=
|
||||
SET COMMENT=
|
||||
FOR /F "skip=1 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED RELEASE SET RELEASE=%%i
|
||||
FOR /F "skip=2 delims=" %%i IN (UmbracoVersion.txt) DO IF NOT DEFINED COMMENT SET COMMENT=%%i
|
||||
|
||||
REM If there's arguments on the command line overrule UmbracoVersion.txt and use that as the version
|
||||
IF [%2] NEQ [] (SET release=%2)
|
||||
IF [%3] NEQ [] (SET comment=%3) ELSE (IF [%2] NEQ [] (SET "comment="))
|
||||
REM process args
|
||||
|
||||
REM Get the "is continuous integration" from the parameters
|
||||
SET "isci=0"
|
||||
IF [%1] NEQ [] (SET isci=1)
|
||||
SET INTEGRATION=0
|
||||
SET nuGetFolder=%CD%\..\src\packages
|
||||
SET SKIPNUGET=0
|
||||
|
||||
SET version=%release%
|
||||
IF [%comment%] EQU [] (SET version=%release%) ELSE (SET version=%release%-%comment%)
|
||||
:processArgs
|
||||
|
||||
ECHO.
|
||||
ECHO Building Umbraco %version%
|
||||
ECHO.
|
||||
:: grab the first parameter as a whole eg "/action:start"
|
||||
:: end if no more parameter
|
||||
SET SWITCHPARSE=%1
|
||||
IF [%SWITCHPARSE%] == [] goto endProcessArgs
|
||||
|
||||
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release%
|
||||
:: get switch and value
|
||||
SET SWITCH=
|
||||
SET VALUE=
|
||||
FOR /F "tokens=1,* delims=: " %%a IN ("%SWITCHPARSE%") DO SET SWITCH=%%a& SET VALUE=%%b
|
||||
|
||||
:: route arg
|
||||
IF '%SWITCH%'=='/release' GOTO argRelease
|
||||
IF '%SWITCH%'=='-release' GOTO argRelease
|
||||
IF '%SWITCH%'=='/comment' GOTO argComment
|
||||
IF '%SWITCH%'=='-comment' GOTO argComment
|
||||
IF '%SWITCH%'=='/integration' GOTO argIntegration
|
||||
IF '%SWITCH%'=='-integration' GOTO argIntegration
|
||||
IF '%SWITCH%'=='/nugetfolder' GOTO argNugetFolder
|
||||
IF '%SWITCH%'=='-nugetfolder' GOTO argNugetFolder
|
||||
IF '%SWITCH%'=='/skipnuget' GOTO argSkipNuget
|
||||
IF '%SWITCH%'=='-skipnuget' GOTO argSkipNuget
|
||||
ECHO "Invalid switch %SWITCH%"
|
||||
GOTO error
|
||||
|
||||
:: handle each arg
|
||||
|
||||
:argRelease
|
||||
set RELEASE=%VALUE%
|
||||
SHIFT
|
||||
goto processArgs
|
||||
|
||||
:argComment
|
||||
SET COMMENT=%VALUE%
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:argIntegration
|
||||
SET INTEGRATION=1
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:argNugetFolder
|
||||
SET nuGetFolder=%VALUE%
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:argSkipNuget
|
||||
SET SKIPNUGET=1
|
||||
SHIFT
|
||||
GOTO processArgs
|
||||
|
||||
:endProcessArgs
|
||||
|
||||
REM run
|
||||
|
||||
SET VERSION=%RELEASE%
|
||||
IF [%COMMENT%] EQU [] (SET VERSION=%RELEASE%) ELSE (SET VERSION=%RELEASE%-%COMMENT%)
|
||||
|
||||
ECHO ################################################################
|
||||
ECHO Building Umbraco %VERSION%
|
||||
ECHO ################################################################
|
||||
|
||||
SET MSBUILDPATH=C:\Program Files (x86)\MSBuild\14.0\Bin
|
||||
SET MSBUILD="%MSBUILDPATH%\MsBuild.exe"
|
||||
SET PATH="%MSBUILDPATH%";%PATH%
|
||||
|
||||
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %RELEASE%
|
||||
|
||||
ECHO.
|
||||
ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle
|
||||
@@ -35,10 +99,10 @@ RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S
|
||||
ECHO.
|
||||
ECHO Removing existing built files to make sure everything is clean as a whistle
|
||||
RMDIR /Q /S _BuildOutput
|
||||
DEL /F /Q UmbracoCms.*.zip
|
||||
DEL /F /Q UmbracoExamine.*.zip
|
||||
DEL /F /Q UmbracoCms.*.nupkg
|
||||
DEL /F /Q webpihash.txt
|
||||
DEL /F /Q UmbracoCms.*.zip 2>NUL
|
||||
DEL /F /Q UmbracoExamine.*.zip 2>NUL
|
||||
DEL /F /Q UmbracoCms.*.nupkg 2>NUL
|
||||
DEL /F /Q webpihash.txt 2>NUL
|
||||
|
||||
ECHO.
|
||||
ECHO Making sure Git is in the path so that the build can succeed
|
||||
@@ -47,11 +111,21 @@ CALL InstallGit.cmd
|
||||
REM Adding the default Git path so that if it's installed it can actually be found
|
||||
REM This is necessary because SETLOCAL is on in InstallGit.cmd so that one might find Git,
|
||||
REM but the path setting is lost due to SETLOCAL
|
||||
path=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH%
|
||||
SET PATH="C:\Program Files (x86)\Git\cmd";"C:\Program Files\Git\cmd";%PATH%
|
||||
|
||||
ECHO.
|
||||
ECHO Making sure we have a web.config
|
||||
IF NOT EXIST %CD%\..\src\Umbraco.Web.UI\web.config COPY %CD%\..\src\Umbraco.Web.UI\web.Template.config %CD%\..\src\Umbraco.Web.UI\web.config
|
||||
IF NOT EXIST "%CD%\..\src\Umbraco.Web.UI\web.config" COPY "%CD%\..\src\Umbraco.Web.UI\web.Template.config" "%CD%\..\src\Umbraco.Web.UI\web.config"
|
||||
|
||||
ECHO.
|
||||
ECHO Reporting NuGet version
|
||||
"%CD%\..\src\.nuget\NuGet.exe" help | findstr "^NuGet Version:"
|
||||
|
||||
ECHO.
|
||||
ECHO Restoring NuGet packages
|
||||
ECHO Into %nuGetFolder%
|
||||
"%CD%\..\src\.nuget\NuGet.exe" restore "%CD%\..\src\umbraco.sln" -Verbosity Quiet -NonInteractive -PackagesDirectory "%nuGetFolder%"
|
||||
IF ERRORLEVEL 1 GOTO :error
|
||||
|
||||
ECHO.
|
||||
ECHO.
|
||||
@@ -60,13 +134,15 @@ ECHO This takes a few minutes and logging is set to report warnings
|
||||
ECHO and errors only so it might seems like nothing is happening for a while.
|
||||
ECHO You can check the msbuild.log file for progress.
|
||||
ECHO.
|
||||
%windir%\Microsoft.NET\Framework\v4.0.30319\msbuild.exe "Build.proj" /p:BUILD_RELEASE=%release% /p:BUILD_COMMENT=%comment% /p:NugetPackagesDirectory=%nuGetFolder% /consoleloggerparameters:Summary;ErrorsOnly;WarningsOnly /fileLogger
|
||||
IF ERRORLEVEL 1 GOTO :error
|
||||
%MSBUILD% "Build.proj" /p:BUILD_RELEASE=%RELEASE% /p:BUILD_COMMENT=%COMMENT% /p:NugetPackagesDirectory="%nuGetFolder%" /consoleloggerparameters:Summary;ErrorsOnly /fileLogger
|
||||
IF ERRORLEVEL 1 GOTO error
|
||||
|
||||
ECHO.
|
||||
ECHO Setting node_modules folder to hidden to prevent VS13 from crashing on it while loading the websites project
|
||||
attrib +h ..\src\Umbraco.Web.UI.Client\node_modules
|
||||
|
||||
IF %SKIPNUGET% EQU 1 GOTO success
|
||||
|
||||
ECHO.
|
||||
ECHO Adding Web.config transform files to the NuGet package
|
||||
REN .\_BuildOutput\WebApp\Views\Web.config Web.config.transform
|
||||
@@ -74,22 +150,25 @@ REN .\_BuildOutput\WebApp\Xslt\Web.config Web.config.transform
|
||||
|
||||
ECHO.
|
||||
ECHO Packing the NuGet release files
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %version% -Symbols -Verbosity quiet
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %version% -Verbosity quiet
|
||||
IF ERRORLEVEL 1 GOTO :error
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.Core.nuspec -Version %VERSION% -Symbols -Verbosity quiet
|
||||
..\src\.nuget\NuGet.exe Pack NuSpecs\UmbracoCms.nuspec -Version %VERSION% -Verbosity quiet
|
||||
IF ERRORLEVEL 1 GOTO error
|
||||
|
||||
:success
|
||||
ECHO.
|
||||
ECHO No errors were detected!
|
||||
ECHO There may still be some in the output, which you would need to investigate.
|
||||
ECHO Warnings are usually normal.
|
||||
ECHO.
|
||||
ECHO.
|
||||
GOTO :EOF
|
||||
|
||||
:error
|
||||
|
||||
ECHO.
|
||||
ECHO Errors were detected!
|
||||
ECHO.
|
||||
|
||||
REM don't pause if continuous integration else the build server waits forever
|
||||
REM before cancelling the build (and, there is noone to read the output anyways)
|
||||
IF isci NEQ 1 PAUSE
|
||||
IF %INTEGRATION% NEQ 1 PAUSE
|
||||
|
||||
+18
-20
@@ -1,8 +1,8 @@
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!--
|
||||
<!--
|
||||
****************************************************
|
||||
INCLUDES
|
||||
INCLUDES
|
||||
*****************************************************
|
||||
-->
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
{
|
||||
using (var cryptoProvider = new SHA1CryptoServiceProvider())
|
||||
{
|
||||
|
||||
|
||||
var fileHash = cryptoProvider.ComputeHash(stream);
|
||||
|
||||
|
||||
using (TextWriter w = new StreamWriter(OutputFile, false))
|
||||
{
|
||||
w.WriteLine(string.Join("", fileHash.Select(b => b.ToString("x2"))));
|
||||
@@ -52,9 +52,9 @@
|
||||
</UsingTask>
|
||||
|
||||
|
||||
<!--
|
||||
<!--
|
||||
****************************************************
|
||||
VARIABLES
|
||||
VARIABLES
|
||||
*****************************************************
|
||||
-->
|
||||
|
||||
@@ -102,9 +102,9 @@
|
||||
<SystemFolders Include="$(WebAppFolder)Views" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
****************************************************
|
||||
TARGETS
|
||||
TARGETS
|
||||
*****************************************************
|
||||
-->
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
<Message Text="Finished zipping to build\$(BuildFolder)\$(buildDate)-$(BuildZipFileName)" Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="CreateSystemFolders" DependsOnTargets="CopyBelleBuild" Inputs="@(SystemFolders)" Outputs="%(Identity).Dummy">
|
||||
<MakeDir Directories="@(SystemFolders)" />
|
||||
</Target>
|
||||
@@ -231,7 +231,7 @@
|
||||
DestinationFiles="@(CustomLanguageFiles->'$(ConfigsFolder)Lang\%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
OverwriteReadOnlyFiles="true"
|
||||
SkipUnchangedFiles="false" />
|
||||
|
||||
|
||||
<Copy SourceFiles="@(WebConfigTransformFile)"
|
||||
DestinationFiles="$(ConfigsFolder)Web.config.transform"
|
||||
OverwriteReadOnlyFiles="true"
|
||||
@@ -264,14 +264,12 @@
|
||||
<Message Text="Compiling web project to build\$(BuildFolder)" Importance="high" />
|
||||
<!-- For UseWPP_CopyWebApplication=True see http://stackoverflow.com/questions/1983575/copywebapplication-with-web-config-transformations -->
|
||||
<!-- Build the Umbraco.Web.UI project -->
|
||||
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath);Verbosity=minimal" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False">
|
||||
</MSBuild>
|
||||
<MSBuild Projects="..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj" Properties="WarningLevel=0;Configuration=$(BuildConfiguration);UseWPP_CopyWebApplication=True;PipelineDependsOnBuild=False;OutDir=$(SolutionBinFolderAbsolutePath);WebProjectOutputDir=$(WebAppFolderAbsolutePath);Verbosity=minimal" Targets="Clean;Rebuild;" BuildInParallel="False" ToolsVersion="4.0" UnloadProjectsOnCompletion="False" />
|
||||
|
||||
<!-- DONE -->
|
||||
<Message Text="Finished compiling projects" Importance="high" />
|
||||
</Target>
|
||||
|
||||
|
||||
<Target Name="SetVersionNumber" Condition="'$(BUILD_RELEASE)'!=''">
|
||||
<PropertyGroup>
|
||||
<NewVersion>$(BUILD_RELEASE)</NewVersion>
|
||||
@@ -307,34 +305,34 @@
|
||||
ReplacementText="AssemblyFileVersion("$(BUILD_RELEASE)")"/>
|
||||
|
||||
<!--This updates the AssemblyInformationalVersion for the solution to the umbraco version and comment-->
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_COMMENT)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'==''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_NIGHTLY)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_NIGHTLY)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'!='' AND '$(BUILD_NIGHTLY)'!=''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)-$(BUILD_COMMENT)-$(BUILD_NIGHTLY)")"/>
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Condition="'$(BUILD_COMMENT)'=='' AND '$(BUILD_NIGHTLY)'==''"
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyInformationalVersion\("(.+)?"\)"
|
||||
ReplacementText="AssemblyInformationalVersion("$(BUILD_RELEASE)")"/>
|
||||
|
||||
|
||||
<!--This updates the copyright year-->
|
||||
<FileUpdate
|
||||
<FileUpdate
|
||||
Files="..\src\SolutionInfo.cs"
|
||||
Regex="AssemblyCopyright\("Copyright © Umbraco (\d{4})"\)"
|
||||
ReplacementText="AssemblyCopyright("Copyright © Umbraco $([System.DateTime]::Now.ToString(`yyyy`))")"/>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
@ECHO OFF
|
||||
SETLOCAL
|
||||
:: SETLOCAL is on, so changes to the path not persist to the actual user's path
|
||||
REM SETLOCAL is on, so changes to the path not persist to the actual user's path
|
||||
|
||||
git.exe --version
|
||||
IF %ERRORLEVEL%==9009 GOTO :trydefaultpath
|
||||
REM OK, DONE
|
||||
GOTO :EOF
|
||||
:: Git is installed, no need to to anything else
|
||||
|
||||
:trydefaultpath
|
||||
PATH=C:\Program Files (x86)\Git\cmd;C:\Program Files\Git\cmd;%PATH%
|
||||
git.exe --version
|
||||
IF %ERRORLEVEL%==9009 GOTO :showerror
|
||||
REM OK, DONE
|
||||
GOTO :EOF
|
||||
:: Git is installed, no need to to anything else
|
||||
|
||||
:showerror
|
||||
ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd
|
||||
|
||||
@@ -27,18 +27,19 @@
|
||||
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
|
||||
<dependency id="MySql.Data" version="[6.9.8, 7.0.0)" />
|
||||
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.1, 2.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 2.0.0)" />
|
||||
<!-- AutoMapper can not be updated due to: https://github.com/AutoMapper/AutoMapper/issues/373#issuecomment-127644405 -->
|
||||
<dependency id="AutoMapper" version="[3.0.0, 3.1.0)" />
|
||||
<dependency id="AutoMapper" version="[3.3.1, 4.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.69, 1.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.4.4, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.6.4, 5.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.80, 1.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.1, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.0, 5.0.0)" />
|
||||
<dependency id="semver" version="[1.1.2, 2.0.0)" />
|
||||
<dependency id="UrlRewritingNet" version="[2.0.7, 3.0.0)" />
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.4, 2.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.6.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
@@ -103,4 +104,4 @@
|
||||
<file src="..\_BuildOutput\bin\UmbracoExamine.pdb" target="lib" />
|
||||
<file src="..\..\src\UmbracoExamine\**\*.cs" exclude="..\..\src\**\TemporaryGeneratedFile*.cs" target="src\UmbracoExamine" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
<dependencies>
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.4, 4.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
@@ -43,6 +44,7 @@
|
||||
<file src="tools\trees.config.install.xdt" target="Content\config\trees.config.install.xdt" />
|
||||
<file src="tools\umbracoSettings.config.install.xdt" target="Content\config\umbracoSettings.config.install.xdt" />
|
||||
<file src="tools\Views.Web.config.install.xdt" target="Views\Web.config.install.xdt" />
|
||||
<file src="tools\processing.config.install.xdt" target="Content\Config\imageprocessor\processing.config.install.xdt" />
|
||||
<file src="build\**" target="build" />
|
||||
</files>
|
||||
</package>
|
||||
|
||||
@@ -319,6 +319,7 @@
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='HtmlAgilityPack')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='AutoMapper')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Net.Http')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Newtonsoft.Json')" xdt:Transform="Remove" />
|
||||
<dependentAssembly xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='System.Web.Mvc')" xdt:Transform="Remove" />
|
||||
@@ -334,10 +335,14 @@
|
||||
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="HtmlAgilityPack" publicKeyToken="bd319b19eaf3b43a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.4.9.0" newVersion="1.4.9.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="AutoMapper" publicKeyToken="be96cd2c38ef1005" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.3.1.0" newVersion="3.3.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly xdt:Transform="Insert">
|
||||
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
|
||||
|
||||
@@ -71,6 +71,7 @@ if ($project) {
|
||||
if(Test-Path $umbracoBinFolder\ImageProcessor.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.dll -Force -Confirm:$false }
|
||||
if(Test-Path $umbracoBinFolder\ImageProcessor.Web.dll) { Remove-Item $umbracoBinFolder\ImageProcessor.Web.dll -Force -Confirm:$false }
|
||||
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
|
||||
if(Test-Path $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll) { Remove-Item $umbracoBinFolder\Microsoft.IO.RecyclableMemoryStream.dll -Force -Confirm:$false }
|
||||
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false }
|
||||
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false }
|
||||
if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false }
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<processing xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform" xdt:Transform="SetAttributes(preserveExifMetaData)" preserveExifMetaData="true" />
|
||||
@@ -1,2 +1,3 @@
|
||||
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
|
||||
7.5.2
|
||||
7.6.0
|
||||
alpha055
|
||||
Binary file not shown.
@@ -49,13 +49,9 @@
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll</HintPath>
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.SqlServerCe.Entity, Version=4.0.0.1, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll</HintPath>
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
||||
@@ -180,12 +180,16 @@ namespace SqlCE4Umbraco
|
||||
/// <returns>The return value of the command.</returns>
|
||||
protected override object ExecuteScalar(string commandText, SqlCeParameter[] parameters)
|
||||
{
|
||||
#if DEBUG && DebugDataLayer
|
||||
#if DEBUG && DebugDataLayer
|
||||
// Log Query Execution
|
||||
Trace.TraceInformation(GetType().Name + " SQL ExecuteScalar: " + commandText);
|
||||
#endif
|
||||
|
||||
return SqlCeApplicationBlock.ExecuteScalar(ConnectionString, CommandType.Text, commandText, parameters);
|
||||
#endif
|
||||
using (var cc = UseCurrentConnection)
|
||||
{
|
||||
return SqlCeApplicationBlock.ExecuteScalar(
|
||||
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
|
||||
CommandType.Text, commandText, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,12 +202,17 @@ namespace SqlCE4Umbraco
|
||||
/// </returns>
|
||||
protected override int ExecuteNonQuery(string commandText, SqlCeParameter[] parameters)
|
||||
{
|
||||
#if DEBUG && DebugDataLayer
|
||||
#if DEBUG && DebugDataLayer
|
||||
// Log Query Execution
|
||||
Trace.TraceInformation(GetType().Name + " SQL ExecuteNonQuery: " + commandText);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return SqlCeApplicationBlock.ExecuteNonQuery(ConnectionString, CommandType.Text, commandText, parameters);
|
||||
using (var cc = UseCurrentConnection)
|
||||
{
|
||||
return SqlCeApplicationBlock.ExecuteNonQuery(
|
||||
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
|
||||
CommandType.Text, commandText, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -216,13 +225,17 @@ namespace SqlCE4Umbraco
|
||||
/// </returns>
|
||||
protected override IRecordsReader ExecuteReader(string commandText, SqlCeParameter[] parameters)
|
||||
{
|
||||
#if DEBUG && DebugDataLayer
|
||||
#if DEBUG && DebugDataLayer
|
||||
// Log Query Execution
|
||||
Trace.TraceInformation(GetType().Name + " SQL ExecuteReader: " + commandText);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(ConnectionString, CommandType.Text,
|
||||
commandText, parameters));
|
||||
using (var cc = UseCurrentConnection)
|
||||
{
|
||||
return new SqlCeDataReaderHelper(SqlCeApplicationBlock.ExecuteReader(
|
||||
(SqlCeConnection) cc.Connection, (SqlCeTransaction) cc.Transaction,
|
||||
CommandType.Text, commandText, parameters));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,15 +21,15 @@ namespace SqlCE4Umbraco
|
||||
public class SqlCEInstaller : DefaultInstallerUtility<SqlCEHelper>
|
||||
{
|
||||
#region Private Constants
|
||||
|
||||
|
||||
/// <summary>The latest database version this installer supports.</summary>
|
||||
private const DatabaseVersion LatestVersionSupported = DatabaseVersion.Version4_8;
|
||||
|
||||
/// <summary>The specifications to determine the database version.</summary>
|
||||
private static readonly VersionSpecs[] m_VersionSpecs = new VersionSpecs[] {
|
||||
new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8),
|
||||
new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1),
|
||||
new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4),
|
||||
new VersionSpecs("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LEFT OUTER JOIN umbracoApp ON appAlias = appAlias WHERE CONSTRAINT_NAME = 'FK_umbracoUser2app_umbracoApp'", 0, DatabaseVersion.Version4_8),
|
||||
new VersionSpecs("SELECT id FROM umbracoNode WHERE id = -21", 1, DatabaseVersion.Version4_1),
|
||||
new VersionSpecs("SELECT action FROM umbracoAppTree",DatabaseVersion.Version4),
|
||||
new VersionSpecs("SELECT description FROM cmsContentType",DatabaseVersion.Version3),
|
||||
new VersionSpecs("SELECT id FROM sysobjects",DatabaseVersion.None) };
|
||||
|
||||
@@ -43,8 +43,9 @@ namespace SqlCE4Umbraco
|
||||
public override bool CanConnect
|
||||
{
|
||||
get
|
||||
{
|
||||
SqlHelper.CreateEmptyDatabase();
|
||||
{
|
||||
using (var sqlHelper = SqlHelper)
|
||||
sqlHelper.CreateEmptyDatabase();
|
||||
return base.CanConnect;
|
||||
}
|
||||
}
|
||||
@@ -93,22 +94,22 @@ namespace SqlCE4Umbraco
|
||||
|
||||
#region DefaultInstaller Members
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sql to do a full install
|
||||
/// </summary>
|
||||
protected override string FullInstallSql
|
||||
{
|
||||
get { return string.Empty; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the sql to do a full install
|
||||
/// </summary>
|
||||
protected override string FullInstallSql
|
||||
{
|
||||
get { return string.Empty; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the sql to do an upgrade
|
||||
/// </summary>
|
||||
protected override string UpgradeSql
|
||||
{
|
||||
get { return string.Empty; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the sql to do an upgrade
|
||||
/// </summary>
|
||||
protected override string UpgradeSql
|
||||
{
|
||||
get { return string.Empty; }
|
||||
}
|
||||
|
||||
// We need to override this as the default way of detection a db connection checks for systables that doesn't exist
|
||||
// in a CE db
|
||||
@@ -123,8 +124,9 @@ namespace SqlCE4Umbraco
|
||||
// verify connection
|
||||
try
|
||||
{
|
||||
if (SqlCeApplicationBlock.VerifyConnection(base.SqlHelper.ConnectionString))
|
||||
return DatabaseVersion.None;
|
||||
using (var sqlHelper = SqlHelper)
|
||||
if (SqlCeApplicationBlock.VerifyConnection(sqlHelper.ConnectionString))
|
||||
return DatabaseVersion.None;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -37,13 +37,15 @@ namespace SqlCE4Umbraco
|
||||
ITable table = null;
|
||||
|
||||
// get name in correct casing
|
||||
name = SqlHelper.ExecuteScalar<string>("SELECT name FROM sys.tables WHERE name=@name",
|
||||
SqlHelper.CreateParameter("name", name));
|
||||
using (var sqlHelper = SqlHelper)
|
||||
name = sqlHelper.ExecuteScalar<string>("SELECT name FROM sys.tables WHERE name=@name",
|
||||
sqlHelper.CreateParameter("name", name));
|
||||
if (name != null)
|
||||
{
|
||||
table = new DefaultTable(name);
|
||||
|
||||
using (IRecordsReader reader = SqlHelper.ExecuteReader(
|
||||
using (var sqlHelper = SqlHelper)
|
||||
using (IRecordsReader reader = sqlHelper.ExecuteReader(
|
||||
@"SELECT c.name AS Name, st.name AS DataType, c.max_length, c.is_nullable, c.is_identity
|
||||
FROM sys.tables AS t
|
||||
JOIN sys.columns AS c ON t.object_id = c.object_id
|
||||
@@ -51,7 +53,7 @@ namespace SqlCE4Umbraco
|
||||
JOIN sys.types AS ty ON ty.user_type_id = c.user_type_id
|
||||
JOIN sys.types st ON ty.system_type_id = st.user_type_id
|
||||
WHERE t.name = @name
|
||||
ORDER BY c.column_id", SqlHelper.CreateParameter("name", name)))
|
||||
ORDER BY c.column_id", sqlHelper.CreateParameter("name", name)))
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
@@ -112,7 +114,8 @@ namespace SqlCE4Umbraco
|
||||
|
||||
// create query
|
||||
StringBuilder createTableQuery = new StringBuilder();
|
||||
createTableQuery.AppendFormat("CREATE TABLE [{0}] (", SqlHelper.EscapeString(table.Name));
|
||||
using (var sqlHelper = SqlHelper)
|
||||
createTableQuery.AppendFormat("CREATE TABLE [{0}] (", sqlHelper.EscapeString(table.Name));
|
||||
|
||||
// add fields
|
||||
while (hasNext)
|
||||
@@ -136,7 +139,8 @@ namespace SqlCE4Umbraco
|
||||
// execute query
|
||||
try
|
||||
{
|
||||
SqlHelper.ExecuteNonQuery(createTableQuery.ToString());
|
||||
using (var sqlHelper = SqlHelper)
|
||||
sqlHelper.ExecuteNonQuery(createTableQuery.ToString());
|
||||
}
|
||||
catch (Exception executeException)
|
||||
{
|
||||
@@ -154,13 +158,15 @@ namespace SqlCE4Umbraco
|
||||
Debug.Assert(table != null && field != null);
|
||||
|
||||
StringBuilder addColumnQuery = new StringBuilder();
|
||||
addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}",
|
||||
SqlHelper.EscapeString(table.Name),
|
||||
SqlHelper.EscapeString(field.Name),
|
||||
SqlHelper.EscapeString(GetDatabaseType(field)));
|
||||
using (var sqlHelper = SqlHelper)
|
||||
addColumnQuery.AppendFormat("ALTER TABLE [{0}] ADD [{1}] {2}",
|
||||
sqlHelper.EscapeString(table.Name),
|
||||
sqlHelper.EscapeString(field.Name),
|
||||
sqlHelper.EscapeString(GetDatabaseType(field)));
|
||||
try
|
||||
{
|
||||
SqlHelper.ExecuteNonQuery(addColumnQuery.ToString());
|
||||
using (var sqlHelper = SqlHelper)
|
||||
sqlHelper.ExecuteNonQuery(addColumnQuery.ToString());
|
||||
}
|
||||
catch (Exception executeException)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
@@ -26,30 +24,61 @@ namespace SqlCE4Umbraco
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
object retVal;
|
||||
|
||||
try
|
||||
{
|
||||
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
|
||||
using (var conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
using (SqlCeCommand cmd = new SqlCeCommand(commandText, conn))
|
||||
{
|
||||
AttachParameters(cmd, commandParameters);
|
||||
Debug.WriteLine("---------------------------------SCALAR-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
retVal = cmd.ExecuteScalar();
|
||||
}
|
||||
return ExecuteScalarTry(conn, null, commandText, commandParameters);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
|
||||
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee);
|
||||
}
|
||||
}
|
||||
|
||||
public static object ExecuteScalar(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ExecuteScalarTry(conn, trx, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running Scalar: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee);
|
||||
}
|
||||
}
|
||||
|
||||
public static object ExecuteScalar(
|
||||
SqlCeConnection conn,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
return ExecuteScalar(conn, null, commandType, commandText, commandParameters);
|
||||
}
|
||||
|
||||
private static object ExecuteScalarTry(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
object retVal;
|
||||
using (var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx))
|
||||
{
|
||||
AttachParameters(cmd, commandParameters);
|
||||
Debug.WriteLine("---------------------------------SCALAR-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
retVal = cmd.ExecuteScalar();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -66,49 +95,10 @@ namespace SqlCE4Umbraco
|
||||
{
|
||||
try
|
||||
{
|
||||
int rowsAffected;
|
||||
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
|
||||
using (var conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
// this is for multiple queries in the installer
|
||||
if (commandText.Trim().StartsWith("!!!"))
|
||||
{
|
||||
commandText = commandText.Trim().Trim('!');
|
||||
string[] commands = commandText.Split('|');
|
||||
string currentCmd = String.Empty;
|
||||
|
||||
foreach (string cmd in commands)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentCmd = cmd;
|
||||
if (!String.IsNullOrWhiteSpace(cmd))
|
||||
{
|
||||
SqlCeCommand c = new SqlCeCommand(cmd, conn);
|
||||
c.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
Debug.WriteLine(currentCmd);
|
||||
Debug.WriteLine(e);
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
SqlCeCommand cmd = new SqlCeCommand(commandText, conn);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
rowsAffected = cmd.ExecuteNonQuery();
|
||||
}
|
||||
return ExecuteNonQueryTry(conn, null, commandText, commandParameters);
|
||||
}
|
||||
|
||||
return rowsAffected;
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
@@ -116,6 +106,74 @@ namespace SqlCE4Umbraco
|
||||
}
|
||||
}
|
||||
|
||||
public static int ExecuteNonQuery(
|
||||
SqlCeConnection conn,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
return ExecuteNonQuery(conn, null, commandType, commandText, commandParameters);
|
||||
}
|
||||
|
||||
public static int ExecuteNonQuery(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ExecuteNonQueryTry(conn, trx, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running NonQuery: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static int ExecuteNonQueryTry(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
// this is for multiple queries in the installer
|
||||
if (commandText.Trim().StartsWith("!!!"))
|
||||
{
|
||||
commandText = commandText.Trim().Trim('!');
|
||||
var commands = commandText.Split('|');
|
||||
var currentCmd = string.Empty;
|
||||
|
||||
foreach (var command in commands)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentCmd = command;
|
||||
if (string.IsNullOrWhiteSpace(command)) continue;
|
||||
var c = trx == null ? new SqlCeCommand(command, conn) : new SqlCeCommand(command, conn, trx);
|
||||
c.ExecuteNonQuery();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
Debug.WriteLine(currentCmd);
|
||||
Debug.WriteLine(e);
|
||||
Debug.WriteLine("*******************************************************************");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
var cmd = new SqlCeCommand(commandText, conn);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
var rowsAffected = cmd.ExecuteNonQuery();
|
||||
return rowsAffected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -133,25 +191,8 @@ namespace SqlCE4Umbraco
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine("---------------------------------READER-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
SqlCeDataReader reader;
|
||||
SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString);
|
||||
|
||||
try
|
||||
{
|
||||
SqlCeCommand cmd = new SqlCeCommand(commandText, conn);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
reader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
conn.Close();
|
||||
throw;
|
||||
}
|
||||
|
||||
return reader;
|
||||
var conn = SqlCeContextGuardian.Open(connectionString);
|
||||
return ExecuteReaderTry(conn, null, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
@@ -159,30 +200,71 @@ namespace SqlCE4Umbraco
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerifyConnection(string connectionString)
|
||||
public static SqlCeDataReader ExecuteReader(
|
||||
SqlCeConnection conn,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
bool isConnected = false;
|
||||
using (SqlCeConnection conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
isConnected = conn.State == ConnectionState.Open;
|
||||
}
|
||||
|
||||
return isConnected;
|
||||
return ExecuteReader(conn, commandType, commandText, commandParameters);
|
||||
}
|
||||
|
||||
private static void AttachParameters(SqlCeCommand command, SqlCeParameter[] commandParameters)
|
||||
public static SqlCeDataReader ExecuteReader(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters
|
||||
)
|
||||
{
|
||||
foreach (SqlCeParameter parameter in commandParameters)
|
||||
try
|
||||
{
|
||||
return ExecuteReaderTry(conn, trx, commandText, commandParameters);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
throw new SqlCeProviderException("Error running Reader: \nSQL Statement:\n" + commandText + "\n\nException:\n" + ee.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static SqlCeDataReader ExecuteReaderTry(
|
||||
SqlCeConnection conn, SqlCeTransaction trx,
|
||||
string commandText,
|
||||
params SqlCeParameter[] commandParameters)
|
||||
{
|
||||
Debug.WriteLine("---------------------------------READER-------------------------------------");
|
||||
Debug.WriteLine(commandText);
|
||||
Debug.WriteLine("----------------------------------------------------------------------------");
|
||||
|
||||
try
|
||||
{
|
||||
var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
conn.Close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool VerifyConnection(string connectionString)
|
||||
{
|
||||
using (var conn = SqlCeContextGuardian.Open(connectionString))
|
||||
{
|
||||
return conn.State == ConnectionState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AttachParameters(SqlCeCommand command, IEnumerable<SqlCeParameter> commandParameters)
|
||||
{
|
||||
foreach (var parameter in commandParameters)
|
||||
{
|
||||
if ((parameter.Direction == ParameterDirection.InputOutput) && (parameter.Value == null))
|
||||
{
|
||||
parameter.Value = DBNull.Value;
|
||||
}
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
using System.Resources;
|
||||
|
||||
[assembly: AssemblyCompany("Umbraco")]
|
||||
[assembly: AssemblyCopyright("Copyright © Umbraco 2016")]
|
||||
[assembly: AssemblyCopyright("Copyright © Umbraco 2017")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -11,5 +11,5 @@ using System.Resources;
|
||||
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.5.2")]
|
||||
[assembly: AssemblyInformationalVersion("7.5.2")]
|
||||
[assembly: AssemblyFileVersion("7.6.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.6.0-alpha055")]
|
||||
@@ -296,7 +296,7 @@ namespace Umbraco.Core
|
||||
// if we have a db context available, if we don't then we are not installed anyways
|
||||
if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect)
|
||||
{
|
||||
var found = Services.MigrationEntryService.FindEntry(GlobalSettings.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion());
|
||||
var found = Services.MigrationEntryService.FindEntry(Constants.System.UmbracoMigrationName, UmbracoVersion.GetSemanticVersion());
|
||||
if (found == null)
|
||||
{
|
||||
//we haven't executed this migration in this environment, so even though the config versions match,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static class ByteArrayExtensions
|
||||
{
|
||||
private static readonly char[] BytesToHexStringLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
|
||||
public static string ToHexString(this byte[] bytes)
|
||||
{
|
||||
int i = 0, p = 0, bytesLength = bytes.Length;
|
||||
var chars = new char[bytesLength * 2];
|
||||
while (i < bytesLength)
|
||||
{
|
||||
var b = bytes[i++];
|
||||
chars[p++] = BytesToHexStringLookup[b / 0x10];
|
||||
chars[p++] = BytesToHexStringLookup[b % 0x10];
|
||||
}
|
||||
return new string(chars, 0, chars.Length);
|
||||
}
|
||||
|
||||
public static string ToHexString(this byte[] bytes, char separator, int blockSize, int blockCount)
|
||||
{
|
||||
int p = 0, bytesLength = bytes.Length, count = 0, size = 0;
|
||||
var chars = new char[bytesLength * 2 + blockCount];
|
||||
for (var i = 0; i < bytesLength; i++)
|
||||
{
|
||||
var b = bytes[i++];
|
||||
chars[p++] = BytesToHexStringLookup[b / 0x10];
|
||||
chars[p++] = BytesToHexStringLookup[b % 0x10];
|
||||
if (count == blockCount) continue;
|
||||
if (++size < blockSize) continue;
|
||||
|
||||
chars[p++] = '/';
|
||||
size = 0;
|
||||
count++;
|
||||
}
|
||||
return new string(chars, 0, chars.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,8 @@ namespace Umbraco.Core.Cache
|
||||
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
|
||||
public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:";
|
||||
|
||||
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
|
||||
[Obsolete("No longer used and will be removed in v8")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache";
|
||||
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
value = result.Value; // will not throw (safe lazy)
|
||||
var eh = value as ExceptionHolder;
|
||||
if (eh != null) throw eh.Exception; // throw once!
|
||||
if (eh != null) throw new Exception("Exception while creating a value.", eh.Exception); // throw once!
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
get
|
||||
{
|
||||
var settings = ConfigurationManager.ConnectionStrings[UmbracoConnectionName];
|
||||
var settings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName];
|
||||
var connectionString = string.Empty;
|
||||
|
||||
if (settings != null)
|
||||
@@ -241,10 +241,7 @@ namespace Umbraco.Core.Configuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Move these to constants!
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoMigrationName = "Umbraco";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance.
|
||||
|
||||
@@ -4,8 +4,7 @@ using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
|
||||
internal class ContentElement : ConfigurationElement, IContentSection
|
||||
internal class ContentElement : UmbracoConfigurationElement, IContentSection
|
||||
{
|
||||
[ConfigurationProperty("imaging")]
|
||||
internal ContentImagingElement Imaging
|
||||
@@ -22,25 +21,13 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
[ConfigurationProperty("ResolveUrlsFromTextString")]
|
||||
internal InnerTextConfigurationElement<bool> ResolveUrlsFromTextString
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["ResolveUrlsFromTextString"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement<bool>("ResolveUrlsFromTextString", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("UploadAllowDirectories")]
|
||||
internal InnerTextConfigurationElement<bool> UploadAllowDirectories
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["UploadAllowDirectories"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("UploadAllowDirectories", true); }
|
||||
}
|
||||
|
||||
public IEnumerable<IContentErrorPage> Error404Collection
|
||||
@@ -63,121 +50,61 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
[ConfigurationProperty("ensureUniqueNaming")]
|
||||
internal InnerTextConfigurationElement<bool> EnsureUniqueNaming
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["ensureUniqueNaming"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("ensureUniqueNaming", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("TidyEditorContent")]
|
||||
internal InnerTextConfigurationElement<bool> TidyEditorContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["TidyEditorContent"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("TidyEditorContent", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("TidyCharEncoding")]
|
||||
internal InnerTextConfigurationElement<string> TidyCharEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["TidyCharEncoding"],
|
||||
//set the default
|
||||
"UTF8");
|
||||
}
|
||||
get { return GetOptionalTextElement("TidyCharEncoding", "UTF8"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("XmlCacheEnabled")]
|
||||
internal InnerTextConfigurationElement<bool> XmlCacheEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["XmlCacheEnabled"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("XmlCacheEnabled", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("ContinouslyUpdateXmlDiskCache")]
|
||||
internal InnerTextConfigurationElement<bool> ContinouslyUpdateXmlDiskCache
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["ContinouslyUpdateXmlDiskCache"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("ContinouslyUpdateXmlDiskCache", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("XmlContentCheckForDiskChanges")]
|
||||
internal InnerTextConfigurationElement<bool> XmlContentCheckForDiskChanges
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["XmlContentCheckForDiskChanges"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("XmlContentCheckForDiskChanges", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("EnableSplashWhileLoading")]
|
||||
internal InnerTextConfigurationElement<bool> EnableSplashWhileLoading
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["EnableSplashWhileLoading"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("EnableSplashWhileLoading", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("PropertyContextHelpOption")]
|
||||
internal InnerTextConfigurationElement<string> PropertyContextHelpOption
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["PropertyContextHelpOption"],
|
||||
//set the default
|
||||
"text");
|
||||
}
|
||||
get { return GetOptionalTextElement("PropertyContextHelpOption", "text"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("UseLegacyXmlSchema")]
|
||||
internal InnerTextConfigurationElement<bool> UseLegacyXmlSchema
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["UseLegacyXmlSchema"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("UseLegacyXmlSchema", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("ForceSafeAliases")]
|
||||
internal InnerTextConfigurationElement<bool> ForceSafeAliases
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["ForceSafeAliases"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("ForceSafeAliases", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("PreviewBadge")]
|
||||
@@ -185,123 +112,63 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["PreviewBadge"],
|
||||
//set the default
|
||||
@"<a id=""umbracoPreviewBadge"" style=""position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{1}/preview/previewModeBadge.png') no-repeat;"" href=""{0}/endPreview.aspx?redir={2}""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
|
||||
return GetOptionalTextElement("PreviewBadge", @"<a id=""umbracoPreviewBadge"" style=""position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{1}/preview/previewModeBadge.png') no-repeat;"" href=""{0}/endPreview.aspx?redir={2}""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
|
||||
}
|
||||
}
|
||||
|
||||
[ConfigurationProperty("UmbracoLibraryCacheDuration")]
|
||||
internal InnerTextConfigurationElement<int> UmbracoLibraryCacheDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<int>(
|
||||
(InnerTextConfigurationElement<int>)this["UmbracoLibraryCacheDuration"],
|
||||
//set the default
|
||||
1800);
|
||||
|
||||
}
|
||||
get { return GetOptionalTextElement("UmbracoLibraryCacheDuration", 1800); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("MacroErrors")]
|
||||
internal InnerTextConfigurationElement<MacroErrorBehaviour> MacroErrors
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
return new OptionalInnerTextConfigurationElement<MacroErrorBehaviour>(
|
||||
(InnerTextConfigurationElement<MacroErrorBehaviour>)this["MacroErrors"],
|
||||
//set the default
|
||||
MacroErrorBehaviour.Inline);
|
||||
}
|
||||
get { return GetOptionalTextElement("MacroErrors", MacroErrorBehaviour.Inline); }
|
||||
}
|
||||
|
||||
[Obsolete("This is here so that if this config element exists we won't get a YSOD, it is not used whatsoever and will be removed in future versions")]
|
||||
[ConfigurationProperty("DocumentTypeIconList")]
|
||||
internal InnerTextConfigurationElement<IconPickerBehaviour> DocumentTypeIconList
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<IconPickerBehaviour>(
|
||||
(InnerTextConfigurationElement<IconPickerBehaviour>)this["DocumentTypeIconList"],
|
||||
//set the default
|
||||
IconPickerBehaviour.HideFileDuplicates);
|
||||
}
|
||||
get { return GetOptionalTextElement("DocumentTypeIconList", IconPickerBehaviour.HideFileDuplicates); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("disallowedUploadFiles")]
|
||||
internal CommaDelimitedConfigurationElement DisallowedUploadFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalCommaDelimitedConfigurationElement(
|
||||
(CommaDelimitedConfigurationElement)this["disallowedUploadFiles"],
|
||||
//set the default
|
||||
new[] { "ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd" });
|
||||
|
||||
}
|
||||
get { return GetOptionalDelimitedElement("disallowedUploadFiles", new[] {"ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd"}); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("cloneXmlContent")]
|
||||
internal InnerTextConfigurationElement<bool> CloneXmlContent
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["cloneXmlContent"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("cloneXmlContent", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("GlobalPreviewStorageEnabled")]
|
||||
internal InnerTextConfigurationElement<bool> GlobalPreviewStorageEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["GlobalPreviewStorageEnabled"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("GlobalPreviewStorageEnabled", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("defaultDocumentTypeProperty")]
|
||||
internal InnerTextConfigurationElement<string> DefaultDocumentTypeProperty
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["defaultDocumentTypeProperty"],
|
||||
//set the default
|
||||
"Textstring");
|
||||
}
|
||||
get { return GetOptionalTextElement("defaultDocumentTypeProperty", "Textstring"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("EnableInheritedDocumentTypes")]
|
||||
internal InnerTextConfigurationElement<bool> EnableInheritedDocumentTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>) this["EnableInheritedDocumentTypes"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("EnableInheritedDocumentTypes", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("EnableInheritedMediaTypes")]
|
||||
internal InnerTextConfigurationElement<bool> EnableInheritedMediaTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["EnableInheritedMediaTypes"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("EnableInheritedMediaTypes", true); }
|
||||
}
|
||||
|
||||
string IContentSection.NotificationEmailAddress
|
||||
|
||||
@@ -3,42 +3,24 @@ using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class ContentScriptEditorElement : ConfigurationElement
|
||||
internal class ContentScriptEditorElement : UmbracoConfigurationElement
|
||||
{
|
||||
[ConfigurationProperty("scriptFolderPath")]
|
||||
internal InnerTextConfigurationElement<string> ScriptFolderPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["scriptFolderPath"],
|
||||
//set the default
|
||||
"/scripts");
|
||||
}
|
||||
get { return GetOptionalTextElement("scriptFolderPath", "/scripts"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("scriptFileTypes")]
|
||||
internal OptionalCommaDelimitedConfigurationElement ScriptFileTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalCommaDelimitedConfigurationElement(
|
||||
(OptionalCommaDelimitedConfigurationElement)this["scriptFileTypes"],
|
||||
//set the default
|
||||
new[] { "js", "xml" });
|
||||
}
|
||||
get { return GetOptionalDelimitedElement("scriptFileTypes", new[] {"js", "xml"}); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("scriptDisableEditor")]
|
||||
internal InnerTextConfigurationElement<bool> ScriptEditorDisable
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>) this["scriptDisableEditor"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("scriptDisableEditor", false); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-29
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class ImagingAutoFillUploadFieldElement : ConfigurationElement, IImagingAutoFillUploadField
|
||||
internal class ImagingAutoFillUploadFieldElement : UmbracoConfigurationElement, IImagingAutoFillUploadField
|
||||
{
|
||||
/// <summary>
|
||||
/// Allow setting internally so we can create a default
|
||||
@@ -17,49 +17,25 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
[ConfigurationProperty("widthFieldAlias")]
|
||||
internal InnerTextConfigurationElement<string> WidthFieldAlias
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["widthFieldAlias"],
|
||||
//set the default
|
||||
"umbracoWidth");
|
||||
}
|
||||
get { return GetOptionalTextElement("widthFieldAlias", "umbracoWidth"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("heightFieldAlias")]
|
||||
internal InnerTextConfigurationElement<string> HeightFieldAlias
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["heightFieldAlias"],
|
||||
//set the default
|
||||
"umbracoHeight");
|
||||
}
|
||||
get { return GetOptionalTextElement("heightFieldAlias", "umbracoHeight"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("lengthFieldAlias")]
|
||||
internal InnerTextConfigurationElement<string> LengthFieldAlias
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["lengthFieldAlias"],
|
||||
//set the default
|
||||
"umbracoBytes");
|
||||
}
|
||||
get { return GetOptionalTextElement("lengthFieldAlias", "umbracoBytes"); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("extensionFieldAlias")]
|
||||
internal InnerTextConfigurationElement<string> ExtensionFieldAlias
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["extensionFieldAlias"],
|
||||
//set the default
|
||||
"umbracoExtension");
|
||||
}
|
||||
get { return GetOptionalTextElement("extensionFieldAlias", "umbracoExtension"); }
|
||||
}
|
||||
|
||||
string IImagingAutoFillUploadField.Alias
|
||||
|
||||
@@ -3,67 +3,37 @@ using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class LoggingElement : ConfigurationElement, ILoggingSection
|
||||
internal class LoggingElement : UmbracoConfigurationElement, ILoggingSection
|
||||
{
|
||||
|
||||
[ConfigurationProperty("autoCleanLogs")]
|
||||
internal InnerTextConfigurationElement<bool> AutoCleanLogs
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["autoCleanLogs"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("autoCleanLogs", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("enableLogging")]
|
||||
internal InnerTextConfigurationElement<bool> EnableLogging
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["enableLogging"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("enableLogging", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("enableAsyncLogging")]
|
||||
internal InnerTextConfigurationElement<bool> EnableAsyncLogging
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["enableAsyncLogging"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("enableAsyncLogging", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("cleaningMiliseconds")]
|
||||
internal InnerTextConfigurationElement<int> CleaningMiliseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<int>(
|
||||
(InnerTextConfigurationElement<int>)this["cleaningMiliseconds"],
|
||||
//set the default
|
||||
-1);
|
||||
}
|
||||
get { return GetOptionalTextElement("cleaningMiliseconds", -1); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("maxLogAge")]
|
||||
internal InnerTextConfigurationElement<int> MaxLogAge
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<int>(
|
||||
(InnerTextConfigurationElement<int>)this["maxLogAge"],
|
||||
//set the default
|
||||
-1);
|
||||
}
|
||||
get { return GetOptionalTextElement("maxLogAge", -1); }
|
||||
}
|
||||
|
||||
[ConfigurationCollection(typeof(DisabledLogTypesCollection), AddItemName = "logTypeAlias")]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class NotificationsElement : ConfigurationElement
|
||||
internal class NotificationsElement : UmbracoConfigurationElement
|
||||
{
|
||||
[ConfigurationProperty("email")]
|
||||
internal InnerTextConfigurationElement<string> NotificationEmailAddress
|
||||
@@ -13,13 +13,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
[ConfigurationProperty("disableHtmlEmail")]
|
||||
internal InnerTextConfigurationElement<bool> DisableHtmlEmail
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>) this["disableHtmlEmail"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("disableHtmlEmail", false); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,30 +5,18 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class RequestHandlerElement : ConfigurationElement, IRequestHandlerSection
|
||||
internal class RequestHandlerElement : UmbracoConfigurationElement, IRequestHandlerSection
|
||||
{
|
||||
[ConfigurationProperty("useDomainPrefixes")]
|
||||
public InnerTextConfigurationElement<bool> UseDomainPrefixes
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["useDomainPrefixes"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("useDomainPrefixes", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("addTrailingSlash")]
|
||||
public InnerTextConfigurationElement<bool> AddTrailingSlash
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["addTrailingSlash"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("addTrailingSlash", true); }
|
||||
}
|
||||
|
||||
private UrlReplacingElement _defaultUrlReplacing;
|
||||
|
||||
@@ -2,66 +2,36 @@
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class SecurityElement : ConfigurationElement, ISecuritySection
|
||||
internal class SecurityElement : UmbracoConfigurationElement, ISecuritySection
|
||||
{
|
||||
[ConfigurationProperty("keepUserLoggedIn")]
|
||||
internal InnerTextConfigurationElement<bool> KeepUserLoggedIn
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["keepUserLoggedIn"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("keepUserLoggedIn", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("hideDisabledUsersInBackoffice")]
|
||||
internal InnerTextConfigurationElement<bool> HideDisabledUsersInBackoffice
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["hideDisabledUsersInBackoffice"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("hideDisabledUsersInBackoffice", false); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("allowPasswordReset")]
|
||||
internal InnerTextConfigurationElement<bool> AllowPasswordReset
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["allowPasswordReset"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("allowPasswordReset", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("authCookieName")]
|
||||
internal InnerTextConfigurationElement<string> AuthCookieName
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["authCookieName"],
|
||||
//set the default
|
||||
Constants.Web.AuthCookieName);
|
||||
}
|
||||
get { return GetOptionalTextElement("authCookieName", Constants.Web.AuthCookieName); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("authCookieDomain")]
|
||||
internal InnerTextConfigurationElement<string> AuthCookieDomain
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<string>(
|
||||
(InnerTextConfigurationElement<string>)this["authCookieDomain"],
|
||||
//set the default
|
||||
null);
|
||||
}
|
||||
get { return GetOptionalTextElement<string>("authCookieDomain", null); }
|
||||
}
|
||||
|
||||
bool ISecuritySection.KeepUserLoggedIn
|
||||
|
||||
@@ -3,55 +3,31 @@ using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
internal class TemplatesElement : ConfigurationElement, ITemplatesSection
|
||||
internal class TemplatesElement : UmbracoConfigurationElement, ITemplatesSection
|
||||
{
|
||||
[ConfigurationProperty("useAspNetMasterPages")]
|
||||
internal InnerTextConfigurationElement<bool> UseAspNetMasterPages
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["useAspNetMasterPages"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("useAspNetMasterPages", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("enableSkinSupport")]
|
||||
internal InnerTextConfigurationElement<bool> EnableSkinSupport
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["enableSkinSupport"],
|
||||
//set the default
|
||||
true);
|
||||
}
|
||||
get { return GetOptionalTextElement("enableSkinSupport", true); }
|
||||
}
|
||||
|
||||
[ConfigurationProperty("defaultRenderingEngine", IsRequired = true)]
|
||||
internal InnerTextConfigurationElement<RenderingEngine> DefaultRenderingEngine
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<RenderingEngine>(
|
||||
(InnerTextConfigurationElement<RenderingEngine>)this["defaultRenderingEngine"],
|
||||
//set the default
|
||||
RenderingEngine.Mvc);
|
||||
}
|
||||
get { return GetOptionalTextElement("defaultRenderingEngine", RenderingEngine.Mvc); }
|
||||
}
|
||||
|
||||
[Obsolete("This has no affect and will be removed in future versions")]
|
||||
[ConfigurationProperty("enableTemplateFolders")]
|
||||
internal InnerTextConfigurationElement<bool> EnableTemplateFolders
|
||||
{
|
||||
get
|
||||
{
|
||||
return new OptionalInnerTextConfigurationElement<bool>(
|
||||
(InnerTextConfigurationElement<bool>)this["enableTemplateFolders"],
|
||||
//set the default
|
||||
false);
|
||||
}
|
||||
get { return GetOptionalTextElement("enableTemplateFolders", false); }
|
||||
}
|
||||
|
||||
bool ITemplatesSection.UseAspNetMasterPages
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Configuration;
|
||||
|
||||
namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class with shared helper methods
|
||||
/// </summary>
|
||||
internal class UmbracoConfigurationElement : ConfigurationElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Used so the RawElement types are not re-created every time they are accessed
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, RawXmlConfigurationElement> _rawElements = new ConcurrentDictionary<string, RawXmlConfigurationElement>();
|
||||
|
||||
protected OptionalInnerTextConfigurationElement<T> GetOptionalTextElement<T>(string name, T defaultVal)
|
||||
{
|
||||
return (OptionalInnerTextConfigurationElement<T>) _rawElements.GetOrAdd(
|
||||
name,
|
||||
s => new OptionalInnerTextConfigurationElement<T>(
|
||||
(InnerTextConfigurationElement<T>) this[s],
|
||||
//set the default
|
||||
defaultVal));
|
||||
}
|
||||
|
||||
protected OptionalCommaDelimitedConfigurationElement GetOptionalDelimitedElement(string name, string[] defaultVal)
|
||||
{
|
||||
return (OptionalCommaDelimitedConfigurationElement) _rawElements.GetOrAdd(
|
||||
name,
|
||||
s => new OptionalCommaDelimitedConfigurationElement(
|
||||
(CommaDelimitedConfigurationElement) this[name],
|
||||
//set the default
|
||||
defaultVal));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public class UmbracoVersion
|
||||
{
|
||||
private static readonly Version Version = new Version("7.5.2");
|
||||
private static readonly Version Version = new Version("7.6.0");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
|
||||
/// Gets the version comment (like beta or RC).
|
||||
/// </summary>
|
||||
/// <value>The version comment.</value>
|
||||
public static string CurrentComment { get { return ""; } }
|
||||
public static string CurrentComment { get { return "alpha055"; } }
|
||||
|
||||
// Get the version of the umbraco.dll by looking at a class in that dll
|
||||
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
|
||||
|
||||
@@ -4,7 +4,8 @@ using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the identifiers for property-type alias conventions that are used within the Umbraco core.
|
||||
@@ -122,6 +123,11 @@ namespace Umbraco.Core
|
||||
/// MediaType alias for an image.
|
||||
/// </summary>
|
||||
public const string Image = "Image";
|
||||
|
||||
/// <summary>
|
||||
/// MediaType alias indicating allowing auto-selection.
|
||||
/// </summary>
|
||||
public const string AutoSelect = "umbracoAutoSelect";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class DatabaseProviders
|
||||
{
|
||||
public const string SqlCe = "System.Data.SqlServerCe.4.0";
|
||||
public const string SqlServer = "System.Data.SqlClient";
|
||||
public const string MySql = "MySql.Data.MySqlClient";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
|
||||
public static partial class Constants
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Defines well-known entity types.
|
||||
/// </summary>
|
||||
/// <remarks>Well-known entity types are those that Deploy already knows about,
|
||||
/// but entity types are strings and so can be extended beyond what is defined here.</remarks>
|
||||
public static class DeployEntityType
|
||||
{
|
||||
// guid entity types
|
||||
|
||||
public const string AnyGuid = "any-guid"; // that one is for tests
|
||||
|
||||
public const string Document = "document";
|
||||
public const string Media = "media";
|
||||
public const string Member = "member";
|
||||
|
||||
public const string DictionaryItem = "dictionary-item";
|
||||
public const string Macro = "macro";
|
||||
public const string Template = "template";
|
||||
|
||||
public const string DocumentType = "document-type";
|
||||
public const string DocumentTypeContainer = "document-type-container";
|
||||
public const string MediaType = "media-type";
|
||||
public const string MediaTypeContainer = "media-type-container";
|
||||
public const string DataType = "data-type";
|
||||
public const string DataTypeContainer = "data-type-container";
|
||||
public const string MemberType = "member-type";
|
||||
public const string MemberGroup = "member-group";
|
||||
|
||||
public const string RelationType = "relation-type";
|
||||
|
||||
// string entity types
|
||||
|
||||
public const string AnyString = "any-string"; // that one is for tests
|
||||
|
||||
public const string MediaFile = "media-file";
|
||||
public const string TemplateFile = "template-file";
|
||||
public const string Script = "script";
|
||||
public const string Stylesheet = "stylesheet";
|
||||
public const string PartialView = "partial-view";
|
||||
public const string PartialViewMacro = "partial-view-macro";
|
||||
public const string Xslt = "xslt";
|
||||
|
||||
public static string FromUmbracoObjectType(UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
switch (umbracoObjectType)
|
||||
{
|
||||
case UmbracoObjectTypes.Document:
|
||||
return Document;
|
||||
case UmbracoObjectTypes.Media:
|
||||
return Media;
|
||||
case UmbracoObjectTypes.Member:
|
||||
return Member;
|
||||
case UmbracoObjectTypes.Template:
|
||||
return Template;
|
||||
case UmbracoObjectTypes.DocumentType:
|
||||
return DocumentType;
|
||||
case UmbracoObjectTypes.DocumentTypeContainer:
|
||||
return DocumentTypeContainer;
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
return MediaType;
|
||||
case UmbracoObjectTypes.MediaTypeContainer:
|
||||
return MediaTypeContainer;
|
||||
case UmbracoObjectTypes.DataType:
|
||||
return DataType;
|
||||
case UmbracoObjectTypes.DataTypeContainer:
|
||||
return DataTypeContainer;
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
return MemberType;
|
||||
case UmbracoObjectTypes.MemberGroup:
|
||||
return MemberGroup;
|
||||
case UmbracoObjectTypes.Stylesheet:
|
||||
return Stylesheet;
|
||||
case UmbracoObjectTypes.RelationType:
|
||||
return RelationType;
|
||||
}
|
||||
throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType));
|
||||
}
|
||||
|
||||
public static UmbracoObjectTypes ToUmbracoObjectType(string entityType)
|
||||
{
|
||||
switch (entityType)
|
||||
{
|
||||
case Document:
|
||||
return UmbracoObjectTypes.Document;
|
||||
case Media:
|
||||
return UmbracoObjectTypes.Media;
|
||||
case Member:
|
||||
return UmbracoObjectTypes.Member;
|
||||
case Template:
|
||||
return UmbracoObjectTypes.Template;
|
||||
case DocumentType:
|
||||
return UmbracoObjectTypes.DocumentType;
|
||||
case DocumentTypeContainer:
|
||||
return UmbracoObjectTypes.DocumentTypeContainer;
|
||||
case MediaType:
|
||||
return UmbracoObjectTypes.MediaType;
|
||||
case MediaTypeContainer:
|
||||
return UmbracoObjectTypes.MediaTypeContainer;
|
||||
case DataType:
|
||||
return UmbracoObjectTypes.DataType;
|
||||
case DataTypeContainer:
|
||||
return UmbracoObjectTypes.DataTypeContainer;
|
||||
case MemberType:
|
||||
return UmbracoObjectTypes.MemberType;
|
||||
case MemberGroup:
|
||||
return UmbracoObjectTypes.MemberGroup;
|
||||
case Stylesheet:
|
||||
return UmbracoObjectTypes.Stylesheet;
|
||||
case RelationType:
|
||||
return UmbracoObjectTypes.RelationType;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the valid selector values.
|
||||
/// </summary>
|
||||
public static class DeploySelector
|
||||
{
|
||||
public const string This = "this";
|
||||
public const string ThisAndChildren = "this-and-children";
|
||||
public const string ThisAndDescendants = "this-and-descendants";
|
||||
public const string ChildrenOfThis = "children";
|
||||
public const string DescendantsOfThis = "descendants";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,11 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public const string Document = "C66BA18E-EAF3-4CFF-8A22-41B16D66A972";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Document object.
|
||||
/// </summary>
|
||||
public static readonly Guid DocumentGuid = new Guid(Document);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Document Type object.
|
||||
/// </summary>
|
||||
@@ -147,6 +152,16 @@ namespace Umbraco.Core
|
||||
/// Guid for a Lock object.
|
||||
/// </summary>
|
||||
public static readonly Guid LockObjectGuid = new Guid(LockObject);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a relation type.
|
||||
/// </summary>
|
||||
public const string RelationType = "B1988FAD-8675-4F47-915A-B3A602BC5D8D";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a relation type.
|
||||
/// </summary>
|
||||
public static readonly Guid RelationTypeGuid = new Guid(RelationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class Security
|
||||
{
|
||||
|
||||
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
|
||||
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
|
||||
public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
|
||||
public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
|
||||
public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used for external identity providers for their authentication type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default we don't want to interfere with front-end external providers and their default setup, for back office the
|
||||
/// providers need to be setup differently and each auth type for the back office will be prefixed with this value
|
||||
/// </remarks>
|
||||
public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco.";
|
||||
|
||||
public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode";
|
||||
public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
|
||||
public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
|
||||
public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,15 +26,9 @@
|
||||
public const int DefaultMediaListViewDataTypeId = -96;
|
||||
public const int DefaultMembersListViewDataTypeId = -97;
|
||||
|
||||
// identifiers for lock objects
|
||||
public const int ServersLock = -331;
|
||||
}
|
||||
|
||||
public static class DatabaseProviders
|
||||
{
|
||||
public const string SqlCe = "System.Data.SqlServerCe.4.0";
|
||||
public const string SqlServer = "System.Data.SqlClient";
|
||||
public const string MySql = "MySql.Data.MySqlClient";
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoMigrationName = "Umbraco";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -29,30 +29,6 @@ namespace Umbraco.Core
|
||||
public const string AuthCookieName = "UMB_UCONTEXT";
|
||||
|
||||
}
|
||||
|
||||
public static class Security
|
||||
{
|
||||
|
||||
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
|
||||
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
|
||||
public const string BackOfficeExternalCookieName = "UMB_EXTLOGIN";
|
||||
public const string BackOfficeTokenAuthenticationType = "UmbracoBackOfficeToken";
|
||||
public const string BackOfficeTwoFactorAuthenticationType = "UmbracoTwoFactorCookie";
|
||||
|
||||
/// <summary>
|
||||
/// The prefix used for external identity providers for their authentication type
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default we don't want to interfere with front-end external providers and their default setup, for back office the
|
||||
/// providers need to be setup differently and each auth type for the back office will be prefixed with this value
|
||||
/// </remarks>
|
||||
public const string BackOfficeExternalAuthenticationTypePrefix = "Umbraco.";
|
||||
|
||||
public const string StartContentNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startcontentnode";
|
||||
public const string StartMediaNodeIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/startmedianode";
|
||||
public const string AllowedApplicationsClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/allowedapp";
|
||||
public const string SessionIdClaimType = "http://umbraco.org/2015/02/identity/claims/backoffice/sessionid";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -30,12 +31,13 @@ using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Core.Strings;
|
||||
using IntegerValidator = Umbraco.Core.PropertyEditors.IntegerValidator;
|
||||
using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application
|
||||
/// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not provide any startup functionality relating to web objects
|
||||
@@ -103,7 +105,7 @@ namespace Umbraco.Core
|
||||
LegacyParameterEditorAliasConverter.CreateMappingsForCoreEditors();
|
||||
|
||||
//create database and service contexts for the app context
|
||||
var dbFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, ProfilingLogger.Logger);
|
||||
var dbFactory = new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, ProfilingLogger.Logger);
|
||||
Database.Mapper = new PetaPocoMapper();
|
||||
|
||||
var dbContext = new DatabaseContext(
|
||||
@@ -191,14 +193,14 @@ namespace Umbraco.Core
|
||||
protected virtual CacheHelper CreateApplicationCache()
|
||||
{
|
||||
var cacheHelper = new CacheHelper(
|
||||
//we need to have the dep clone runtime cache provider to ensure
|
||||
//we need to have the dep clone runtime cache provider to ensure
|
||||
//all entities are cached properly (cloned in and cloned out)
|
||||
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
|
||||
new StaticCacheProvider(),
|
||||
//we have no request based cache when not running in web-based context
|
||||
new NullCacheProvider(),
|
||||
new IsolatedRuntimeCache(type =>
|
||||
//we need to have the dep clone runtime cache provider to ensure
|
||||
//we need to have the dep clone runtime cache provider to ensure
|
||||
//all entities are cached properly (cloned in and cloned out)
|
||||
new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
|
||||
|
||||
@@ -251,18 +253,18 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Special method to initialize the ApplicationEventsResolver and any modifications required for it such
|
||||
/// Special method to initialize the ApplicationEventsResolver and any modifications required for it such
|
||||
/// as adding custom types to the resolver.
|
||||
/// </summary>
|
||||
protected virtual void InitializeApplicationEventsResolver()
|
||||
{
|
||||
//find and initialize the application startup handlers, we need to initialize this resolver here because
|
||||
//it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to
|
||||
//it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to
|
||||
//events and to call their events during bootup.
|
||||
//ApplicationStartupHandler.RegisterHandlers();
|
||||
//... and set the special flag to let us resolve before frozen resolution
|
||||
ApplicationEventsResolver.Current = new ApplicationEventsResolver(
|
||||
ServiceProvider,
|
||||
ServiceProvider,
|
||||
ProfilingLogger.Logger,
|
||||
PluginManager.ResolveApplicationStartupHandlers())
|
||||
{
|
||||
@@ -282,7 +284,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fires after initialization and calls the callback to allow for customizations to occur &
|
||||
/// Fires after initialization and calls the callback to allow for customizations to occur &
|
||||
/// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called
|
||||
/// </summary>
|
||||
/// <param name="afterStartup"></param>
|
||||
@@ -334,7 +336,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (_isComplete)
|
||||
throw new InvalidOperationException("The boot manager has already been completed");
|
||||
|
||||
|
||||
FreezeResolution();
|
||||
|
||||
//Here we need to make sure the db can be connected to
|
||||
@@ -366,7 +368,7 @@ namespace Umbraco.Core
|
||||
ProfilingLogger.Logger.Error<CoreBootManager>("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex);
|
||||
throw;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Now, startup all of our legacy startup handler
|
||||
@@ -413,7 +415,7 @@ namespace Umbraco.Core
|
||||
|
||||
if (currentTry == 5)
|
||||
{
|
||||
throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database.");
|
||||
throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but Umbraco cannot connect to the database.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,6 +457,10 @@ namespace Umbraco.Core
|
||||
{
|
||||
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar());
|
||||
}
|
||||
else if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"]))
|
||||
{
|
||||
ServerRegistrarResolver.Current = new ServerRegistrarResolver(new SingleServerRegistrar());
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerRegistrarResolver.Current = new ServerRegistrarResolver(
|
||||
@@ -462,7 +468,6 @@ namespace Umbraco.Core
|
||||
new Lazy<IServerRegistrationService>(() => ApplicationContext.Services.ServerRegistrationService),
|
||||
new DatabaseServerRegistrarOptions()));
|
||||
}
|
||||
|
||||
|
||||
//by default we'll use the database server messenger with default options (no callbacks),
|
||||
// this will be overridden in the web startup
|
||||
@@ -473,7 +478,7 @@ namespace Umbraco.Core
|
||||
ServiceProvider, ProfilingLogger.Logger,
|
||||
() => PluginManager.ResolveAssignedMapperTypes());
|
||||
|
||||
|
||||
|
||||
//RepositoryResolver.Current = new RepositoryResolver(
|
||||
// new RepositoryFactory(ApplicationCache));
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Configuration;
|
||||
using System.Xml.Linq;
|
||||
using Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -30,17 +30,22 @@ namespace Umbraco.Core
|
||||
private readonly ILogger _logger;
|
||||
private readonly SqlSyntaxProviders _syntaxProviders;
|
||||
private bool _configured;
|
||||
private readonly object _locker = new object();
|
||||
private string _connectionString;
|
||||
private string _providerName;
|
||||
private DatabaseSchemaResult _result;
|
||||
private DateTime? _connectionLastChecked = null;
|
||||
|
||||
/// <summary>
|
||||
/// The number of minutes to throttle the checks to CanConnect
|
||||
/// </summary>
|
||||
private const int ConnectionCheckMinutes = 1;
|
||||
|
||||
[Obsolete("Use the constructor specifying all dependencies instead")]
|
||||
public DatabaseContext(IDatabaseFactory factory)
|
||||
: this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
|
||||
{
|
||||
new MySqlSyntaxProvider(LoggerResolver.Current.Logger),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlServerSyntaxProvider()
|
||||
}))
|
||||
{
|
||||
@@ -80,21 +85,60 @@ namespace Umbraco.Core
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
public List<UmbracoDatabase> Databases
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
return factory.Databases;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public ISqlSyntaxProvider SqlSyntax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Database"/> object for doing CRUD operations
|
||||
/// against custom tables that resides in the Umbraco database.
|
||||
/// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should not be used for CRUD operations or queries against the
|
||||
/// standard Umbraco tables! Use the Public services for that.
|
||||
/// <para>Should not be used for operation against standard Umbraco tables; as services should be used instead.</para>
|
||||
/// <para>Gets or creates an "ambient" database that is either stored in http context + available for the whole
|
||||
/// request + auto-disposed at the end of the request, or stored in call context if there is no http context - in which
|
||||
/// case it *must* be explicitely disposed (which will remove it from call context).</para>
|
||||
/// </remarks>
|
||||
public virtual UmbracoDatabase Database
|
||||
{
|
||||
get { return _factory.CreateDatabase(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the "ambient" database (if any) and by a new temp database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The returned IDisposable *must* be diposed in order to properly dispose the temp database and
|
||||
/// restore the original "ambient" database (if any).</para>
|
||||
/// <para>This is to be used in background tasks to ensure that they have an "ambient" database which
|
||||
/// will be properly removed from call context and does not interfere with anything else. In most case
|
||||
/// it is not replacing anything, just temporarily installing a database in context.</para>
|
||||
/// </remarks>
|
||||
public virtual IDisposable UseSafeDatabase(bool force = false)
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
|
||||
if (DefaultDatabaseFactory.HasAmbientDatabase)
|
||||
{
|
||||
return force
|
||||
? new UsingDatabase(DefaultDatabaseFactory.DetachAmbientDatabase(), factory.CreateDatabase())
|
||||
: new UsingDatabase(null, null);
|
||||
}
|
||||
|
||||
// create a new, temp, database (will be disposed with UsingDatabase)
|
||||
return new UsingDatabase(null, factory.CreateDatabase());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the database has been configured
|
||||
/// </summary>
|
||||
@@ -110,13 +154,28 @@ namespace Umbraco.Core
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDatabaseConfigured == false) return false;
|
||||
var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
|
||||
LogHelper.Info<DatabaseContext>("CanConnect = " + canConnect);
|
||||
return canConnect;
|
||||
if (IsDatabaseConfigured == false)
|
||||
return false;
|
||||
|
||||
//Don't check again if the timeout period hasn't elapsed
|
||||
//this ensures we don't keep checking the connection too many times in a row like during startup.
|
||||
//Do check if the _connectionLastChecked is null which means we're just initializing or it could
|
||||
//not connect last time it was checked.
|
||||
if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes)
|
||||
|| _connectionLastChecked.HasValue == false)
|
||||
{
|
||||
var canConnect = DbConnectionExtensions.IsConnectionAvailable(ConnectionString, DatabaseProvider);
|
||||
LogHelper.Info<DatabaseContext>("CanConnect = " + canConnect);
|
||||
|
||||
_connectionLastChecked = canConnect == false ? null : (DateTime?) DateTime.Now;
|
||||
return canConnect;
|
||||
}
|
||||
|
||||
return _connectionLastChecked.HasValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configured umbraco db connection string.
|
||||
/// </summary>
|
||||
@@ -136,14 +195,14 @@ namespace Umbraco.Core
|
||||
return _providerName;
|
||||
|
||||
_providerName = Constants.DatabaseProviders.SqlServer;
|
||||
if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null)
|
||||
if (ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName] != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName) == false)
|
||||
_providerName = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName;
|
||||
if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName) == false)
|
||||
_providerName = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Can't find a connection string with the name '" + GlobalSettings.UmbracoConnectionName + "'");
|
||||
throw new InvalidOperationException("Can't find a connection string with the name '" + Constants.System.UmbracoConnectionName + "'");
|
||||
}
|
||||
return _providerName;
|
||||
}
|
||||
@@ -174,7 +233,7 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public void ConfigureEmbeddedDatabaseConnection()
|
||||
{
|
||||
const string providerName = "System.Data.SqlServerCe.4.0";
|
||||
const string providerName = Constants.DatabaseProviders.SqlCe;
|
||||
|
||||
var connectionString = GetEmbeddedDatabaseConnectionString();
|
||||
SaveConnectionString(connectionString, providerName);
|
||||
@@ -182,15 +241,10 @@ namespace Umbraco.Core
|
||||
var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf");
|
||||
if (File.Exists(path) == false)
|
||||
{
|
||||
var engine = new SqlCeEngine(connectionString);
|
||||
engine.CreateDatabase();
|
||||
|
||||
// SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here
|
||||
// since it's been like this for quite some time
|
||||
//using (var engine = new SqlCeEngine(connectionString))
|
||||
//{
|
||||
// engine.CreateDatabase();
|
||||
//}
|
||||
using (var engine = new SqlCeEngine(connectionString))
|
||||
{
|
||||
engine.CreateDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
Initialize(providerName);
|
||||
@@ -230,14 +284,14 @@ namespace Umbraco.Core
|
||||
/// <param name="password">Database Password</param>
|
||||
/// <param name="databaseProvider">Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql)</param>
|
||||
public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider)
|
||||
{
|
||||
{
|
||||
string providerName;
|
||||
var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName);
|
||||
|
||||
SaveConnectionString(connectionString, providerName);
|
||||
Initialize(providerName);
|
||||
}
|
||||
|
||||
|
||||
public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName)
|
||||
{
|
||||
providerName = Constants.DatabaseProviders.SqlServer;
|
||||
@@ -268,18 +322,18 @@ namespace Umbraco.Core
|
||||
|
||||
public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName)
|
||||
{
|
||||
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
|
||||
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
|
||||
}
|
||||
|
||||
internal string BuildAzureConnectionString(string server, string databaseName, string user, string password)
|
||||
{
|
||||
if (server.Contains(".") && ServerStartsWithTcp(server) == false)
|
||||
server = string.Format("tcp:{0}", server);
|
||||
|
||||
|
||||
if (server.Contains(".") == false && ServerStartsWithTcp(server))
|
||||
{
|
||||
string serverName = server.Contains(",")
|
||||
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
|
||||
string serverName = server.Contains(",")
|
||||
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
|
||||
: server;
|
||||
|
||||
var portAddition = string.Empty;
|
||||
@@ -289,10 +343,10 @@ namespace Umbraco.Core
|
||||
|
||||
server = string.Format("{0}.database.windows.net{1}", serverName, portAddition);
|
||||
}
|
||||
|
||||
|
||||
if (ServerStartsWithTcp(server) == false)
|
||||
server = string.Format("tcp:{0}.database.windows.net", server);
|
||||
|
||||
|
||||
if (server.Contains(",") == false)
|
||||
server = string.Format("{0},1433", server);
|
||||
|
||||
@@ -329,23 +383,23 @@ namespace Umbraco.Core
|
||||
{
|
||||
//Set the connection string for the new datalayer
|
||||
var connectionStringSettings = string.IsNullOrEmpty(providerName)
|
||||
? new ConnectionStringSettings(GlobalSettings.UmbracoConnectionName,
|
||||
? new ConnectionStringSettings(Constants.System.UmbracoConnectionName,
|
||||
connectionString)
|
||||
: new ConnectionStringSettings(GlobalSettings.UmbracoConnectionName,
|
||||
: new ConnectionStringSettings(Constants.System.UmbracoConnectionName,
|
||||
connectionString, providerName);
|
||||
|
||||
_connectionString = connectionString;
|
||||
_providerName = providerName;
|
||||
|
||||
|
||||
var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root));
|
||||
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
|
||||
var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
|
||||
|
||||
// Update connectionString if it exists, or else create a new appSetting for the given key and value
|
||||
var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == GlobalSettings.UmbracoConnectionName);
|
||||
var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == Constants.System.UmbracoConnectionName);
|
||||
if (setting == null)
|
||||
connectionstrings.Add(new XElement("add",
|
||||
new XAttribute("name", GlobalSettings.UmbracoConnectionName),
|
||||
new XAttribute("name", Constants.System.UmbracoConnectionName),
|
||||
new XAttribute("connectionString", connectionStringSettings),
|
||||
new XAttribute("providerName", providerName)));
|
||||
else
|
||||
@@ -370,23 +424,23 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
internal void Initialize()
|
||||
{
|
||||
var databaseSettings = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName];
|
||||
var databaseSettings = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName];
|
||||
if (databaseSettings != null && string.IsNullOrWhiteSpace(databaseSettings.ConnectionString) == false && string.IsNullOrWhiteSpace(databaseSettings.ProviderName) == false)
|
||||
{
|
||||
var providerName = Constants.DatabaseProviders.SqlServer;
|
||||
string connString = null;
|
||||
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName))
|
||||
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName))
|
||||
{
|
||||
providerName = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName;
|
||||
connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString;
|
||||
providerName = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ProviderName;
|
||||
connString = ConfigurationManager.ConnectionStrings[Constants.System.UmbracoConnectionName].ConnectionString;
|
||||
}
|
||||
Initialize(providerName, connString);
|
||||
|
||||
|
||||
}
|
||||
else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false)
|
||||
else if (ConfigurationManager.AppSettings.ContainsKey(Constants.System.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName]) == false)
|
||||
{
|
||||
//A valid connectionstring does not exist, but the legacy appSettings key was found, so we'll reconfigure the conn.string.
|
||||
var legacyConnString = ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName];
|
||||
var legacyConnString = ConfigurationManager.AppSettings[Constants.System.UmbracoConnectionName];
|
||||
if (legacyConnString.ToLowerInvariant().Contains("sqlce4umbraco"))
|
||||
{
|
||||
ConfigureEmbeddedDatabaseConnection();
|
||||
@@ -417,8 +471,8 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
//Remove the legacy connection string, so we don't end up in a loop if something goes wrong.
|
||||
GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName);
|
||||
|
||||
GlobalSettings.RemoveSetting(Constants.System.UmbracoConnectionName);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -437,15 +491,15 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (_syntaxProviders != null)
|
||||
{
|
||||
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
|
||||
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
|
||||
}
|
||||
else if (SqlSyntax == null)
|
||||
{
|
||||
throw new InvalidOperationException("No " + typeof(ISqlSyntaxProvider) + " specified or no " + typeof(SqlSyntaxProviders) + " instance specified");
|
||||
}
|
||||
|
||||
|
||||
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
|
||||
|
||||
|
||||
_configured = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -485,7 +539,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
var readyForInstall = CheckReadyForInstall();
|
||||
@@ -503,7 +557,7 @@ namespace Umbraco.Core
|
||||
// If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks
|
||||
// for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive.
|
||||
|
||||
/*
|
||||
/*
|
||||
var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
if (supportsCaseInsensitiveQueries == false)
|
||||
{
|
||||
@@ -521,9 +575,9 @@ namespace Umbraco.Core
|
||||
message = GetResultMessageForMySql();
|
||||
|
||||
var schemaResult = ValidateDatabaseSchema();
|
||||
|
||||
|
||||
var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
|
||||
|
||||
|
||||
//If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing
|
||||
if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0)))
|
||||
{
|
||||
@@ -542,9 +596,9 @@ namespace Umbraco.Core
|
||||
message = "<p>Upgrading database, this may take some time...</p>";
|
||||
return new Result
|
||||
{
|
||||
RequiresUpgrade = true,
|
||||
Message = message,
|
||||
Success = true,
|
||||
RequiresUpgrade = true,
|
||||
Message = message,
|
||||
Success = true,
|
||||
Percentage = "30"
|
||||
};
|
||||
}
|
||||
@@ -572,7 +626,7 @@ namespace Umbraco.Core
|
||||
_logger.Info<DatabaseContext>("Database upgrade started");
|
||||
|
||||
var database = new UmbracoDatabase(_connectionString, ProviderName, _logger);
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
|
||||
var message = GetResultMessageForMySql();
|
||||
|
||||
@@ -580,32 +634,32 @@ namespace Umbraco.Core
|
||||
|
||||
var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion());
|
||||
|
||||
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
|
||||
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
|
||||
var targetVersion = UmbracoVersion.Current;
|
||||
|
||||
|
||||
//In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will
|
||||
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
// migrations are run for the target version
|
||||
if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0)))
|
||||
{
|
||||
//set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute
|
||||
installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision());
|
||||
}
|
||||
|
||||
|
||||
//Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum
|
||||
// version detected between the schema installed and the migrations listed in the migration table.
|
||||
// version detected between the schema installed and the migrations listed in the migration table.
|
||||
// If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what
|
||||
// is declared in the web.config.
|
||||
|
||||
|
||||
var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus)
|
||||
//Take the minimum version between the detected schema version and the installed migration version
|
||||
? new[] {installedSchemaVersion, installedMigrationVersion}.Min()
|
||||
//Take the minimum version between the installed migration version and the version specified in the config
|
||||
: new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min();
|
||||
|
||||
//Ok, another edge case here. If the current version is a pre-release,
|
||||
// then we want to ensure all migrations for the current release are executed.
|
||||
//Ok, another edge case here. If the current version is a pre-release,
|
||||
// then we want to ensure all migrations for the current release are executed.
|
||||
if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision());
|
||||
@@ -613,7 +667,7 @@ namespace Umbraco.Core
|
||||
|
||||
//DO the upgrade!
|
||||
|
||||
var runner = new MigrationRunner(migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.GetSemanticVersion(), GlobalSettings.UmbracoMigrationName);
|
||||
var runner = new MigrationRunner(migrationEntryService, _logger, currentInstalledVersion, UmbracoVersion.GetSemanticVersion(), Constants.System.UmbracoMigrationName);
|
||||
|
||||
var upgraded = runner.Execute(database, true);
|
||||
|
||||
@@ -740,7 +794,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
|
||||
var filePath = datasource.Replace("Data Source=", string.Empty);
|
||||
sqlCeDatabaseExists = File.Exists(filePath);
|
||||
sqlCeDatabaseExists = File.Exists(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,5 +808,28 @@ namespace Umbraco.Core
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class UsingDatabase : IDisposable
|
||||
{
|
||||
private readonly UmbracoDatabase _orig;
|
||||
private readonly UmbracoDatabase _temp;
|
||||
|
||||
public UsingDatabase(UmbracoDatabase orig, UmbracoDatabase temp)
|
||||
{
|
||||
_orig = orig;
|
||||
_temp = temp;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_temp != null)
|
||||
{
|
||||
_temp.Dispose();
|
||||
if (_orig != null)
|
||||
DefaultDatabaseFactory.AttachAmbientDatabase(_orig);
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class to all artifacts.
|
||||
/// </summary>
|
||||
public abstract class ArtifactBase<TUdi> : IArtifact
|
||||
where TUdi : Udi
|
||||
{
|
||||
protected ArtifactBase(TUdi udi, IEnumerable<ArtifactDependency> dependencies = null)
|
||||
{
|
||||
if (udi == null)
|
||||
throw new ArgumentNullException("udi");
|
||||
Udi = udi;
|
||||
|
||||
Dependencies = dependencies ?? Enumerable.Empty<ArtifactDependency>();
|
||||
_checksum = new Lazy<string>(GetChecksum);
|
||||
}
|
||||
|
||||
private readonly Lazy<string> _checksum;
|
||||
private IEnumerable<ArtifactDependency> _dependencies;
|
||||
|
||||
protected abstract string GetChecksum();
|
||||
|
||||
#region Abstract implementation of IArtifactSignature
|
||||
|
||||
Udi IArtifactSignature.Udi
|
||||
{
|
||||
get { return Udi; }
|
||||
}
|
||||
|
||||
public TUdi Udi { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Checksum
|
||||
{
|
||||
get { return _checksum.Value; }
|
||||
}
|
||||
|
||||
public IEnumerable<ArtifactDependency> Dependencies
|
||||
{
|
||||
get { return _dependencies; }
|
||||
set { _dependencies = value.OrderBy(x => x.Udi); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an artifact dependency.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Dependencies have an order property which indicates whether it must be respected when ordering artifacts.</para>
|
||||
/// <para>Dependencies have a mode which can be <c>Match</c> or <c>Exist</c> depending on whether the checksum should match.</para>
|
||||
/// </remarks>
|
||||
public class ArtifactDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ArtifactDependency class with an entity identifier and a mode.
|
||||
/// </summary>
|
||||
/// <param name="udi">The entity identifier of the artifact that is a dependency.</param>
|
||||
/// <param name="ordering">A value indicating whether the dependency is ordering.</param>
|
||||
/// <param name="mode">The dependency mode.</param>
|
||||
public ArtifactDependency(Udi udi, bool ordering, ArtifactDependencyMode mode)
|
||||
{
|
||||
Udi = udi;
|
||||
Ordering = ordering;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity id of the artifact that is a dependency.
|
||||
/// </summary>
|
||||
public Udi Udi { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the dependency is ordering.
|
||||
/// </summary>
|
||||
public bool Ordering { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dependency mode.
|
||||
/// </summary>
|
||||
public ArtifactDependencyMode Mode { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a collection of distinct <see cref="ArtifactDependency"/>.
|
||||
/// </summary>
|
||||
/// <remarks>The collection cannot contain duplicates and modes are properly managed.</remarks>
|
||||
public class ArtifactDependencyCollection : ICollection<ArtifactDependency>
|
||||
{
|
||||
private readonly Dictionary<Udi, ArtifactDependency> _dependencies
|
||||
= new Dictionary<Udi, ArtifactDependency>();
|
||||
|
||||
public IEnumerator<ArtifactDependency> GetEnumerator()
|
||||
{
|
||||
return _dependencies.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(ArtifactDependency item)
|
||||
{
|
||||
if (_dependencies.ContainsKey(item.Udi))
|
||||
{
|
||||
var exist = _dependencies[item.Udi];
|
||||
if (item.Mode == ArtifactDependencyMode.Exist || item.Mode == exist.Mode)
|
||||
return;
|
||||
}
|
||||
|
||||
_dependencies[item.Udi] = item;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_dependencies.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(ArtifactDependency item)
|
||||
{
|
||||
return _dependencies.ContainsKey(item.Udi) &&
|
||||
(_dependencies[item.Udi].Mode == item.Mode || _dependencies[item.Udi].Mode == ArtifactDependencyMode.Match);
|
||||
}
|
||||
|
||||
public void CopyTo(ArtifactDependency[] array, int arrayIndex)
|
||||
{
|
||||
_dependencies.Values.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(ArtifactDependency item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _dependencies.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the mode of the dependency.
|
||||
/// </summary>
|
||||
public enum ArtifactDependencyMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The dependency must match exactly.
|
||||
/// </summary>
|
||||
Match,
|
||||
|
||||
/// <summary>
|
||||
/// The dependency must exist.
|
||||
/// </summary>
|
||||
Exist
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent the state of an artifact being deployed.
|
||||
/// </summary>
|
||||
public abstract class ArtifactDeployState
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtifactDeployState"/> class from an artifact and an entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TArtifact">The type of the artifact.</typeparam>
|
||||
/// <typeparam name="TEntity">The type of the entity.</typeparam>
|
||||
/// <param name="art">The artifact.</param>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <param name="connector">The service connector deploying the artifact.</param>
|
||||
/// <param name="nextPass">The next pass number.</param>
|
||||
/// <returns>A deploying artifact.</returns>
|
||||
public static ArtifactDeployState<TArtifact, TEntity> Create<TArtifact, TEntity>(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass)
|
||||
where TArtifact : IArtifact
|
||||
{
|
||||
return new ArtifactDeployState<TArtifact, TEntity>(art, entity, connector, nextPass);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the artifact.
|
||||
/// </summary>
|
||||
public IArtifact Artifact
|
||||
{
|
||||
get { return GetArtifactAsIArtifact(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the artifact as an <see cref="IArtifact"/>.
|
||||
/// </summary>
|
||||
/// <returns>The artifact, as an <see cref="IArtifact"/>.</returns>
|
||||
/// <remarks>This is because classes that inherit from this class cannot override the Artifact property
|
||||
/// with a property that specializes the return type, and so they need to 'new' the property.</remarks>
|
||||
protected abstract IArtifact GetArtifactAsIArtifact();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service connector in charge of deploying the artifact.
|
||||
/// </summary>
|
||||
public IServiceConnector Connector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the next pass number.
|
||||
/// </summary>
|
||||
public int NextPass { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent the state of an artifact being deployed.
|
||||
/// </summary>
|
||||
/// <typeparam name="TArtifact">The type of the artifact.</typeparam>
|
||||
/// <typeparam name="TEntity">The type of the entity.</typeparam>
|
||||
public class ArtifactDeployState<TArtifact, TEntity> : ArtifactDeployState
|
||||
where TArtifact : IArtifact
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtifactDeployState{TArtifact,TEntity}"/> class.
|
||||
/// </summary>
|
||||
public ArtifactDeployState()
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArtifactDeployState{TArtifact,TEntity}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="art">The artifact.</param>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <param name="connector">The service connector deploying the artifact.</param>
|
||||
/// <param name="nextPass">The next pass number.</param>
|
||||
public ArtifactDeployState(TArtifact art, TEntity entity, IServiceConnector connector, int nextPass)
|
||||
{
|
||||
Artifact = art;
|
||||
Entity = entity;
|
||||
Connector = connector;
|
||||
NextPass = nextPass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the artifact.
|
||||
/// </summary>
|
||||
public new TArtifact Artifact { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the entity.
|
||||
/// </summary>
|
||||
public TEntity Entity { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected sealed override IArtifact GetArtifactAsIArtifact()
|
||||
{
|
||||
return Artifact;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
public sealed class ArtifactSignature : IArtifactSignature
|
||||
{
|
||||
public ArtifactSignature(Udi udi, string checksum, IEnumerable<ArtifactDependency> dependencies = null)
|
||||
{
|
||||
Udi = udi;
|
||||
Checksum = checksum;
|
||||
Dependencies = dependencies ?? Enumerable.Empty<ArtifactDependency>();
|
||||
}
|
||||
|
||||
public Udi Udi { get; private set; }
|
||||
|
||||
public string Checksum { get; private set; }
|
||||
|
||||
public IEnumerable<ArtifactDependency> Dependencies { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
public class Difference
|
||||
{
|
||||
public Difference(string title, string text = null, string category = null)
|
||||
{
|
||||
Title = title;
|
||||
Text = text;
|
||||
Category = category;
|
||||
}
|
||||
|
||||
public string Title { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Category { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var s = Title;
|
||||
if (!string.IsNullOrWhiteSpace(Category)) s += string.Format("[{0}]", Category);
|
||||
if (!string.IsNullOrWhiteSpace(Text))
|
||||
{
|
||||
if (s.Length > 0) s += ":";
|
||||
s += Text;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
public enum Direction
|
||||
{
|
||||
ToArtifact,
|
||||
FromArtifact
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an artifact ie an object that can be transfered between environments.
|
||||
/// </summary>
|
||||
public interface IArtifact : IArtifactSignature
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the signature of an artifact.
|
||||
/// </summary>
|
||||
public interface IArtifactSignature
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the entity unique identifier of this artifact.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The project identifier is independent from the state of the artifact, its data
|
||||
/// values, dependencies, anything. It never changes and fully identifies the artifact.</para>
|
||||
/// <para>What an entity uses as a unique identifier will influence what we can transfer
|
||||
/// between environments. Eg content type "Foo" on one environment is not necessarily the
|
||||
/// same as "Foo" on another environment, if guids are used as unique identifiers. What is
|
||||
/// used should be documented for each entity, along with the consequences of the choice.</para>
|
||||
/// </remarks>
|
||||
Udi Udi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the checksum of this artifact.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The checksum depends on the artifact's properties, and on the identifiers of all its dependencies,
|
||||
/// but not on their checksums. So the checksum changes when any of the artifact's properties changes,
|
||||
/// or when the list of dependencies changes. But not if one of these dependencies change.</para>
|
||||
/// <para>It is assumed that checksum collisions cannot happen ie that no two different artifact's
|
||||
/// states will ever produce the same checksum, so that if two artifacts have the same checksum then
|
||||
/// they are identical.</para>
|
||||
/// </remarks>
|
||||
string Checksum { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dependencies of this artifact.
|
||||
/// </summary>
|
||||
IEnumerable<ArtifactDependency> Dependencies { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a deployment context.
|
||||
/// </summary>
|
||||
public interface IDeployContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the deployment.
|
||||
/// </summary>
|
||||
Guid SessionId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file source.
|
||||
/// </summary>
|
||||
/// <remarks>The file source is used to obtain files from the source environment.</remarks>
|
||||
IFileSource FileSource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next number in a numerical sequence.
|
||||
/// </summary>
|
||||
/// <returns>The next sequence number.</returns>
|
||||
/// <remarks>Can be used to uniquely number things during a deployment.</remarks>
|
||||
int NextSeq();
|
||||
|
||||
/// <summary>
|
||||
/// Gets items.
|
||||
/// </summary>
|
||||
IDictionary<string, object> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the item.</typeparam>
|
||||
/// <param name="key">The key of the item.</param>
|
||||
/// <returns>The item with the specified key and type, if any, else null.</returns>
|
||||
T Item<T>(string key) where T : class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a file source, ie a mean for a target environment involved in a
|
||||
/// deployment to obtain the content of files being deployed.
|
||||
/// </summary>
|
||||
public interface IFileSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the content of a file as a stream.
|
||||
/// </summary>
|
||||
/// <param name="udi">A file entity identifier.</param>
|
||||
/// <returns>A stream with read access to the file content.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Returns null if no content could be read.</para>
|
||||
/// <para>The caller should ensure that the stream is properly closed/disposed.</para>
|
||||
/// </remarks>
|
||||
Stream GetFileStream(StringUdi udi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a file as a stream.
|
||||
/// </summary>
|
||||
/// <param name="udi">A file entity identifier.</param>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A stream with read access to the file content.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Returns null if no content could be read.</para>
|
||||
/// <para>The caller should ensure that the stream is properly closed/disposed.</para>
|
||||
/// </remarks>
|
||||
Task<Stream> GetFileStreamAsync(StringUdi udi, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a file as a string.
|
||||
/// </summary>
|
||||
/// <param name="udi">A file entity identifier.</param>
|
||||
/// <returns>A string containing the file content.</returns>
|
||||
/// <remarks>Returns null if no content could be read.</remarks>
|
||||
string GetFileContent(StringUdi udi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content of a file as a string.
|
||||
/// </summary>
|
||||
/// <param name="udi">A file entity identifier.</param>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A string containing the file content.</returns>
|
||||
/// <remarks>Returns null if no content could be read.</remarks>
|
||||
Task<string> GetFileContentAsync(StringUdi udi, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a file.
|
||||
/// </summary>
|
||||
/// <param name="udi">A file entity identifier.</param>
|
||||
/// <returns>The length of the file, or -1 if the file does not exist.</returns>
|
||||
long GetFileLength(StringUdi udi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of a file.
|
||||
/// </summary>
|
||||
/// <param name="udi">A file entity identifier.</param>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>The length of the file, or -1 if the file does not exist.</returns>
|
||||
Task<long> GetFileLengthAsync(StringUdi udi, CancellationToken token);
|
||||
|
||||
// fixme - doc
|
||||
void GetFiles(IEnumerable<StringUdi> udis, IFileStore fileStore);
|
||||
|
||||
Task GetFilesAsync(IEnumerable<StringUdi> udis, IFileStore fileStore, CancellationToken token);
|
||||
|
||||
///// <summary>
|
||||
///// Gets the content of a file as a bytes array.
|
||||
///// </summary>
|
||||
///// <param name="Udi">A file entity identifier.</param>
|
||||
///// <returns>A byte array containing the file content.</returns>
|
||||
//byte[] GetFileBytes(StringUdi Udi);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
public interface IFileStore
|
||||
{
|
||||
void SaveStream(StringUdi udi, Stream stream);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines methods that can convert a grid cell value to / from an environment-agnostic string.
|
||||
/// </summary>
|
||||
/// <remarks>Grid cell values may contain values such as content identifiers, that would be local
|
||||
/// to one environment, and need to be converted in order to be deployed.</remarks>
|
||||
public interface IGridCellValueConnector
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the connector supports a specified grid editor alias.
|
||||
/// </summary>
|
||||
/// <param name="alias">The grid editor alias.</param>
|
||||
/// <remarks>A value indicating whether the connector supports the grid editor alias.</remarks>
|
||||
/// <remarks>Note that <paramref name="alias" /> can be string.Empty to indicate the "default" connector.</remarks>
|
||||
bool IsConnector(string alias);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value to be deployed from the control value as a string.
|
||||
/// </summary>
|
||||
/// <param name="gridControl">The control containing the value.</param>
|
||||
/// <param name="property">The property where the control is located. Do not modify - only used for context</param>
|
||||
/// <param name="dependencies">The dependencies of the property.</param>
|
||||
/// <returns>The grid cell value to be deployed.</returns>
|
||||
/// <remarks>Note that </remarks>
|
||||
string GetValue(GridValue.GridControl gridControl, Property property, ICollection<ArtifactDependency> dependencies);
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to modify the value of a control being deployed.
|
||||
/// </summary>
|
||||
/// <param name="gridControl">The control being deployed.</param>
|
||||
/// <param name="property">The property where the <paramref name="gridControl"/> is located. Do not modify - only used for context.</param>
|
||||
/// <remarks>Follows the pattern of the property value connectors (<see cref="IValueConnector"/>). The SetValue method is used to modify the value of the <paramref name="gridControl"/>.</remarks>
|
||||
/// <remarks>Note that only the <paramref name="gridControl"/> value should be modified - not the <paramref name="property"/>.</remarks>
|
||||
/// <remarks>The <paramref name="property"/> should only be used to assist with context data relevant when setting the <paramref name="gridControl"/> value.</remarks>
|
||||
void SetValue(GridValue.GridControl gridControl, Property property);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to parse image tag sources in property values.
|
||||
/// </summary>
|
||||
public interface IImageSourceParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses an Umbraco property value and produces an artifact property value.
|
||||
/// </summary>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <param name="dependencies">A list of dependencies.</param>
|
||||
/// <returns>The parsed value.</returns>
|
||||
/// <remarks>Turns src="/media/..." into src="umb://media/..." and adds the corresponding udi to the dependencies.</remarks>
|
||||
string ToArtifact(string value, ICollection<Udi> dependencies);
|
||||
|
||||
/// <summary>
|
||||
/// Parses an artifact property value and produces an Umbraco property value.
|
||||
/// </summary>
|
||||
/// <param name="value">The artifact property value.</param>
|
||||
/// <returns>The parsed value.</returns>
|
||||
/// <remarks>Turns umb://media/... into /media/....</remarks>
|
||||
string FromArtifact(string value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to parse local link tags in property values.
|
||||
/// </summary>
|
||||
public interface ILocalLinkParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses an Umbraco property value and produces an artifact property value.
|
||||
/// </summary>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <param name="dependencies">A list of dependencies.</param>
|
||||
/// <returns>The parsed value.</returns>
|
||||
/// <remarks>Turns {{localLink:1234}} into {{localLink:umb://{type}/{id}}} and adds the corresponding udi to the dependencies.</remarks>
|
||||
string ToArtifact(string value, ICollection<Udi> dependencies);
|
||||
|
||||
/// <summary>
|
||||
/// Parses an artifact property value and produces an Umbraco property value.
|
||||
/// </summary>
|
||||
/// <param name="value">The artifact property value.</param>
|
||||
/// <returns>The parsed value.</returns>
|
||||
/// <remarks>Turns {{localLink:umb://{type}/{id}}} into {{localLink:1234}}.</remarks>
|
||||
string FromArtifact(string value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
public interface IMacroParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses an Umbraco property value and produces an artifact property value.
|
||||
/// </summary>
|
||||
/// <param name="value">Property value.</param>
|
||||
/// <param name="dependencies">A list of dependencies.</param>
|
||||
/// <returns>Parsed value.</returns>
|
||||
string ToArtifact(string value, ICollection<Udi> dependencies);
|
||||
|
||||
/// <summary>
|
||||
/// Parses an artifact property value and produces an Umbraco property value.
|
||||
/// </summary>
|
||||
/// <param name="value">Artifact property value.</param>
|
||||
/// <returns>Parsed value.</returns>
|
||||
string FromArtifact(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to replace the value of the attribute/parameter with a value containing a converted identifier.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to attempt to convert</param>
|
||||
/// <param name="editorAlias">Alias of the editor used for the parameter</param>
|
||||
/// <param name="dependencies">Collection to add dependencies to when performing ToArtifact</param>
|
||||
/// <param name="direction">Indicates which action is being performed (to or from artifact)</param>
|
||||
/// <returns>Value with converted identifiers</returns>
|
||||
string ReplaceAttributeValue(string value, string editorAlias, ICollection<Udi> dependencies, Direction direction);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines methods that can convert a preValue to / from an environment-agnostic string.
|
||||
/// </summary>
|
||||
/// <remarks>PreValues may contain values such as content identifiers, that would be local
|
||||
/// to one environment, and need to be converted in order to be deployed.</remarks>
|
||||
public interface IPreValueConnector
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the property editor aliases that the value converter supports by default.
|
||||
/// </summary>
|
||||
IEnumerable<string> PropertyEditorAliases { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the environment-agnostic preValues corresponding to environment-specific preValues.
|
||||
/// </summary>
|
||||
/// <param name="preValues">The environment-specific preValues.</param>
|
||||
/// <param name="dependencies">The dependencies.</param>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, string> ConvertToDeploy(IDictionary<string, string> preValues, ICollection<ArtifactDependency> dependencies);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the environment-specific preValues corresponding to environment-agnostic preValues.
|
||||
/// </summary>
|
||||
/// <param name="preValues">The environment-agnostic preValues.</param>
|
||||
/// <returns></returns>
|
||||
IDictionary<string, string> ConvertToLocalEnvironment(IDictionary<string, string> preValues);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
public interface IPrettyArtifact
|
||||
{
|
||||
string Name { get; }
|
||||
string Alias { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects to an Umbraco service.
|
||||
/// </summary>
|
||||
public interface IServiceConnector
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an artifact.
|
||||
/// </summary>
|
||||
/// <param name="udi">The entity identifier of the artifact.</param>
|
||||
/// <returns>The corresponding artifact, or null.</returns>
|
||||
IArtifact GetArtifact(Udi udi);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an artifact.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <returns>The corresponding artifact.</returns>
|
||||
IArtifact GetArtifact(object entity);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes processing for an artifact.
|
||||
/// </summary>
|
||||
/// <param name="art">The artifact.</param>
|
||||
/// <param name="context">The deploy context.</param>
|
||||
/// <returns>The mapped artifact.</returns>
|
||||
ArtifactDeployState ProcessInit(IArtifact art, IDeployContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Processes an artifact.
|
||||
/// </summary>
|
||||
/// <param name="dart">The mapped artifact.</param>
|
||||
/// <param name="context">The deploy context.</param>
|
||||
/// <param name="pass">The processing pass number.</param>
|
||||
void Process(ArtifactDeployState dart, IDeployContext context, int pass);
|
||||
|
||||
/// <summary>
|
||||
/// Explodes a range into udis.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="udis">The list of udis where to add the new udis.</param>
|
||||
/// <remarks>Also, it's cool to have a method named Explode. Kaboom!</remarks>
|
||||
void Explode(UdiRange range, List<Udi> udis);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a named range for a specified udi and selector.
|
||||
/// </summary>
|
||||
/// <param name="udi">The udi.</param>
|
||||
/// <param name="selector">The selector.</param>
|
||||
/// <returns>The named range for the specified udi and selector.</returns>
|
||||
NamedUdiRange GetRange(Udi udi, string selector);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a named range for specified entity type, identifier and selector.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity type.</param>
|
||||
/// <param name="sid">The identifier.</param>
|
||||
/// <param name="selector">The selector.</param>
|
||||
/// <returns>The named range for the specified entity type, identifier and selector.</returns>
|
||||
/// <remarks>
|
||||
/// <para>This is temporary. At least we thought it would be, in sept. 2016. What day is it now?</para>
|
||||
/// <para>At the moment our UI has a hard time returning proper udis, mainly because Core's tree do
|
||||
/// not manage guids but only ints... so we have to provide a way to support it. The string id here
|
||||
/// can be either a real string (for string udis) or an "integer as a string", using the value "-1" to
|
||||
/// indicate the "root" i.e. an open udi.</para>
|
||||
/// </remarks>
|
||||
NamedUdiRange GetRange(string entityType, string sid, string selector);
|
||||
|
||||
/// <summary>
|
||||
/// Compares two artifacts.
|
||||
/// </summary>
|
||||
/// <param name="art1">The first artifact.</param>
|
||||
/// <param name="art2">The second artifact.</param>
|
||||
/// <param name="differences">A collection of differences to append to, if not null.</param>
|
||||
/// <returns>A boolean value indicating whether the artifacts are identical.</returns>
|
||||
/// <remarks>ServiceConnectorBase{TArtifact} provides a very basic default implementation.</remarks>
|
||||
bool Compare(IArtifact art1, IArtifact art2, ICollection<Difference> differences = null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Deploy
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines methods that can convert a property value to / from an environment-agnostic string.
|
||||
/// </summary>
|
||||
/// <remarks>Property values may contain values such as content identifiers, that would be local
|
||||
/// to one environment, and need to be converted in order to be deployed. Connectors also deal
|
||||
/// with serializing to / from string.</remarks>
|
||||
public interface IValueConnector
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the property editor aliases that the value converter supports by default.
|
||||
/// </summary>
|
||||
IEnumerable<string> PropertyEditorAliases { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the deploy property corresponding to a content property.
|
||||
/// </summary>
|
||||
/// <param name="property">The content property.</param>
|
||||
/// <param name="dependencies">The content dependencies.</param>
|
||||
/// <returns>The deploy property value.</returns>
|
||||
string GetValue(Property property, ICollection<ArtifactDependency> dependencies);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a content property value using a deploy property.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="value">The deploy property value.</param>
|
||||
void SetValue(IContentBase content, string alias, string value);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
@@ -47,5 +48,35 @@ namespace Umbraco.Core.Events
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
// moves the last handler that was added to an instance event, to first position
|
||||
public static void PromoteLastHandler(object sender, string eventName)
|
||||
{
|
||||
var fieldInfo = sender.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
|
||||
PromoteLastHandler(sender, fieldInfo);
|
||||
}
|
||||
|
||||
// moves the last handler that was added to a static event, to first position
|
||||
public static void PromoteLastHandler<TSender>(string eventName)
|
||||
{
|
||||
var fieldInfo = typeof(TSender).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
|
||||
PromoteLastHandler(null, fieldInfo);
|
||||
}
|
||||
|
||||
private static void PromoteLastHandler(object sender, FieldInfo fieldInfo)
|
||||
{
|
||||
var d = fieldInfo.GetValue(sender) as Delegate;
|
||||
if (d == null) return;
|
||||
|
||||
var l = d.GetInvocationList();
|
||||
var x = l[l.Length - 1];
|
||||
for (var i = l.Length - 1; i > 0; i--)
|
||||
l[i] = l[i - 1];
|
||||
l[0] = x;
|
||||
|
||||
fieldInfo.SetValue(sender, Delegate.Combine(l));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,13 @@ namespace Umbraco.Core.Events
|
||||
[Obsolete("Use constructor accepting a product name instead.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion, bool canCancel)
|
||||
: this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, canCancel)
|
||||
: this(eventObject, null, configuredVersion, targetVersion, Constants.System.UmbracoMigrationName, canCancel)
|
||||
{ }
|
||||
|
||||
[Obsolete("Use constructor accepting SemVersion instances and a product name instead.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public MigrationEventArgs(IList<IMigration> eventObject, Version configuredVersion, Version targetVersion, bool canCancel)
|
||||
: this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, canCancel)
|
||||
: this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), Constants.System.UmbracoMigrationName, canCancel)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -74,7 +74,7 @@ namespace Umbraco.Core.Events
|
||||
MigrationContext = migrationContext;
|
||||
ConfiguredSemVersion = configuredVersion;
|
||||
TargetSemVersion = targetVersion;
|
||||
ProductName = GlobalSettings.UmbracoMigrationName;
|
||||
ProductName = Constants.System.UmbracoMigrationName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,13 +97,13 @@ namespace Umbraco.Core.Events
|
||||
[Obsolete("Use constructor accepting a product name instead.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public MigrationEventArgs(IList<IMigration> eventObject, SemVersion configuredVersion, SemVersion targetVersion)
|
||||
: this(eventObject, null, configuredVersion, targetVersion, GlobalSettings.UmbracoMigrationName, false)
|
||||
: this(eventObject, null, configuredVersion, targetVersion, Constants.System.UmbracoMigrationName, false)
|
||||
{ }
|
||||
|
||||
[Obsolete("Use constructor accepting SemVersion instances and a product name instead.")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public MigrationEventArgs(IList<IMigration> eventObject, Version configuredVersion, Version targetVersion)
|
||||
: this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), GlobalSettings.UmbracoMigrationName, false)
|
||||
: this(eventObject, null, new SemVersion(configuredVersion), new SemVersion(targetVersion), Constants.System.UmbracoMigrationName, false)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -128,8 +128,14 @@ namespace Umbraco.Core.Events
|
||||
get { return TargetSemVersion.GetVersion(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the origin version of the migration, i.e. the one that is currently installed.
|
||||
/// </summary>
|
||||
public SemVersion ConfiguredSemVersion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target version of the migration.
|
||||
/// </summary>
|
||||
public SemVersion TargetSemVersion { get; private set; }
|
||||
|
||||
public string ProductName { get; private set; }
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a guid-based entity identifier.
|
||||
/// </summary>
|
||||
public class GuidUdi : Udi
|
||||
{
|
||||
/// <summary>
|
||||
/// The guid part of the identifier.
|
||||
/// </summary>
|
||||
public Guid Guid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GuidUdi class with an entity type and a guid.
|
||||
/// </summary>
|
||||
/// <param name="entityType">The entity type part of the udi.</param>
|
||||
/// <param name="guid">The guid part of the udi.</param>
|
||||
public GuidUdi(string entityType, Guid guid)
|
||||
: base(entityType, "umb://" + entityType + "/" + guid.ToString("N"))
|
||||
{
|
||||
Guid = guid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GuidUdi class with an uri value.
|
||||
/// </summary>
|
||||
/// <param name="uriValue">The uri value of the udi.</param>
|
||||
public GuidUdi(Uri uriValue)
|
||||
: base(uriValue)
|
||||
{
|
||||
Guid = Guid.Parse(uriValue.AbsolutePath.TrimStart('/'));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the string representation of an entity identifier into the equivalent GuidUdi instance.
|
||||
/// </summary>
|
||||
/// <param name="s">The string to convert.</param>
|
||||
/// <returns>A GuidUdi instance that contains the value that was parsed.</returns>
|
||||
public new static GuidUdi Parse(string s)
|
||||
{
|
||||
var udi = Udi.Parse(s);
|
||||
if (!(udi is GuidUdi))
|
||||
throw new FormatException("String \"" + s + "\" is not a guid entity id.");
|
||||
return (GuidUdi)udi;
|
||||
}
|
||||
|
||||
public static bool TryParse(string s, out GuidUdi udi)
|
||||
{
|
||||
Udi tmp;
|
||||
udi = null;
|
||||
if (!TryParse(s, out tmp)) return false;
|
||||
udi = tmp as GuidUdi;
|
||||
return udi != null;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as GuidUdi;
|
||||
if (other == null) return false;
|
||||
return EntityType == other.EntityType && Guid == other.Guid;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsRoot
|
||||
{
|
||||
get { return Guid == Guid.Empty; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public GuidUdi EnsureClosed()
|
||||
{
|
||||
base.EnsureNotRoot();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public interface ICompletable : IDisposable
|
||||
{
|
||||
void Complete();
|
||||
}
|
||||
}
|
||||
@@ -37,15 +37,16 @@ namespace Umbraco.Core.IO
|
||||
throw new ArgumentException("Retries must be greater than zero");
|
||||
}
|
||||
|
||||
// GetSize has been added to IFileSystem2 but not IFileSystem
|
||||
public static long GetSize(this IFileSystem fs, string path)
|
||||
{
|
||||
var fs2 = fs as IFileSystem2;
|
||||
if (fs2 != null) return fs2.GetSize(path);
|
||||
|
||||
// this is implementing GetSize for IFileSystem, the old way
|
||||
using (var file = fs.OpenFile(path))
|
||||
{
|
||||
using (var sr = new StreamReader(file))
|
||||
{
|
||||
var str = sr.ReadToEnd();
|
||||
return str.Length;
|
||||
}
|
||||
return file.Length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Umbraco.Core.CodeAnnotations;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
@@ -13,8 +11,27 @@ namespace Umbraco.Core.IO
|
||||
public class FileSystemProviderManager
|
||||
{
|
||||
private readonly FileSystemProvidersSection _config;
|
||||
private readonly WeakSet<ShadowWrapper> _wrappers = new WeakSet<ShadowWrapper>();
|
||||
|
||||
#region Singleton
|
||||
// actual well-known filesystems returned by properties
|
||||
private readonly IFileSystem2 _macroPartialFileSystem;
|
||||
private readonly IFileSystem2 _partialViewsFileSystem;
|
||||
private readonly IFileSystem2 _stylesheetsFileSystem;
|
||||
private readonly IFileSystem2 _scriptsFileSystem;
|
||||
private readonly IFileSystem2 _xsltFileSystem;
|
||||
private readonly IFileSystem2 _masterPagesFileSystem;
|
||||
private readonly IFileSystem2 _mvcViewsFileSystem;
|
||||
|
||||
// when shadowing is enabled, above filesystems, as wrappers
|
||||
private readonly ShadowWrapper _macroPartialFileSystemWrapper;
|
||||
private readonly ShadowWrapper _partialViewsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _stylesheetsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _scriptsFileSystemWrapper;
|
||||
private readonly ShadowWrapper _xsltFileSystemWrapper;
|
||||
private readonly ShadowWrapper _masterPagesFileSystemWrapper;
|
||||
private readonly ShadowWrapper _mvcViewsFileSystemWrapper;
|
||||
|
||||
#region Singleton & Constructor
|
||||
|
||||
private static readonly FileSystemProviderManager Instance = new FileSystemProviderManager();
|
||||
|
||||
@@ -23,109 +40,224 @@ namespace Umbraco.Core.IO
|
||||
get { return Instance; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal FileSystemProviderManager()
|
||||
{
|
||||
_config = (FileSystemProvidersSection)ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||
|
||||
_macroPartialFileSystem = new PhysicalFileSystem(SystemDirectories.MacroPartials);
|
||||
_partialViewsFileSystem = new PhysicalFileSystem(SystemDirectories.PartialViews);
|
||||
_stylesheetsFileSystem = new PhysicalFileSystem(SystemDirectories.Css);
|
||||
_scriptsFileSystem = new PhysicalFileSystem(SystemDirectories.Scripts);
|
||||
_xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt);
|
||||
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||
|
||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials");
|
||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials");
|
||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css");
|
||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts");
|
||||
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt");
|
||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages");
|
||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views");
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
|
||||
/// </summary>
|
||||
private class ProviderConstructionInfo
|
||||
#region Well-Known FileSystems
|
||||
|
||||
public IFileSystem2 MacroPartialsFileSystem { get { return _macroPartialFileSystem; } }
|
||||
public IFileSystem2 PartialViewsFileSystem { get { return _partialViewsFileSystem; } }
|
||||
public IFileSystem2 StylesheetsFileSystem { get { return _stylesheetsFileSystem; } }
|
||||
public IFileSystem2 ScriptsFileSystem { get { return _scriptsFileSystem; } }
|
||||
public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } }
|
||||
public IFileSystem2 MasterPagesFileSystem { get { return _masterPagesFileSystem; } }
|
||||
public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } }
|
||||
public MediaFileSystem MediaFileSystem { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Providers
|
||||
|
||||
/// <summary>
|
||||
/// used to cache the lookup of how to construct this object so we don't have to reflect each time.
|
||||
/// </summary>
|
||||
private class ProviderConstructionInfo
|
||||
{
|
||||
public object[] Parameters { get; set; }
|
||||
public ConstructorInfo Constructor { get; set; }
|
||||
public string ProviderAlias { get; set; }
|
||||
//public string ProviderAlias { get; set; }
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
|
||||
private readonly ConcurrentDictionary<Type, string> _wrappedProviderLookup = new ConcurrentDictionary<Type, string>();
|
||||
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying (non-typed) file system provider for the alias specified
|
||||
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <param name="alias"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// It is recommended to use the typed GetFileSystemProvider method instead to get a strongly typed provider instance.
|
||||
/// </remarks>
|
||||
/// <param name="alias">The alias of the strongly-typed filesystem.</param>
|
||||
/// <returns>The non-typed filesystem supporting the strongly-typed filesystem with the specified alias.</returns>
|
||||
/// <remarks>This method should not be used directly, used <see cref="GetFileSystemProvider{TFileSystem}"/> instead.</remarks>
|
||||
public IFileSystem GetUnderlyingFileSystemProvider(string alias)
|
||||
{
|
||||
//either get the constructor info from cache or create it and add to cache
|
||||
// either get the constructor info from cache or create it and add to cache
|
||||
var ctorInfo = _providerLookup.GetOrAdd(alias, s =>
|
||||
{
|
||||
// get config
|
||||
var providerConfig = _config.Providers[s];
|
||||
if (providerConfig == null)
|
||||
throw new ArgumentException(string.Format("No provider found with the alias '{0}'", s));
|
||||
throw new ArgumentException(string.Format("No provider found with alias {0}.", s));
|
||||
|
||||
// get the filesystem type
|
||||
var providerType = Type.GetType(providerConfig.Type);
|
||||
if (providerType == null)
|
||||
throw new InvalidOperationException(string.Format("Could not find type '{0}'", providerConfig.Type));
|
||||
throw new InvalidOperationException(string.Format("Could not find type {0}.", providerConfig.Type));
|
||||
|
||||
// ensure it implements IFileSystem
|
||||
if (providerType.IsAssignableFrom(typeof (IFileSystem)))
|
||||
throw new InvalidOperationException(string.Format("The type '{0}' does not implement IFileSystem", providerConfig.Type));
|
||||
throw new InvalidOperationException(string.Format("Type {0} does not implement IFileSystem.", providerType.FullName));
|
||||
|
||||
// find a ctor matching the config parameters
|
||||
var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0;
|
||||
var constructor = providerType.GetConstructors()
|
||||
.SingleOrDefault(x => x.GetParameters().Count() == paramCount
|
||||
&& x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
||||
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException(string.Format("Could not find constructor for type '{0}' which accepts {1} parameters", providerConfig.Type, paramCount));
|
||||
throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount));
|
||||
|
||||
var parameters = new object[paramCount];
|
||||
for (var i = 0; i < paramCount; i++)
|
||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||
if (providerConfig.Parameters != null) // keeps ReSharper happy
|
||||
for (var i = 0; i < paramCount; i++)
|
||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||
|
||||
//return the new constructor info class to cache so we don't have to do this again.
|
||||
return new ProviderConstructionInfo()
|
||||
return new ProviderConstructionInfo
|
||||
{
|
||||
Constructor = constructor,
|
||||
Parameters = parameters,
|
||||
ProviderAlias = s
|
||||
//ProviderAlias = s
|
||||
};
|
||||
});
|
||||
|
||||
var fs = (IFileSystem)ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
|
||||
return fs;
|
||||
// create the fs and return
|
||||
return (IFileSystem) ctorInfo.Constructor.Invoke(ctorInfo.Parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the strongly typed file system provider
|
||||
/// Gets a strongly-typed filesystem.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProviderTypeFilter"></typeparam>
|
||||
/// <returns></returns>
|
||||
public TProviderTypeFilter GetFileSystemProvider<TProviderTypeFilter>()
|
||||
where TProviderTypeFilter : FileSystemWrapper
|
||||
/// <typeparam name="TFileSystem">The type of the filesystem.</typeparam>
|
||||
/// <returns>A strongly-typed filesystem of the specified type.</returns>
|
||||
public TFileSystem GetFileSystemProvider<TFileSystem>()
|
||||
where TFileSystem : FileSystemWrapper
|
||||
{
|
||||
//get the alias for the type from cache or look it up and add it to the cache, then we don't have to reflect each time
|
||||
var alias = _wrappedProviderLookup.GetOrAdd(typeof (TProviderTypeFilter), fsType =>
|
||||
// deal with known types - avoid infinite loops!
|
||||
if (typeof(TFileSystem) == typeof(MediaFileSystem) && MediaFileSystem != null)
|
||||
return MediaFileSystem as TFileSystem; // else create and return
|
||||
|
||||
// get/cache the alias for the filesystem type
|
||||
var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType =>
|
||||
{
|
||||
//validate the ctor
|
||||
var constructor = fsType.GetConstructors()
|
||||
.SingleOrDefault(x =>
|
||||
x.GetParameters().Count() == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||
// validate the ctor
|
||||
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException("The type of " + fsType + " must inherit from FileSystemWrapper and must have a constructor that accepts one parameter of type " + typeof(IFileSystem));
|
||||
|
||||
var attr =
|
||||
(FileSystemProviderAttribute)fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).
|
||||
SingleOrDefault();
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
|
||||
|
||||
// find the attribute and get the alias
|
||||
var attr = (FileSystemProviderAttribute) fsType.GetCustomAttributes(typeof(FileSystemProviderAttribute), false).SingleOrDefault();
|
||||
if (attr == null)
|
||||
throw new InvalidOperationException(string.Format("The provider type filter '{0}' is missing the required FileSystemProviderAttribute", typeof(FileSystemProviderAttribute).FullName));
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + "is missing the required FileSystemProviderAttribute.");
|
||||
|
||||
return attr.Alias;
|
||||
});
|
||||
|
||||
|
||||
// gets the inner fs, create the strongly-typed fs wrapping the inner fs, register & return
|
||||
// so we are double-wrapping here
|
||||
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
||||
var innerFs = GetUnderlyingFileSystemProvider(alias);
|
||||
var outputFs = Activator.CreateInstance(typeof (TProviderTypeFilter), innerFs);
|
||||
return (TProviderTypeFilter)outputFs;
|
||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias);
|
||||
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs);
|
||||
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
|
||||
return fs;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shadow
|
||||
|
||||
// note
|
||||
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
|
||||
// global shadow for the entire application, so great care should be taken to ensure that the
|
||||
// application is *not* doing anything else when using a shadow.
|
||||
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
|
||||
// be created directly (via ctor) or via GetFileSystemProvider<T> is *not* shadowed.
|
||||
|
||||
// shadow must be enabled in an app event handler before anything else ie before any filesystem
|
||||
// is actually created and used - after, it is too late - enabling shadow has a neglictible perfs
|
||||
// impact.
|
||||
// NO! by the time an app event handler is instanciated it is already too late, see note in ctor.
|
||||
//internal void EnableShadow()
|
||||
//{
|
||||
// if (_mvcViewsFileSystem != null) // test one of the fs...
|
||||
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
|
||||
// _shadowEnabled = true;
|
||||
//}
|
||||
|
||||
public ICompletable Shadow(Guid id)
|
||||
{
|
||||
var typed = _wrappers.ToArray();
|
||||
var wrappers = new ShadowWrapper[typed.Length + 7];
|
||||
var i = 0;
|
||||
while (i < typed.Length) wrappers[i] = typed[i++];
|
||||
wrappers[i++] = _macroPartialFileSystemWrapper;
|
||||
wrappers[i++] = _partialViewsFileSystemWrapper;
|
||||
wrappers[i++] = _stylesheetsFileSystemWrapper;
|
||||
wrappers[i++] = _scriptsFileSystemWrapper;
|
||||
wrappers[i++] = _xsltFileSystemWrapper;
|
||||
wrappers[i++] = _masterPagesFileSystemWrapper;
|
||||
wrappers[i] = _mvcViewsFileSystemWrapper;
|
||||
|
||||
return ShadowFileSystemsScope.CreateScope(id, wrappers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class WeakSet<T>
|
||||
where T : class
|
||||
{
|
||||
private readonly HashSet<WeakReference<T>> _set = new HashSet<WeakReference<T>>();
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
_set.Add(new WeakReference<T>(item));
|
||||
CollectLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
lock (_set)
|
||||
{
|
||||
CollectLocked();
|
||||
return _set.Select(x =>
|
||||
{
|
||||
T target;
|
||||
return x.TryGetTarget(out target) ? target : null;
|
||||
}).WhereNotNull().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectLocked()
|
||||
{
|
||||
_set.RemoveWhere(x =>
|
||||
{
|
||||
T target;
|
||||
return x.TryGetTarget(out target) == false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,93 +14,100 @@ namespace Umbraco.Core.IO
|
||||
///
|
||||
/// This abstract class just wraps the 'real' IFileSystem object passed in to its constructor.
|
||||
/// </remarks>
|
||||
public abstract class FileSystemWrapper : IFileSystem
|
||||
public abstract class FileSystemWrapper : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _wrapped;
|
||||
|
||||
protected FileSystemWrapper(IFileSystem wrapped)
|
||||
protected FileSystemWrapper(IFileSystem wrapped)
|
||||
{
|
||||
_wrapped = wrapped;
|
||||
Wrapped = wrapped;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
internal IFileSystem Wrapped { get; set; }
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
return _wrapped.GetDirectories(path);
|
||||
return Wrapped.GetDirectories(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
_wrapped.DeleteDirectory(path);
|
||||
Wrapped.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
_wrapped.DeleteDirectory(path, recursive);
|
||||
Wrapped.DeleteDirectory(path, recursive);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return _wrapped.DirectoryExists(path);
|
||||
return Wrapped.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
_wrapped.AddFile(path, stream);
|
||||
Wrapped.AddFile(path, stream);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
_wrapped.AddFile(path, stream, overrideIfExists);
|
||||
Wrapped.AddFile(path, stream, overrideExisting);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return _wrapped.GetFiles(path);
|
||||
return Wrapped.GetFiles(path);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return _wrapped.GetFiles(path, filter);
|
||||
return Wrapped.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
return _wrapped.OpenFile(path);
|
||||
return Wrapped.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
_wrapped.DeleteFile(path);
|
||||
Wrapped.DeleteFile(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return _wrapped.FileExists(path);
|
||||
return Wrapped.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return _wrapped.GetRelativePath(fullPathOrUrl);
|
||||
return Wrapped.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return _wrapped.GetFullPath(path);
|
||||
return Wrapped.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return _wrapped.GetUrl(path);
|
||||
return Wrapped.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return _wrapped.GetLastModified(path);
|
||||
return Wrapped.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return _wrapped.GetCreated(path);
|
||||
return Wrapped.GetCreated(path);
|
||||
}
|
||||
}
|
||||
|
||||
// explicitely implementing - not breaking
|
||||
long IFileSystem2.GetSize(string path)
|
||||
{
|
||||
var wrapped2 = Wrapped as IFileSystem2;
|
||||
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,6 @@ using System.IO;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
//TODO: There is no way to create a directory here without creating a file in a directory and then deleting it
|
||||
//TODO: Should probably implement a rename?
|
||||
|
||||
public interface IFileSystem
|
||||
{
|
||||
IEnumerable<string> GetDirectories(string path);
|
||||
@@ -16,7 +13,7 @@ namespace Umbraco.Core.IO
|
||||
void DeleteDirectory(string path, bool recursive);
|
||||
|
||||
bool DirectoryExists(string path);
|
||||
|
||||
|
||||
void AddFile(string path, Stream stream);
|
||||
|
||||
void AddFile(string path, Stream stream, bool overrideIfExists);
|
||||
@@ -31,7 +28,6 @@ namespace Umbraco.Core.IO
|
||||
|
||||
bool FileExists(string path);
|
||||
|
||||
|
||||
string GetRelativePath(string fullPathOrUrl);
|
||||
|
||||
string GetFullPath(string path);
|
||||
@@ -42,4 +38,17 @@ namespace Umbraco.Core.IO
|
||||
|
||||
DateTimeOffset GetCreated(string path);
|
||||
}
|
||||
|
||||
// this should be part of IFileSystem but we don't want to change the interface
|
||||
public interface IFileSystem2 : IFileSystem
|
||||
{
|
||||
long GetSize(string path);
|
||||
|
||||
// TODO: implement these
|
||||
//
|
||||
//void CreateDirectory(string path);
|
||||
//
|
||||
//// move or rename, directory or file
|
||||
//void Move(string source, string target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Media.Exif;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
@@ -14,59 +23,662 @@ namespace Umbraco.Core.IO
|
||||
public class MediaFileSystem : FileSystemWrapper
|
||||
{
|
||||
private readonly IContentSection _contentConfig;
|
||||
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped)
|
||||
: this(wrapped, UmbracoConfig.For.UmbracoSettings().Content)
|
||||
{
|
||||
}
|
||||
private readonly object _folderCounterLock = new object();
|
||||
private long _folderCounter;
|
||||
private bool _folderCounterInitialized;
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig) : base(wrapped)
|
||||
private static readonly Dictionary<int, string> DefaultSizes = new Dictionary<int, string>
|
||||
{
|
||||
{ 100, "thumb" },
|
||||
{ 500, "big-thumb" }
|
||||
};
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped)
|
||||
: this(wrapped, UmbracoConfig.For.UmbracoSettings().Content, ApplicationContext.Current.ProfilingLogger.Logger)
|
||||
{ }
|
||||
|
||||
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger)
|
||||
: base(wrapped)
|
||||
{
|
||||
_contentConfig = contentConfig;
|
||||
_uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig);
|
||||
}
|
||||
|
||||
public string GetRelativePath(int propertyId, string fileName)
|
||||
internal UploadAutoFillProperties UploadAutoFillProperties { get { return _uploadAutoFillProperties; } }
|
||||
|
||||
// note - this is currently experimental / being developed
|
||||
//public static bool UseTheNewMediaPathScheme { get; set; }
|
||||
public const bool UseTheNewMediaPathScheme = false;
|
||||
|
||||
// none of the methods below are used in Core anymore
|
||||
|
||||
[Obsolete("This low-level method should NOT exist.")]
|
||||
public string GetRelativePath(int propertyId, string fileName)
|
||||
{
|
||||
var seperator = _contentConfig.UploadAllowDirectories
|
||||
var sep = _contentConfig.UploadAllowDirectories
|
||||
? Path.DirectorySeparatorChar
|
||||
: '-';
|
||||
|
||||
return propertyId.ToString(CultureInfo.InvariantCulture) + seperator + fileName;
|
||||
return propertyId.ToString(CultureInfo.InvariantCulture) + sep + fileName;
|
||||
}
|
||||
|
||||
[Obsolete("This low-level method should NOT exist.", false)]
|
||||
public string GetRelativePath(string subfolder, string fileName)
|
||||
{
|
||||
var seperator = _contentConfig.UploadAllowDirectories
|
||||
var sep = _contentConfig.UploadAllowDirectories
|
||||
? Path.DirectorySeparatorChar
|
||||
: '-';
|
||||
|
||||
return subfolder + seperator + fileName;
|
||||
return subfolder + sep + fileName;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetThumbnails(string path)
|
||||
{
|
||||
var parentDirectory = Path.GetDirectoryName(path);
|
||||
var extension = Path.GetExtension(path);
|
||||
#region Media Path
|
||||
|
||||
return GetFiles(parentDirectory)
|
||||
.Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb"))
|
||||
.ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>With the old media path scheme, this CREATES a new media path each time it is invoked.</remarks>
|
||||
public string GetMediaPath(string filename, Guid cuid, Guid puid)
|
||||
{
|
||||
filename = Path.GetFileName(filename);
|
||||
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename");
|
||||
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
|
||||
|
||||
public void DeleteFile(string path, bool deleteThumbnails)
|
||||
{
|
||||
DeleteFile(path);
|
||||
string folder;
|
||||
if (UseTheNewMediaPathScheme == false)
|
||||
{
|
||||
// old scheme: filepath is "<int>/<filename>" OR "<int>-<filename>"
|
||||
// default media filesystem maps to "~/media/<filepath>"
|
||||
folder = GetNextFolder();
|
||||
}
|
||||
else
|
||||
{
|
||||
// new scheme: path is "<cuid>-<puid>/<filename>" OR "<cuid>-<puid>-<filename>"
|
||||
// default media filesystem maps to "~/media/<filepath>"
|
||||
// assumes that cuid and puid keys can be trusted - and that a single property type
|
||||
// for a single content cannot store two different files with the same name
|
||||
folder = Combine(cuid, puid).ToHexString(/*'/', 2, 4*/); // could use ext to fragment path eg 12/e4/f2/...
|
||||
}
|
||||
|
||||
if (deleteThumbnails == false)
|
||||
return;
|
||||
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(folder, filename)
|
||||
: folder + "-" + filename;
|
||||
|
||||
DeleteThumbnails(path);
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
public void DeleteThumbnails(string path)
|
||||
{
|
||||
GetThumbnails(path)
|
||||
.ForEach(DeleteFile);
|
||||
}
|
||||
}
|
||||
private static byte[] Combine(Guid guid1, Guid guid2)
|
||||
{
|
||||
var bytes1 = guid1.ToByteArray();
|
||||
var bytes2 = guid2.ToByteArray();
|
||||
var bytes = new byte[bytes1.Length];
|
||||
for (var i = 0; i < bytes1.Length; i++)
|
||||
bytes[i] = (byte)(bytes1[i] ^ bytes2[i]);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a media file.
|
||||
/// </summary>
|
||||
/// <param name="filename">The file name.</param>
|
||||
/// <param name="prevpath">A previous file path.</param>
|
||||
/// <param name="cuid">The unique identifier of the content/media owning the file.</param>
|
||||
/// <param name="puid">The unique identifier of the property type owning the file.</param>
|
||||
/// <returns>The filesystem-relative path to the media file.</returns>
|
||||
/// <remarks>In the old, legacy, number-based scheme, we try to re-use the media folder
|
||||
/// specified by <paramref name="prevpath"/>. Else, we CREATE a new one. Each time we are invoked.</remarks>
|
||||
public string GetMediaPath(string filename, string prevpath, Guid cuid, Guid puid)
|
||||
{
|
||||
if (UseTheNewMediaPathScheme || string.IsNullOrWhiteSpace(prevpath))
|
||||
return GetMediaPath(filename, cuid, puid);
|
||||
|
||||
filename = Path.GetFileName(filename);
|
||||
if (filename == null) throw new ArgumentException("Cannot become a safe filename.", "filename");
|
||||
filename = IOHelper.SafeFileName(filename.ToLowerInvariant());
|
||||
|
||||
// old scheme, with a previous path
|
||||
// prevpath should be "<int>/<filename>" OR "<int>-<filename>"
|
||||
// and we want to reuse the "<int>" part, so try to find it
|
||||
|
||||
var sep = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories ? "/" : "-";
|
||||
var pos = prevpath.IndexOf(sep, StringComparison.Ordinal);
|
||||
var s = pos > 0 ? prevpath.Substring(0, pos) : null;
|
||||
int ignored;
|
||||
|
||||
var folder = (pos > 0 && int.TryParse(s, out ignored)) ? s : GetNextFolder();
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
var filepath = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(folder, filename)
|
||||
: folder + "-" + filename;
|
||||
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next media folder in the original number-based scheme.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Should be private, is internal for legacy FileHandlerData which is obsolete.</remarks>
|
||||
internal string GetNextFolder()
|
||||
{
|
||||
EnsureFolderCounterIsInitialized();
|
||||
return Interlocked.Increment(ref _folderCounter).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void EnsureFolderCounterIsInitialized()
|
||||
{
|
||||
lock (_folderCounterLock)
|
||||
{
|
||||
if (_folderCounterInitialized) return;
|
||||
|
||||
_folderCounter = 1000; // seed
|
||||
var directories = GetDirectories("");
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
long folderNumber;
|
||||
if (long.TryParse(directory, out folderNumber) && folderNumber > _folderCounter)
|
||||
_folderCounter = folderNumber;
|
||||
}
|
||||
|
||||
// note: not multi-domains ie LB safe as another domain could create directories
|
||||
// while we read and parse them - don't fix, move to new scheme eventually
|
||||
|
||||
_folderCounterInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Associated Media Files
|
||||
|
||||
/// <summary>
|
||||
/// Stores a media file.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the media file.</param>
|
||||
/// <param name="filename">The media file name.</param>
|
||||
/// <param name="filestream">A stream containing the media bytes.</param>
|
||||
/// <param name="oldpath">An optional filesystem-relative filepath to the previous media file.</param>
|
||||
/// <returns>The filesystem-relative filepath to the media file.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The file is considered "owned" by the content/propertyType.</para>
|
||||
/// <para>If an <paramref name="oldpath"/> is provided then that file (and thumbnails) is deleted
|
||||
/// before the new file is saved, and depending on the media path scheme, the folder
|
||||
/// may be reused for the new file.</para>
|
||||
/// </remarks>
|
||||
public string StoreFile(IContentBase content, PropertyType propertyType, string filename, Stream filestream, string oldpath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyType == null) throw new ArgumentNullException("propertyType");
|
||||
if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Null or empty.", "filename");
|
||||
if (filestream == null) throw new ArgumentNullException("filestream");
|
||||
|
||||
// clear the old file, if any
|
||||
if (string.IsNullOrWhiteSpace(oldpath) == false)
|
||||
DeleteFile(oldpath, true);
|
||||
|
||||
// get the filepath, store the data
|
||||
// use oldpath as "prevpath" to try and reuse the folder, in original number-based scheme
|
||||
var filepath = GetMediaPath(filename, oldpath, content.Key, propertyType.Key);
|
||||
AddFile(filepath, filestream);
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a media file.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The filesystem-relative path to the media file.</param>
|
||||
public new void DeleteFile(string filepath)
|
||||
{
|
||||
DeleteFile(filepath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a media file.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item owning the copy of the media file.</param>
|
||||
/// <param name="propertyType">The property type owning the copy of the media file.</param>
|
||||
/// <param name="sourcepath">The filesystem-relative path to the source media file.</param>
|
||||
/// <returns>The filesystem-relative path to the copy of the media file.</returns>
|
||||
public string CopyFile(IContentBase content, PropertyType propertyType, string sourcepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyType == null) throw new ArgumentNullException("propertyType");
|
||||
if (string.IsNullOrWhiteSpace(sourcepath)) throw new ArgumentException("Null or empty.", "sourcepath");
|
||||
|
||||
// ensure we have a file to copy
|
||||
if (FileExists(sourcepath) == false) return null;
|
||||
|
||||
// get the filepath
|
||||
var filename = Path.GetFileName(sourcepath);
|
||||
var filepath = GetMediaPath(filename, content.Key, propertyType.Key);
|
||||
this.CopyFile(sourcepath, filepath);
|
||||
CopyThumbnails(sourcepath, filepath);
|
||||
return filepath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a property for a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <returns>The property.</returns>
|
||||
private static Property GetProperty(IContentBase content, string propertyTypeAlias)
|
||||
{
|
||||
var property = content.Properties.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
|
||||
if (property != null) return property;
|
||||
|
||||
var propertyType = content.GetContentType().CompositionPropertyTypes
|
||||
.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
|
||||
if (propertyType == null)
|
||||
throw new Exception("No property type exists with alias " + propertyTypeAlias + ".");
|
||||
|
||||
property = new Property(propertyType);
|
||||
content.Properties.Add(property);
|
||||
return property;
|
||||
}
|
||||
|
||||
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
|
||||
{
|
||||
var property = GetProperty(content, propertyTypeAlias);
|
||||
var svalue = property.Value as string;
|
||||
var oldpath = svalue == null ? null : GetRelativePath(svalue);
|
||||
var filepath = StoreFile(content, property.PropertyType, filename, filestream, oldpath);
|
||||
property.Value = GetUrl(filepath);
|
||||
SetUploadFile(content, property, filepath, filestream);
|
||||
}
|
||||
|
||||
public void SetUploadFile(IContentBase content, string propertyTypeAlias, string filepath)
|
||||
{
|
||||
var property = GetProperty(content, propertyTypeAlias);
|
||||
var svalue = property.Value as string;
|
||||
var oldpath = svalue == null ? null : GetRelativePath(svalue); // FIXME DELETE?
|
||||
if (string.IsNullOrWhiteSpace(oldpath) == false && oldpath != filepath)
|
||||
DeleteFile(oldpath, true);
|
||||
property.Value = GetUrl(filepath);
|
||||
using (var filestream = OpenFile(filepath))
|
||||
{
|
||||
SetUploadFile(content, property, filepath, filestream);
|
||||
}
|
||||
}
|
||||
|
||||
// sets a file for the FileUpload property editor
|
||||
// ie generates thumbnails and populates autofill properties
|
||||
private void SetUploadFile(IContentBase content, Property property, string filepath, Stream filestream)
|
||||
{
|
||||
// check if file is an image (and supports resizing and thumbnails etc)
|
||||
var extension = Path.GetExtension(filepath);
|
||||
var isImage = IsImageFile(extension);
|
||||
|
||||
// specific stuff for images (thumbnails etc)
|
||||
if (isImage)
|
||||
{
|
||||
using (var image = Image.FromStream(filestream))
|
||||
{
|
||||
// use one image for all
|
||||
GenerateThumbnails(image, filepath, property.PropertyType);
|
||||
_uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream, image);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// will use filepath for extension, and filestream for length
|
||||
_uploadAutoFillProperties.Populate(content, property.Alias, filepath, filestream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the file extension corresponds to an image.
|
||||
/// </summary>
|
||||
/// <param name="extension">The file extension.</param>
|
||||
/// <returns>A value indicating whether the file extension corresponds to an image.</returns>
|
||||
public bool IsImageFile(string extension)
|
||||
{
|
||||
if (extension == null) return false;
|
||||
extension = extension.TrimStart('.');
|
||||
return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dimensions of an image.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream containing the image bytes.</param>
|
||||
/// <returns>The dimension of the image.</returns>
|
||||
/// <remarks>First try with EXIF as it is faster and does not load the entire image
|
||||
/// in memory. Fallback to GDI which means loading the image in memory and thus
|
||||
/// use potentially large amounts of memory.</remarks>
|
||||
public Size GetDimensions(Stream stream)
|
||||
{
|
||||
//Try to load with exif
|
||||
try
|
||||
{
|
||||
var jpgInfo = ImageFile.FromStream(stream);
|
||||
|
||||
if (jpgInfo.Format != ImageFileFormat.Unknown
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
|
||||
{
|
||||
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
|
||||
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
|
||||
if (height > 0 && width > 0)
|
||||
{
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//We will just swallow, just means we can't read exif data, we don't want to log an error either
|
||||
}
|
||||
|
||||
//we have no choice but to try to read in via GDI
|
||||
using (var image = Image.FromStream(stream))
|
||||
{
|
||||
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
return new Size(fileWidth, fileHeight);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Manage thumbnails
|
||||
|
||||
// note: this does not find 'custom' thumbnails?
|
||||
// will find _thumb and _big-thumb but NOT _custom?
|
||||
public IEnumerable<string> GetThumbnails(string path)
|
||||
{
|
||||
var parentDirectory = Path.GetDirectoryName(path);
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
return GetFiles(parentDirectory)
|
||||
.Where(x => x.StartsWith(path.TrimEnd(extension) + "_thumb") || x.StartsWith(path.TrimEnd(extension) + "_big-thumb"))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public void DeleteFile(string path, bool deleteThumbnails)
|
||||
{
|
||||
base.DeleteFile(path);
|
||||
|
||||
if (deleteThumbnails == false)
|
||||
return;
|
||||
|
||||
DeleteThumbnails(path);
|
||||
}
|
||||
|
||||
public void DeleteThumbnails(string path)
|
||||
{
|
||||
GetThumbnails(path)
|
||||
.ForEach(x => base.DeleteFile(x));
|
||||
}
|
||||
|
||||
public void CopyThumbnails(string sourcePath, string targetPath)
|
||||
{
|
||||
var targetPathBase = Path.GetDirectoryName(targetPath) ?? "";
|
||||
foreach (var sourceThumbPath in GetThumbnails(sourcePath))
|
||||
{
|
||||
var sourceThumbFilename = Path.GetFileName(sourceThumbPath) ?? "";
|
||||
var targetThumbPath = Path.Combine(targetPathBase, sourceThumbFilename);
|
||||
this.CopyFile(sourceThumbPath, targetThumbPath);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateThumbnails
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Image image,
|
||||
string filepath,
|
||||
string preValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(preValue))
|
||||
return GenerateThumbnails(image, filepath);
|
||||
|
||||
var additionalSizes = new List<int>();
|
||||
var sep = preValue.Contains(",") ? "," : ";";
|
||||
var values = preValue.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var value in values)
|
||||
{
|
||||
int size;
|
||||
if (int.TryParse(value, out size))
|
||||
additionalSizes.Add(size);
|
||||
}
|
||||
|
||||
return GenerateThumbnails(image, filepath, additionalSizes);
|
||||
}
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Image image,
|
||||
string filepath,
|
||||
IEnumerable<int> additionalSizes = null)
|
||||
{
|
||||
var w = image.Width;
|
||||
var h = image.Height;
|
||||
|
||||
var sizes = additionalSizes == null ? DefaultSizes.Keys : DefaultSizes.Keys.Concat(additionalSizes);
|
||||
|
||||
// start with default sizes,
|
||||
// add additional sizes,
|
||||
// filter out duplicates,
|
||||
// filter out those that would be larger that the original image
|
||||
// and create the thumbnail
|
||||
return sizes
|
||||
.Distinct()
|
||||
.Where(x => w >= x && h >= x)
|
||||
.Select(x => GenerateResized(image, filepath, DefaultSizes.ContainsKey(x) ? DefaultSizes[x] : "", x))
|
||||
.ToList(); // now
|
||||
}
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Stream filestream,
|
||||
string filepath,
|
||||
PropertyType propertyType)
|
||||
{
|
||||
// get the original image from the original stream
|
||||
if (filestream.CanSeek) filestream.Seek(0, 0); // fixme - what if we cannot seek?
|
||||
using (var image = Image.FromStream(filestream))
|
||||
{
|
||||
return GenerateThumbnails(image, filepath, propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ResizedImage> GenerateThumbnails(
|
||||
Image image,
|
||||
string filepath,
|
||||
PropertyType propertyType)
|
||||
{
|
||||
// if the editor is an upload field, check for additional thumbnail sizes
|
||||
// that can be defined in the prevalue for the property data type. otherwise,
|
||||
// just use the default sizes.
|
||||
var sizes = propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias
|
||||
? ApplicationContext.Current.Services.DataTypeService
|
||||
.GetPreValuesByDataTypeId(propertyType.DataTypeDefinitionId)
|
||||
.FirstOrDefault()
|
||||
: string.Empty;
|
||||
|
||||
return GenerateThumbnails(image, filepath, sizes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateResized - Generate at resized filepath derived from origin filepath
|
||||
|
||||
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight)
|
||||
{
|
||||
return GenerateResized(originImage, originFilepath, sizeName, maxWidthHeight, -1, -1);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int fixedWidth, int fixedHeight)
|
||||
{
|
||||
return GenerateResized(originImage, originFilepath, sizeName, -1, fixedWidth, fixedHeight);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResized(Image originImage, string originFilepath, string sizeName, int maxWidthHeight, int fixedWidth, int fixedHeight)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sizeName))
|
||||
sizeName = "UMBRACOSYSTHUMBNAIL";
|
||||
var extension = Path.GetExtension(originFilepath) ?? string.Empty;
|
||||
var filebase = originFilepath.TrimEnd(extension);
|
||||
var resizedFilepath = filebase + "_" + sizeName + extension;
|
||||
|
||||
return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, fixedWidth, fixedHeight);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateResizedAt - Generate at specified resized filepath
|
||||
|
||||
public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight)
|
||||
{
|
||||
return GenerateResizedAt(originImage, resizedFilepath, maxWidthHeight, -1, -1);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResizedAt(Image originImage, int fixedWidth, int fixedHeight, string resizedFilepath)
|
||||
{
|
||||
return GenerateResizedAt(originImage, resizedFilepath, -1, fixedWidth, fixedHeight);
|
||||
}
|
||||
|
||||
public ResizedImage GenerateResizedAt(Image originImage, string resizedFilepath, int maxWidthHeight, int fixedWidth, int fixedHeight)
|
||||
{
|
||||
// target dimensions
|
||||
int width, height;
|
||||
|
||||
// if maxWidthHeight then get ratio
|
||||
if (maxWidthHeight > 0)
|
||||
{
|
||||
var fx = (float)originImage.Size.Width / maxWidthHeight;
|
||||
var fy = (float)originImage.Size.Height / maxWidthHeight;
|
||||
var f = Math.Max(fx, fy); // fit in thumbnail size
|
||||
width = (int)Math.Round(originImage.Size.Width / f);
|
||||
height = (int)Math.Round(originImage.Size.Height / f);
|
||||
if (width == 0) width = 1;
|
||||
if (height == 0) height = 1;
|
||||
}
|
||||
else if (fixedWidth > 0 && fixedHeight > 0)
|
||||
{
|
||||
width = fixedWidth;
|
||||
height = fixedHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = height = 1;
|
||||
}
|
||||
|
||||
// create new image with best quality settings
|
||||
using (var bitmap = new Bitmap(width, height))
|
||||
using (var graphics = Graphics.FromImage(bitmap))
|
||||
{
|
||||
// if the image size is rather large we cannot use the best quality interpolation mode
|
||||
// because we'll get out of mem exceptions. So we detect how big the image is and use
|
||||
// the mid quality interpolation mode when the image size exceeds our max limit.
|
||||
graphics.InterpolationMode = originImage.Width > 5000 || originImage.Height > 5000
|
||||
? InterpolationMode.Bilinear // mid quality
|
||||
: InterpolationMode.HighQualityBicubic; // best quality
|
||||
|
||||
// everything else is best-quality
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
|
||||
// copy the old image to the new and resize
|
||||
var rect = new Rectangle(0, 0, width, height);
|
||||
graphics.DrawImage(originImage, rect, 0, 0, originImage.Width, originImage.Height, GraphicsUnit.Pixel);
|
||||
|
||||
// copy metadata
|
||||
// fixme - er... no?
|
||||
|
||||
// get an encoder - based upon the file type
|
||||
var extension = (Path.GetExtension(resizedFilepath) ?? "").TrimStart('.').ToLowerInvariant();
|
||||
var encoders = ImageCodecInfo.GetImageEncoders();
|
||||
ImageCodecInfo encoder;
|
||||
switch (extension)
|
||||
{
|
||||
case "png":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/png"));
|
||||
break;
|
||||
case "gif":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/gif"));
|
||||
break;
|
||||
case "tif":
|
||||
case "tiff":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/tiff"));
|
||||
break;
|
||||
case "bmp":
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/bmp"));
|
||||
break;
|
||||
// TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the
|
||||
// place so left it here, but it needs to not set a codec if it doesn't know which one to pick
|
||||
// Note: when fixing this: both .jpg and .jpeg should be handled as extensions
|
||||
default:
|
||||
encoder = encoders.Single(t => t.MimeType.Equals("image/jpeg"));
|
||||
break;
|
||||
}
|
||||
|
||||
// set compresion ratio to 90%
|
||||
var encoderParams = new EncoderParameters();
|
||||
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
|
||||
|
||||
// save the new image
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(stream, encoder, encoderParams);
|
||||
stream.Seek(0, 0);
|
||||
if (resizedFilepath.Contains("UMBRACOSYSTHUMBNAIL"))
|
||||
{
|
||||
var filepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToInvariantString());
|
||||
AddFile(filepath, stream);
|
||||
if (extension != "jpg")
|
||||
{
|
||||
filepath = filepath.TrimEnd(extension) + "jpg";
|
||||
stream.Seek(0, 0);
|
||||
AddFile(filepath, stream);
|
||||
}
|
||||
// TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385
|
||||
stream.Seek(0, 0);
|
||||
resizedFilepath = resizedFilepath.Replace("UMBRACOSYSTHUMBNAIL", width + "x" + height);
|
||||
}
|
||||
|
||||
AddFile(resizedFilepath, stream);
|
||||
}
|
||||
|
||||
return new ResizedImage(resizedFilepath, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inner classes
|
||||
|
||||
public class ResizedImage
|
||||
{
|
||||
public ResizedImage()
|
||||
{ }
|
||||
|
||||
public ResizedImage(string filepath, int width, int height)
|
||||
{
|
||||
Filepath = filepath;
|
||||
Width = width;
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public string Filepath { get; set; }
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,29 +6,32 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
public class PhysicalFileSystem : IFileSystem
|
||||
public class PhysicalFileSystem : IFileSystem2
|
||||
{
|
||||
// the rooted, filesystem path, using directory separator chars, NOT ending with a separator
|
||||
// eg "c:" or "c:\path\to\site" or "\\server\path"
|
||||
private readonly string _rootPath;
|
||||
|
||||
// the ??? url, using url separator chars, NOT ending with a separator
|
||||
// eg "" (?) or "/Scripts" or ???
|
||||
// _rootPath, but with separators replaced by forward-slashes
|
||||
// eg "c:" or "c:/path/to/site" or "//server/path"
|
||||
// (is used in GetRelativePath)
|
||||
private readonly string _rootPathFwd;
|
||||
|
||||
// the relative url, using url separator chars, NOT ending with a separator
|
||||
// eg "" or "/Views" or "/Media" or "/<vpath>/Media" in case of a virtual path
|
||||
private readonly string _rootUrl;
|
||||
|
||||
// virtualRoot should be "~/path/to/root" eg "~/Views"
|
||||
// the "~/" is mandatory.
|
||||
public PhysicalFileSystem(string virtualRoot)
|
||||
{
|
||||
if (virtualRoot == null) throw new ArgumentNullException("virtualRoot");
|
||||
if (virtualRoot.StartsWith("~/") == false)
|
||||
throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'");
|
||||
|
||||
_rootPath = IOHelper.MapPath(virtualRoot);
|
||||
_rootPath = EnsureDirectorySeparatorChar(_rootPath);
|
||||
_rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
|
||||
_rootUrl = IOHelper.ResolveUrl(virtualRoot);
|
||||
_rootUrl = EnsureUrlSeparatorChar(_rootUrl);
|
||||
_rootUrl = _rootUrl.TrimEnd('/');
|
||||
_rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
|
||||
_rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/');
|
||||
}
|
||||
|
||||
public PhysicalFileSystem(string rootPath, string rootUrl)
|
||||
@@ -43,22 +46,24 @@ namespace Umbraco.Core.IO
|
||||
throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'");
|
||||
|
||||
// rootPath should be... rooted, as in, it's a root path!
|
||||
// but the test suite App.config cannot really "root" anything so we'll have to do it here
|
||||
|
||||
//var localRoot = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var localRoot = IOHelper.GetRootDirectorySafe();
|
||||
if (Path.IsPathRooted(rootPath) == false)
|
||||
{
|
||||
// but the test suite App.config cannot really "root" anything so we have to do it here
|
||||
var localRoot = IOHelper.GetRootDirectorySafe();
|
||||
rootPath = Path.Combine(localRoot, rootPath);
|
||||
}
|
||||
|
||||
rootPath = EnsureDirectorySeparatorChar(rootPath);
|
||||
rootUrl = EnsureUrlSeparatorChar(rootUrl);
|
||||
|
||||
_rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootUrl = rootUrl.TrimEnd('/');
|
||||
_rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
|
||||
_rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets directories in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the directory.</param>
|
||||
/// <returns>The filesystem-relative path to the directories in the directory.</returns>
|
||||
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -80,11 +85,20 @@ namespace Umbraco.Core.IO
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
DeleteDirectory(path, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <param name="recursive">A value indicating whether to recursively delete sub-directories.</param>
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -101,38 +115,71 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a directory exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <returns>A value indicating whether a directory exists.</returns>
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
return Directory.Exists(fullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the file.</param>
|
||||
/// <param name="stream">A stream containing the file data.</param>
|
||||
/// <remarks>Overrides the existing file, if any.</remarks>
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
AddFile(path, stream, true);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
/// <summary>
|
||||
/// Saves a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the file.</param>
|
||||
/// <param name="stream">A stream containing the file data.</param>
|
||||
/// <param name="overrideExisting">A value indicating whether to override the existing file, if any.</param>
|
||||
/// <remarks>If a file exists and <paramref name="overrideExisting"/> is false, an exception is thrown.</remarks>
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
var exists = File.Exists(fullPath);
|
||||
if (exists && overrideIfExists == false)
|
||||
if (exists && overrideExisting == false)
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (directory == null) throw new InvalidOperationException("Could not get directory.");
|
||||
Directory.CreateDirectory(directory); // ensure it exists
|
||||
|
||||
if (stream.CanSeek)
|
||||
if (stream.CanSeek) // fixme - what else?
|
||||
stream.Seek(0, 0);
|
||||
|
||||
using (var destination = (Stream)File.Create(fullPath))
|
||||
using (var destination = (Stream) File.Create(fullPath))
|
||||
stream.CopyTo(destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets files in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <returns>The filesystem-relative path to the files in the directory.</returns>
|
||||
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return GetFiles(path, "*.*");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets files in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path of the directory.</param>
|
||||
/// <param name="filter">A filter.</param>
|
||||
/// <returns>The filesystem-relative path to the matching files in the directory.</returns>
|
||||
/// <remarks>Filesystem-relative paths use forward-slashes as directory separators.</remarks>
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -154,12 +201,21 @@ namespace Umbraco.Core.IO
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
/// <returns></returns>
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
return File.OpenRead(fullPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
@@ -176,52 +232,53 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a file exists.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
/// <returns>A value indicating whether the file exists.</returns>
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
var fullpath = GetFullPath(path);
|
||||
return File.Exists(fullpath);
|
||||
}
|
||||
|
||||
// beware, many things depend on how the GetRelative/AbsolutePath methods work!
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative path.
|
||||
/// Gets the filesystem-relative path of a full path or of an url.
|
||||
/// </summary>
|
||||
/// <param name="fullPathOrUrl">The full path or url.</param>
|
||||
/// <returns>The path, relative to this filesystem's root.</returns>
|
||||
/// <remarks>
|
||||
/// <para>The relative path is relative to this filesystem's root, not starting with any
|
||||
/// directory separator. If input was recognized as a url (path), then output uses url (path) separator
|
||||
/// chars.</para>
|
||||
/// directory separator. All separators are forward-slashes.</para>
|
||||
/// </remarks>
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
// test url
|
||||
var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char
|
||||
|
||||
if (IOHelper.PathStartsWith(path, _rootUrl, '/')) // if it starts with the root url...
|
||||
return path.Substring(_rootUrl.Length) // strip it
|
||||
.TrimStart('/'); // it's relative
|
||||
// if it starts with the root url, strip it and trim the starting slash to make it relative
|
||||
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
|
||||
if (IOHelper.PathStartsWith(path, _rootUrl, '/'))
|
||||
return path.Substring(_rootUrl.Length).TrimStart('/');
|
||||
|
||||
// test path
|
||||
path = EnsureDirectorySeparatorChar(fullPathOrUrl);
|
||||
// if it starts with the root path, strip it and trim the starting slash to make it relative
|
||||
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
|
||||
if (IOHelper.PathStartsWith(path, _rootPathFwd, '/'))
|
||||
return path.Substring(_rootPathFwd.Length).TrimStart('/');
|
||||
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) // if it starts with the root path
|
||||
return path.Substring(_rootPath.Length) // strip it
|
||||
.TrimStart(Path.DirectorySeparatorChar); // it's relative
|
||||
|
||||
// unchanged - including separators
|
||||
return fullPathOrUrl;
|
||||
// unchanged - what else?
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path.
|
||||
/// </summary>
|
||||
/// <param name="path">The full or relative path.</param>
|
||||
/// <param name="path">The full or filesystem-relative path.</param>
|
||||
/// <returns>The full path.</returns>
|
||||
/// <remarks>
|
||||
/// <para>On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this
|
||||
/// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar.</para>
|
||||
/// filesystem's root) path. All separators are Path.DirectorySeparatorChar.</para>
|
||||
/// </remarks>
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
@@ -229,49 +286,82 @@ namespace Umbraco.Core.IO
|
||||
var opath = path;
|
||||
path = EnsureDirectorySeparatorChar(path);
|
||||
|
||||
// fixme - this part should go!
|
||||
// not sure what we are doing here - so if input starts with a (back) slash,
|
||||
// we assume it's not a FS relative path and we try to convert it... but it
|
||||
// really makes little sense?
|
||||
if (path.StartsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
path = GetRelativePath(path);
|
||||
|
||||
// if already a full path, return
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
||||
return path;
|
||||
// if not already rooted, combine with the root path
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar) == false)
|
||||
path = Path.Combine(_rootPath, path);
|
||||
|
||||
// else combine and sanitize, ie GetFullPath will take care of any relative
|
||||
// sanitize - GetFullPath will take care of any relative
|
||||
// segments in path, eg '../../foo.tmp' - it may throw a SecurityException
|
||||
// if the combined path reaches illegal parts of the filesystem
|
||||
var fpath = Path.Combine(_rootPath, path);
|
||||
fpath = Path.GetFullPath(fpath);
|
||||
path = Path.GetFullPath(path);
|
||||
|
||||
// at that point, path is within legal parts of the filesystem, ie we have
|
||||
// permissions to reach that path, but it may nevertheless be outside of
|
||||
// our root path, due to relative segments, so better check
|
||||
if (IOHelper.PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar))
|
||||
return fpath;
|
||||
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
||||
return path;
|
||||
|
||||
// nothing prevents us to reach the file, security-wise, yet it is outside
|
||||
// this filesystem's root - throw
|
||||
throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the url.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path.</param>
|
||||
/// <returns>The url.</returns>
|
||||
/// <remarks>All separators are forward-slashes.</remarks>
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
path = EnsureUrlSeparatorChar(path).Trim('/');
|
||||
return _rootUrl + "/" + path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last-modified date of a directory or file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the directory or the file.</param>
|
||||
/// <returns>The last modified date of the directory or the file.</returns>
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return DirectoryExists(path)
|
||||
? new DirectoryInfo(GetFullPath(path)).LastWriteTimeUtc
|
||||
: new FileInfo(GetFullPath(path)).LastWriteTimeUtc;
|
||||
var fullpath = GetFullPath(path);
|
||||
return DirectoryExists(fullpath)
|
||||
? new DirectoryInfo(fullpath).LastWriteTimeUtc
|
||||
: new FileInfo(fullpath).LastWriteTimeUtc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the created date of a directory or file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the directory or the file.</param>
|
||||
/// <returns>The created date of the directory or the file.</returns>
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return DirectoryExists(path)
|
||||
? Directory.GetCreationTimeUtc(GetFullPath(path))
|
||||
: File.GetCreationTimeUtc(GetFullPath(path));
|
||||
var fullpath = GetFullPath(path);
|
||||
return DirectoryExists(fullpath)
|
||||
? Directory.GetCreationTimeUtc(fullpath)
|
||||
: File.GetCreationTimeUtc(fullpath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The filesystem-relative path to the file.</param>
|
||||
/// <returns>The file of the size, in bytes.</returns>
|
||||
/// <remarks>If the file does not exist, returns -1.</remarks>
|
||||
public long GetSize(string path)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
var file = new FileInfo(fullPath);
|
||||
return file.Exists ? file.Length : -1;
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ResizedImage
|
||||
{
|
||||
public ResizedImage()
|
||||
{
|
||||
}
|
||||
|
||||
public ResizedImage(int width, int height, string fileName)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
FileName = fileName;
|
||||
}
|
||||
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public string FileName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowFileSystem : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _fs;
|
||||
private readonly IFileSystem2 _sfs;
|
||||
|
||||
public ShadowFileSystem(IFileSystem fs, IFileSystem2 sfs)
|
||||
{
|
||||
_fs = fs;
|
||||
_sfs = sfs;
|
||||
}
|
||||
|
||||
public IFileSystem Inner
|
||||
{
|
||||
get { return _fs; }
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (_nodes == null) return;
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var kvp in _nodes)
|
||||
{
|
||||
if (kvp.Value.IsExist)
|
||||
{
|
||||
if (kvp.Value.IsFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = _sfs.OpenFile(kvp.Key))
|
||||
_fs.AddFile(kvp.Key, stream, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptions.Add(new Exception("Could not save file \"" + kvp.Key + "\".", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (kvp.Value.IsDir)
|
||||
_fs.DeleteDirectory(kvp.Key, true);
|
||||
else
|
||||
_fs.DeleteFile(kvp.Key);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
exceptions.Add(new Exception("Could not delete " + (kvp.Value.IsDir ? "directory": "file") + " \"" + kvp.Key + "\".", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
_nodes.Clear();
|
||||
|
||||
if (exceptions.Count == 0) return;
|
||||
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
}
|
||||
|
||||
private Dictionary<string, ShadowNode> _nodes;
|
||||
|
||||
private Dictionary<string, ShadowNode> Nodes { get { return _nodes ?? (_nodes = new Dictionary<string, ShadowNode>()); } }
|
||||
|
||||
private class ShadowNode
|
||||
{
|
||||
public ShadowNode(bool isDelete, bool isdir)
|
||||
{
|
||||
IsDelete = isDelete;
|
||||
IsDir = isdir;
|
||||
}
|
||||
|
||||
public bool IsDelete { get; private set; }
|
||||
public bool IsDir { get; private set; }
|
||||
|
||||
public bool IsExist { get { return IsDelete == false; } }
|
||||
public bool IsFile { get { return IsDir == false; } }
|
||||
}
|
||||
|
||||
private static string NormPath(string path)
|
||||
{
|
||||
return path.ToLowerInvariant().Replace("\\", "/");
|
||||
}
|
||||
|
||||
// values can be "" (root), "foo", "foo/bar"...
|
||||
private static bool IsChild(string path, string input)
|
||||
{
|
||||
if (input.StartsWith(path) == false || input.Length < path.Length + 2)
|
||||
return false;
|
||||
if (path.Length > 0 && input[path.Length] != '/') return false;
|
||||
var pos = input.IndexOf("/", path.Length + 1, StringComparison.OrdinalIgnoreCase);
|
||||
return pos < 0;
|
||||
}
|
||||
|
||||
private static bool IsDescendant(string path, string input)
|
||||
{
|
||||
if (input.StartsWith(path) == false || input.Length < path.Length + 2)
|
||||
return false;
|
||||
return path.Length == 0 || input[path.Length] == '/';
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
var normPath = NormPath(path);
|
||||
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
|
||||
var directories = _fs.GetDirectories(path);
|
||||
return directories
|
||||
.Except(shadows.Where(kvp => (kvp.Value.IsDir && kvp.Value.IsDelete) || (kvp.Value.IsFile && kvp.Value.IsExist))
|
||||
.Select(kvp => kvp.Key))
|
||||
.Union(shadows.Where(kvp => kvp.Value.IsDir && kvp.Value.IsExist).Select(kvp => kvp.Key))
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
DeleteDirectory(path, false);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
if (DirectoryExists(path) == false) return;
|
||||
var normPath = NormPath(path);
|
||||
if (recursive)
|
||||
{
|
||||
Nodes[normPath] = new ShadowNode(true, true);
|
||||
var remove = Nodes.Where(x => IsDescendant(normPath, x.Key)).ToList();
|
||||
foreach (var kvp in remove) Nodes.Remove(kvp.Key);
|
||||
Delete(path, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Nodes.Any(x => IsChild(normPath, x.Key) && x.Value.IsExist) // shadow content
|
||||
|| _fs.GetDirectories(path).Any() || _fs.GetFiles(path).Any()) // actual content
|
||||
throw new InvalidOperationException("Directory is not empty.");
|
||||
Nodes[path] = new ShadowNode(true, true);
|
||||
var remove = Nodes.Where(x => IsChild(normPath, x.Key)).ToList();
|
||||
foreach (var kvp in remove) Nodes.Remove(kvp.Key);
|
||||
Delete(path, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Delete(string path, bool recurse)
|
||||
{
|
||||
foreach (var file in _fs.GetFiles(path))
|
||||
{
|
||||
Nodes[NormPath(file)] = new ShadowNode(true, false);
|
||||
}
|
||||
foreach (var dir in _fs.GetDirectories(path))
|
||||
{
|
||||
Nodes[NormPath(dir)] = new ShadowNode(true, true);
|
||||
if (recurse) Delete(dir, true);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf))
|
||||
return sf.IsDir && sf.IsExist;
|
||||
return _fs.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
AddFile(path, stream, true);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
{
|
||||
ShadowNode sf;
|
||||
var normPath = NormPath(path);
|
||||
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
var parts = normPath.Split('/');
|
||||
for (var i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
var dirPath = string.Join("/", parts.Take(i + 1));
|
||||
ShadowNode sd;
|
||||
if (Nodes.TryGetValue(dirPath, out sd))
|
||||
{
|
||||
if (sd.IsFile) throw new InvalidOperationException("Invalid path.");
|
||||
if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_fs.DirectoryExists(dirPath)) continue;
|
||||
if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path.");
|
||||
Nodes[dirPath] = new ShadowNode(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
_sfs.AddFile(path, stream, overrideIfExists);
|
||||
Nodes[normPath] = new ShadowNode(false, false);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
var normPath = NormPath(path);
|
||||
var shadows = Nodes.Where(kvp => IsChild(normPath, kvp.Key)).ToArray();
|
||||
var files = _fs.GetFiles(path);
|
||||
return files
|
||||
.Except(shadows.Where(kvp => (kvp.Value.IsFile && kvp.Value.IsDelete) || kvp.Value.IsDir)
|
||||
.Select(kvp => kvp.Key))
|
||||
.Union(shadows.Where(kvp => kvp.Value.IsFile && kvp.Value.IsExist).Select(kvp => kvp.Key))
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return _fs.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf))
|
||||
return sf.IsDir || sf.IsDelete ? null : _sfs.OpenFile(path);
|
||||
return _fs.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
if (FileExists(path) == false) return;
|
||||
Nodes[NormPath(path)] = new ShadowNode(true, false);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf))
|
||||
return sf.IsFile && sf.IsExist;
|
||||
return _fs.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return _fs.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return _fs.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return _fs.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetLastModified(path);
|
||||
if (sf.IsDelete) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false) return _fs.GetCreated(path);
|
||||
if (sf.IsDelete) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetCreated(path);
|
||||
}
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
ShadowNode sf;
|
||||
if (Nodes.TryGetValue(NormPath(path), out sf) == false)
|
||||
{
|
||||
// the inner filesystem (_fs) can be IFileSystem2... or just IFileSystem
|
||||
// figure it out and use the most effective GetSize method
|
||||
var fs2 = _fs as IFileSystem2;
|
||||
return fs2 == null ? _fs.GetSize(path) : fs2.GetSize(path);
|
||||
}
|
||||
if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowFileSystemsScope : ICompletable
|
||||
{
|
||||
// note: taking a reference to the _manager instead of using manager.Current
|
||||
// to avoid using Current everywhere but really, we support only 1 scope at
|
||||
// a time, not multiple scopes in case of multiple managers (not supported)
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystemsScope";
|
||||
private static readonly object Locker = new object();
|
||||
private readonly Guid _id;
|
||||
private readonly ShadowWrapper[] _wrappers;
|
||||
|
||||
static ShadowFileSystemsScope()
|
||||
{
|
||||
SafeCallContext.Register(
|
||||
() =>
|
||||
{
|
||||
var scope = CallContext.LogicalGetData(ItemKey);
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
return scope;
|
||||
},
|
||||
o =>
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException();
|
||||
if (o != null) CallContext.LogicalSetData(ItemKey, o);
|
||||
});
|
||||
}
|
||||
|
||||
private ShadowFileSystemsScope(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("Shadow " + id + ".");
|
||||
_id = id;
|
||||
_wrappers = wrappers;
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.Shadow(id);
|
||||
}
|
||||
|
||||
// internal for tests + FileSystemProviderManager
|
||||
// do NOT use otherwise
|
||||
internal static ShadowFileSystemsScope CreateScope(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
|
||||
CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
|
||||
}
|
||||
return new ShadowFileSystemsScope(id, wrappers);
|
||||
}
|
||||
|
||||
internal static bool InScope
|
||||
{
|
||||
get { return NoScope == false; }
|
||||
}
|
||||
|
||||
internal static bool NoScope
|
||||
{
|
||||
get { return CallContext.LogicalGetData(ItemKey) == null; }
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (complete).");
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var wrapper in _wrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
wrapper.UnShadow(true);
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
exceptions.Add(ae);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
|
||||
// last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can)
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (abort)");
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.UnShadow(false); // should not throw
|
||||
}
|
||||
finally
|
||||
{
|
||||
// last, & always
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowWrapper : IFileSystem2
|
||||
{
|
||||
private readonly IFileSystem _innerFileSystem;
|
||||
private readonly string _shadowPath;
|
||||
private ShadowFileSystem _shadowFileSystem;
|
||||
private string _shadowDir;
|
||||
|
||||
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath)
|
||||
{
|
||||
_innerFileSystem = innerFileSystem;
|
||||
_shadowPath = shadowPath;
|
||||
}
|
||||
|
||||
internal void Shadow(Guid id)
|
||||
{
|
||||
// note: no thread-safety here, because ShadowFs is thread-safe due to the check
|
||||
// on ShadowFileSystemsScope.None - and if None is false then we should be running
|
||||
// in a single thread anyways
|
||||
|
||||
var virt = "~/App_Data/Shadow/" + id + "/" + _shadowPath;
|
||||
_shadowDir = IOHelper.MapPath(virt);
|
||||
Directory.CreateDirectory(_shadowDir);
|
||||
var tempfs = new PhysicalFileSystem(virt);
|
||||
_shadowFileSystem = new ShadowFileSystem(_innerFileSystem, tempfs);
|
||||
}
|
||||
|
||||
internal void UnShadow(bool complete)
|
||||
{
|
||||
var shadowFileSystem = _shadowFileSystem;
|
||||
var dir = _shadowDir;
|
||||
_shadowFileSystem = null;
|
||||
_shadowDir = null;
|
||||
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
if (complete) shadowFileSystem.Complete();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// in any case, cleanup
|
||||
try
|
||||
{
|
||||
Directory.Delete(dir, true);
|
||||
dir = dir.Substring(0, dir.Length - _shadowPath.Length - 1);
|
||||
if (Directory.EnumerateFileSystemEntries(dir).Any() == false)
|
||||
Directory.Delete(dir, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ugly, isn't it? but if we cannot cleanup, bah, just leave it there
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IFileSystem FileSystem
|
||||
{
|
||||
get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
{
|
||||
return FileSystem.GetDirectories(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path);
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path, bool recursive)
|
||||
{
|
||||
FileSystem.DeleteDirectory(path, recursive);
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
return FileSystem.DirectoryExists(path);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream)
|
||||
{
|
||||
FileSystem.AddFile(path, stream);
|
||||
}
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideExisting)
|
||||
{
|
||||
FileSystem.AddFile(path, stream, overrideExisting);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path)
|
||||
{
|
||||
return FileSystem.GetFiles(path);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetFiles(string path, string filter)
|
||||
{
|
||||
return FileSystem.GetFiles(path, filter);
|
||||
}
|
||||
|
||||
public Stream OpenFile(string path)
|
||||
{
|
||||
return FileSystem.OpenFile(path);
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
FileSystem.DeleteFile(path);
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return FileSystem.FileExists(path);
|
||||
}
|
||||
|
||||
public string GetRelativePath(string fullPathOrUrl)
|
||||
{
|
||||
return FileSystem.GetRelativePath(fullPathOrUrl);
|
||||
}
|
||||
|
||||
public string GetFullPath(string path)
|
||||
{
|
||||
return FileSystem.GetFullPath(path);
|
||||
}
|
||||
|
||||
public string GetUrl(string path)
|
||||
{
|
||||
return FileSystem.GetUrl(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetLastModified(string path)
|
||||
{
|
||||
return FileSystem.GetLastModified(path);
|
||||
}
|
||||
|
||||
public DateTimeOffset GetCreated(string path)
|
||||
{
|
||||
return FileSystem.GetCreated(path);
|
||||
}
|
||||
|
||||
public long GetSize(string path)
|
||||
{
|
||||
var filesystem = FileSystem; // will be either a ShadowFileSystem OR the actual underlying IFileSystem
|
||||
|
||||
// and the underlying filesystem can be IFileSystem2... or just IFileSystem
|
||||
// figure it out and use the most effective GetSize method
|
||||
var filesystem2 = filesystem as IFileSystem2;
|
||||
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,23 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string PartialViews
|
||||
{
|
||||
get
|
||||
{
|
||||
return MvcViews + "/Partials/";
|
||||
}
|
||||
}
|
||||
|
||||
public static string MacroPartials
|
||||
{
|
||||
get
|
||||
{
|
||||
return MvcViews + "/MacroPartials/";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static string Media
|
||||
{
|
||||
get
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Media;
|
||||
|
||||
@@ -86,15 +80,15 @@ namespace Umbraco.Core.IO
|
||||
private void Initialize()
|
||||
{
|
||||
Filename = _fs.GetFileName(Path);
|
||||
Extension = _fs.GetExtension(Path) != null
|
||||
? _fs.GetExtension(Path).Substring(1).ToLowerInvariant()
|
||||
var ext = _fs.GetExtension(Path);
|
||||
Extension = string.IsNullOrEmpty(ext) == false
|
||||
? ext.TrimStart('.').ToLowerInvariant()
|
||||
: "";
|
||||
Url = _fs.GetUrl(Path);
|
||||
|
||||
Exists = _fs.FileExists(Path);
|
||||
if (Exists == false)
|
||||
{
|
||||
LogHelper.Warn<UmbracoMediaFile>("The media file doesn't exist: " + Path);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Exists { get; private set; }
|
||||
@@ -117,27 +111,15 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_length == null)
|
||||
{
|
||||
if (Exists)
|
||||
{
|
||||
_length = _fs.GetSize(Path);
|
||||
}
|
||||
else
|
||||
{
|
||||
_length = -1;
|
||||
}
|
||||
}
|
||||
if (_length.HasValue) return _length.Value;
|
||||
_length = Exists ? _fs.GetSize(Path) : -1;
|
||||
return _length.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsResizing
|
||||
{
|
||||
get
|
||||
{
|
||||
return UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(Extension);
|
||||
}
|
||||
get { return _fs.IsImageFile(Extension); }
|
||||
}
|
||||
|
||||
public string GetFriendlyName()
|
||||
@@ -147,67 +129,49 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public Size GetDimensions()
|
||||
{
|
||||
if (_size == null)
|
||||
{
|
||||
if (_fs.FileExists(Path))
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (_size.HasValue) return _size.Value;
|
||||
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
{
|
||||
_size = ImageHelper.GetDimensions(fs);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (_fs.FileExists(Path))
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
{
|
||||
_size = new Size(-1, -1);
|
||||
_size = _fs.GetDimensions(fs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_size = new Size(-1, -1);
|
||||
}
|
||||
|
||||
return _size.Value;
|
||||
}
|
||||
|
||||
public string Resize(int width, int height)
|
||||
{
|
||||
if (Exists)
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (Exists == false) return string.Empty;
|
||||
|
||||
var fileNameThumb = DoResize(width, height, -1, string.Empty);
|
||||
|
||||
return _fs.GetUrl(fileNameThumb);
|
||||
}
|
||||
return string.Empty;
|
||||
EnsureFileSupportsResizing();
|
||||
var filepath = Resize(width, height, -1, string.Empty);
|
||||
return _fs.GetUrl(filepath);
|
||||
}
|
||||
|
||||
public string Resize(int maxWidthHeight, string fileNameAddition)
|
||||
{
|
||||
if (Exists)
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (Exists == false) return string.Empty;
|
||||
|
||||
var fileNameThumb = DoResize(-1, -1, maxWidthHeight, fileNameAddition);
|
||||
|
||||
return _fs.GetUrl(fileNameThumb);
|
||||
}
|
||||
return string.Empty;
|
||||
EnsureFileSupportsResizing();
|
||||
var filepath = Resize(-1, -1, maxWidthHeight, fileNameAddition);
|
||||
return _fs.GetUrl(filepath);
|
||||
}
|
||||
|
||||
private string DoResize(int width, int height, int maxWidthHeight, string fileNameAddition)
|
||||
private string Resize(int width, int height, int maxWidthHeight, string sizeName)
|
||||
{
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
using (var filestream = _fs.OpenFile(Path))
|
||||
using (var image = Image.FromStream(filestream))
|
||||
{
|
||||
using (var image = Image.FromStream(fs))
|
||||
{
|
||||
var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
|
||||
? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)))
|
||||
: string.Format("{0}_{1}." + Extension, Path.Substring(0, Path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
|
||||
|
||||
var thumbnail = maxWidthHeight == -1
|
||||
? ImageHelper.GenerateThumbnail(image, width, height, fileNameThumb, Extension, _fs)
|
||||
: ImageHelper.GenerateThumbnail(image, maxWidthHeight, fileNameThumb, Extension, _fs);
|
||||
|
||||
return thumbnail.FileName;
|
||||
}
|
||||
return _fs.GenerateResized(image, Path, sizeName, maxWidthHeight, width, height).Filepath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +180,5 @@ namespace Umbraco.Core.IO
|
||||
if (SupportsResizing == false)
|
||||
throw new InvalidOperationException(string.Format("The file {0} is not an image, so can't get dimensions", Filename));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
internal class DebugDiagnosticsLogger : ILogger
|
||||
/// <summary>
|
||||
/// Implements <see cref="ILogger"/> on top of System.Diagnostics.Debug.
|
||||
/// </summary>
|
||||
/// <remarks>Useful for tests.</remarks>
|
||||
public class DebugDiagnosticsLogger : ILogger
|
||||
{
|
||||
public void Error(Type callingType, string message, Exception exception)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ImageProcessor.Common.Exceptions;
|
||||
|
||||
using ImageProcessor.Common.Exceptions;
|
||||
namespace Umbraco.Core.Logging
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A logger for explicitly logging ImageProcessor exceptions.
|
||||
|
||||
+60
-22
@@ -1,18 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
// represents the main domain
|
||||
class MainDom : IRegisteredObject
|
||||
/// <summary>
|
||||
/// Represents the main AppDomain running for a given application.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>There can be only one "main" AppDomain running for a given application at a time.</para>
|
||||
/// <para>When an AppDomain starts, it tries to acquire the main domain status.</para>
|
||||
/// <para>When an AppDomain stops (eg the application is restarting) it should release the main domain status.</para>
|
||||
/// <para>It is possible to register against the MainDom and be notified when it is released.</para>
|
||||
/// </remarks>
|
||||
internal class MainDom : IRegisteredObject
|
||||
{
|
||||
#region Vars
|
||||
|
||||
@@ -25,7 +28,7 @@ namespace Umbraco.Core
|
||||
private readonly AsyncLock _asyncLock;
|
||||
private IDisposable _asyncLocker;
|
||||
|
||||
// event wait handle used to notify current main domain that it should
|
||||
// event wait handle used to notify current main domain that it should
|
||||
// release the lock because a new domain wants to be the main domain
|
||||
private readonly EventWaitHandle _signal;
|
||||
|
||||
@@ -34,16 +37,26 @@ namespace Umbraco.Core
|
||||
private volatile bool _signaled; // we have been signaled
|
||||
|
||||
// actions to run before releasing the main domain
|
||||
private readonly SortedList<int, Action> _callbacks = new SortedList<int, Action>();
|
||||
private readonly SortedList<int, Action> _callbacks = new SortedList<int, Action>(new WeightComparer());
|
||||
|
||||
private const int LockTimeoutMilliseconds = 90000; // (1.5 * 60 * 1000) == 1 min 30 seconds
|
||||
|
||||
private class WeightComparer : IComparer<int>
|
||||
{
|
||||
public int Compare(int x, int y)
|
||||
{
|
||||
var result = x.CompareTo(y);
|
||||
// return "equal" as "greater than"
|
||||
return result == 0 ? 1 : result;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ctor
|
||||
|
||||
// initializes a new instance of MainDom
|
||||
public MainDom(ILogger logger)
|
||||
internal MainDom(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
@@ -52,22 +65,47 @@ namespace Umbraco.Core
|
||||
if (HostingEnvironment.ApplicationID != null)
|
||||
appId = HostingEnvironment.ApplicationID.ReplaceNonAlphanumericChars(string.Empty);
|
||||
|
||||
var lockName = "UMBRACO-" + appId + "-MAINDOM-LCK";
|
||||
// combining with the physical path because if running on eg IIS Express,
|
||||
// two sites could have the same appId even though they are different.
|
||||
//
|
||||
// now what could still collide is... two sites, running in two different processes
|
||||
// and having the same appId, and running on the same app physical path
|
||||
//
|
||||
// we *cannot* use the process ID here because when an AppPool restarts it is
|
||||
// a new process for the same application path
|
||||
|
||||
var appPath = HostingEnvironment.ApplicationPhysicalPath;
|
||||
var hash = (appId + ":::" + appPath).ToSHA1();
|
||||
|
||||
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
|
||||
_asyncLock = new AsyncLock(lockName);
|
||||
|
||||
var eventName = "UMBRACO-" + appId + "-MAINDOM-EVT";
|
||||
var eventName = "UMBRACO-" + hash + "-MAINDOM-EVT";
|
||||
_signal = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// register a main domain consumer
|
||||
/// <summary>
|
||||
/// Registers a resource that requires the current AppDomain to be the main domain to function.
|
||||
/// </summary>
|
||||
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
|
||||
/// <param name="weight">An optional weight (lower goes first).</param>
|
||||
/// <returns>A value indicating whether it was possible to register.</returns>
|
||||
public bool Register(Action release, int weight = 100)
|
||||
{
|
||||
return Register(null, release, weight);
|
||||
}
|
||||
|
||||
// register a main domain consumer
|
||||
/// <summary>
|
||||
/// Registers a resource that requires the current AppDomain to be the main domain to function.
|
||||
/// </summary>
|
||||
/// <param name="install">An action to execute when registering.</param>
|
||||
/// <param name="release">An action to execute before the AppDomain releases the main domain status.</param>
|
||||
/// <param name="weight">An optional weight (lower goes first).</param>
|
||||
/// <returns>A value indicating whether it was possible to register.</returns>
|
||||
/// <remarks>If registering is successful, then the <paramref name="install"/> action
|
||||
/// is guaranteed to execute before the AppDomain releases the main domain status.</remarks>
|
||||
public bool Register(Action install, Action release, int weight = 100)
|
||||
{
|
||||
lock (_locko)
|
||||
@@ -97,7 +135,7 @@ namespace Umbraco.Core
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug<MainDom>("Stopping...");
|
||||
_logger.Info<MainDom>("Stopping...");
|
||||
foreach (var callback in _callbacks.Values)
|
||||
{
|
||||
try
|
||||
@@ -109,7 +147,7 @@ namespace Umbraco.Core
|
||||
_logger.Error<MainDom>("Error while running callback, remaining callbacks will not run.", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
_logger.Debug<MainDom>("Stopped.");
|
||||
}
|
||||
@@ -118,12 +156,12 @@ namespace Umbraco.Core
|
||||
// in any case...
|
||||
_isMainDom = false;
|
||||
_asyncLocker.Dispose();
|
||||
_logger.Debug<MainDom>("Released MainDom.");
|
||||
_logger.Info<MainDom>("Released MainDom.");
|
||||
}
|
||||
}
|
||||
|
||||
// acquires the main domain
|
||||
public bool Acquire()
|
||||
internal bool Acquire()
|
||||
{
|
||||
lock (_locko) // we don't want the hosting environment to interfere by signaling
|
||||
{
|
||||
@@ -131,11 +169,11 @@ namespace Umbraco.Core
|
||||
// the handler is not installed so that would be the hosting environment
|
||||
if (_signaled)
|
||||
{
|
||||
_logger.Debug<MainDom>("Cannot acquire MainDom (signaled).");
|
||||
_logger.Info<MainDom>("Cannot acquire MainDom (signaled).");
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.Debug<MainDom>("Acquiring MainDom...");
|
||||
_logger.Info<MainDom>("Acquiring MainDom...");
|
||||
|
||||
// signal other instances that we want the lock, then wait one the lock,
|
||||
// which may timeout, and this is accepted - see comments below
|
||||
@@ -162,7 +200,7 @@ namespace Umbraco.Core
|
||||
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
|
||||
_logger.Debug<MainDom>("Acquired MainDom.");
|
||||
_logger.Info<MainDom>("Acquired MainDom.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -174,7 +212,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
// IRegisteredObject
|
||||
public void Stop(bool immediate)
|
||||
void IRegisteredObject.Stop(bool immediate)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
public static class ImageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MIME type of an image.
|
||||
/// </summary>
|
||||
/// <param name="image">The image.</param>
|
||||
/// <returns>The MIME type of the image.</returns>
|
||||
public static string GetMimeType(this Image image)
|
||||
{
|
||||
var format = image.RawFormat;
|
||||
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
|
||||
return codec.MimeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media.Exif;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class used for imaging
|
||||
/// </summary>
|
||||
internal static class ImageHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dimensions of an image based on a stream
|
||||
/// </summary>
|
||||
/// <param name="imageStream"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// First try with EXIF, this is because it is insanely faster and doesn't use any memory to read exif data than to load in the entire
|
||||
/// image via GDI. Otherwise loading an image into GDI consumes a crazy amount of memory on large images.
|
||||
///
|
||||
/// Of course EXIF data might not exist in every file and can only exist in JPGs
|
||||
/// </remarks>
|
||||
public static Size GetDimensions(Stream imageStream)
|
||||
{
|
||||
//Try to load with exif
|
||||
try
|
||||
{
|
||||
var jpgInfo = ImageFile.FromStream(imageStream);
|
||||
|
||||
if (jpgInfo.Format != ImageFileFormat.Unknown
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelYDimension)
|
||||
&& jpgInfo.Properties.ContainsKey(ExifTag.PixelXDimension))
|
||||
{
|
||||
var height = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelYDimension].Value);
|
||||
var width = Convert.ToInt32(jpgInfo.Properties[ExifTag.PixelXDimension].Value);
|
||||
if (height > 0 && width > 0)
|
||||
{
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//We will just swallow, just means we can't read exif data, we don't want to log an error either
|
||||
}
|
||||
|
||||
//we have no choice but to try to read in via GDI
|
||||
using (var image = Image.FromStream(imageStream))
|
||||
{
|
||||
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
return new Size(fileWidth, fileHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static string GetMimeType(this Image image)
|
||||
{
|
||||
var format = image.RawFormat;
|
||||
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
|
||||
return codec.MimeType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the thumbnails if the image is larger than all of the specified ones.
|
||||
/// </summary>
|
||||
/// <param name="fs"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="extension"></param>
|
||||
/// <param name="originalImage"></param>
|
||||
/// <param name="additionalThumbSizes"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<ResizedImage> GenerateMediaThumbnails(
|
||||
IFileSystem fs,
|
||||
string fileName,
|
||||
string extension,
|
||||
Image originalImage,
|
||||
IEnumerable<int> additionalThumbSizes)
|
||||
{
|
||||
|
||||
var result = new List<ResizedImage>();
|
||||
|
||||
var allSizesDictionary = new Dictionary<int, string> { { 100, "thumb" }, { 500, "big-thumb" } };
|
||||
|
||||
//combine the static dictionary with the additional sizes with only unique values
|
||||
var allSizes = allSizesDictionary.Select(kv => kv.Key)
|
||||
.Union(additionalThumbSizes.Where(x => x > 0).Distinct());
|
||||
|
||||
var sizesDictionary = allSizes.ToDictionary(s => s, s => allSizesDictionary.ContainsKey(s) ? allSizesDictionary[s] : "");
|
||||
|
||||
foreach (var s in sizesDictionary)
|
||||
{
|
||||
var size = s.Key;
|
||||
var name = s.Value;
|
||||
if (originalImage.Width >= size && originalImage.Height >= size)
|
||||
{
|
||||
result.Add(Resize(fs, fileName, extension, size, name, originalImage));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an image resize
|
||||
/// </summary>
|
||||
/// <param name="fileSystem"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="extension"></param>
|
||||
/// <param name="maxWidthHeight"></param>
|
||||
/// <param name="fileNameAddition"></param>
|
||||
/// <param name="originalImage"></param>
|
||||
/// <returns></returns>
|
||||
private static ResizedImage Resize(IFileSystem fileSystem, string path, string extension, int maxWidthHeight, string fileNameAddition, Image originalImage)
|
||||
{
|
||||
var fileNameThumb = string.IsNullOrWhiteSpace(fileNameAddition)
|
||||
? string.Format("{0}_UMBRACOSYSTHUMBNAIL." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)))
|
||||
: string.Format("{0}_{1}." + extension, path.Substring(0, path.LastIndexOf(".", StringComparison.Ordinal)), fileNameAddition);
|
||||
|
||||
var thumb = GenerateThumbnail(
|
||||
originalImage,
|
||||
maxWidthHeight,
|
||||
fileNameThumb,
|
||||
extension,
|
||||
fileSystem);
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
internal static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, string thumbnailFileName, string extension, IFileSystem fs)
|
||||
{
|
||||
return GenerateThumbnail(image, maxWidthHeight, -1, -1, thumbnailFileName, extension, fs);
|
||||
}
|
||||
|
||||
internal static ResizedImage GenerateThumbnail(Image image, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs)
|
||||
{
|
||||
return GenerateThumbnail(image, -1, fixedWidth, fixedHeight, thumbnailFileName, extension, fs);
|
||||
}
|
||||
|
||||
private static ResizedImage GenerateThumbnail(Image image, int maxWidthHeight, int fixedWidth, int fixedHeight, string thumbnailFileName, string extension, IFileSystem fs)
|
||||
{
|
||||
// Generate thumbnail
|
||||
float f = 1;
|
||||
if (maxWidthHeight >= 0)
|
||||
{
|
||||
var fx = (float)image.Size.Width / maxWidthHeight;
|
||||
var fy = (float)image.Size.Height / maxWidthHeight;
|
||||
|
||||
// must fit in thumbnail size
|
||||
f = Math.Max(fx, fy);
|
||||
}
|
||||
|
||||
//depending on if we are doing fixed width resizing or not.
|
||||
fixedWidth = (maxWidthHeight > 0) ? image.Width : fixedWidth;
|
||||
fixedHeight = (maxWidthHeight > 0) ? image.Height : fixedHeight;
|
||||
|
||||
var widthTh = (int)Math.Round(fixedWidth / f);
|
||||
var heightTh = (int)Math.Round(fixedHeight / f);
|
||||
|
||||
// fixes for empty width or height
|
||||
if (widthTh == 0)
|
||||
widthTh = 1;
|
||||
if (heightTh == 0)
|
||||
heightTh = 1;
|
||||
|
||||
// Create new image with best quality settings
|
||||
using (var bp = new Bitmap(widthTh, heightTh))
|
||||
{
|
||||
using (var g = Graphics.FromImage(bp))
|
||||
{
|
||||
//if the image size is rather large we cannot use the best quality interpolation mode
|
||||
// because we'll get out of mem exceptions. So we'll detect how big the image is and use
|
||||
// the mid quality interpolation mode when the image size exceeds our max limit.
|
||||
|
||||
if (image.Width > 5000 || image.Height > 5000)
|
||||
{
|
||||
//use mid quality
|
||||
g.InterpolationMode = InterpolationMode.Bilinear;
|
||||
}
|
||||
else
|
||||
{
|
||||
//use best quality
|
||||
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
}
|
||||
|
||||
|
||||
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||
g.CompositingQuality = CompositingQuality.HighQuality;
|
||||
|
||||
// Copy the old image to the new and resized
|
||||
var rect = new Rectangle(0, 0, widthTh, heightTh);
|
||||
g.DrawImage(image, rect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
|
||||
|
||||
// Copy metadata
|
||||
var imageEncoders = ImageCodecInfo.GetImageEncoders();
|
||||
ImageCodecInfo codec;
|
||||
switch (extension.ToLower())
|
||||
{
|
||||
case "png":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/png"));
|
||||
break;
|
||||
case "gif":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/gif"));
|
||||
break;
|
||||
case "tif":
|
||||
case "tiff":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/tiff"));
|
||||
break;
|
||||
case "bmp":
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/bmp"));
|
||||
break;
|
||||
// TODO: this is dirty, defaulting to jpg but the return value of this thing is used all over the
|
||||
// place so left it here, but it needs to not set a codec if it doesn't know which one to pick
|
||||
// Note: when fixing this: both .jpg and .jpeg should be handled as extensions
|
||||
default:
|
||||
codec = imageEncoders.Single(t => t.MimeType.Equals("image/jpeg"));
|
||||
break;
|
||||
}
|
||||
|
||||
// Set compresion ratio to 90%
|
||||
var ep = new EncoderParameters();
|
||||
ep.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
|
||||
|
||||
// Save the new image using the dimensions of the image
|
||||
var predictableThumbnailName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", maxWidthHeight.ToString(CultureInfo.InvariantCulture));
|
||||
var predictableThumbnailNameJpg = predictableThumbnailName.Substring(0, predictableThumbnailName.LastIndexOf(".", StringComparison.Ordinal)) + ".jpg";
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
bp.Save(ms, codec, ep);
|
||||
ms.Seek(0, 0);
|
||||
|
||||
fs.AddFile(predictableThumbnailName, ms);
|
||||
fs.AddFile(predictableThumbnailNameJpg, ms);
|
||||
}
|
||||
|
||||
// TODO: Remove this, this is ONLY here for backwards compatibility but it is essentially completely unusable see U4-5385
|
||||
var newFileName = thumbnailFileName.Replace("UMBRACOSYSTHUMBNAIL", string.Format("{0}x{1}", widthTh, heightTh));
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
bp.Save(ms, codec, ep);
|
||||
ms.Seek(0, 0);
|
||||
|
||||
fs.AddFile(newFileName, ms);
|
||||
}
|
||||
|
||||
return new ResizedImage(widthTh, heightTh, newFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal singleton to handle the numbering of subfolders within the Media-folder.
|
||||
/// When this class is initiated it will look for numbered subfolders and select the highest number,
|
||||
/// which will be the start point for the naming of the next subfolders. If no subfolders exists
|
||||
/// then the starting point will be 1000, ie. /media/1000/koala.jpg
|
||||
/// </summary>
|
||||
internal class MediaSubfolderCounter
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private long _numberedFolder = 1000;//Default starting point
|
||||
private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim();
|
||||
private static readonly Lazy<MediaSubfolderCounter> Lazy = new Lazy<MediaSubfolderCounter>(() => new MediaSubfolderCounter());
|
||||
|
||||
public static MediaSubfolderCounter Current { get { return Lazy.Value; } }
|
||||
|
||||
private MediaSubfolderCounter()
|
||||
{
|
||||
var folders = new List<long>();
|
||||
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
|
||||
var directories = fs.GetDirectories("");
|
||||
foreach (var directory in directories)
|
||||
{
|
||||
long dirNum;
|
||||
if (long.TryParse(directory, out dirNum))
|
||||
{
|
||||
folders.Add(dirNum);
|
||||
}
|
||||
}
|
||||
var last = folders.OrderBy(x => x).LastOrDefault();
|
||||
if(last != default(long))
|
||||
_numberedFolder = last;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns an increment of the numbered media subfolders.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="System.Int64"/> value</returns>
|
||||
public long Increment()
|
||||
{
|
||||
using (new ReadLock(ClearLock))
|
||||
{
|
||||
_numberedFolder = _numberedFolder + 1;
|
||||
return _numberedFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Media
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to manage auto-fill properties for upload fields.
|
||||
/// </summary>
|
||||
internal class UploadAutoFillProperties
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly MediaFileSystem _mediaFileSystem;
|
||||
private readonly IContentSection _contentSettings;
|
||||
|
||||
public UploadAutoFillProperties(MediaFileSystem mediaFileSystem, ILogger logger, IContentSection contentSettings)
|
||||
{
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_logger = logger;
|
||||
_contentSettings = contentSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auto-fill configuration for a specified property alias.
|
||||
/// </summary>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <returns>The auto-fill configuration for the specified property alias, or null.</returns>
|
||||
public IImagingAutoFillUploadField GetConfig(string propertyTypeAlias)
|
||||
{
|
||||
var autoFillConfigs = _contentSettings.ImageAutoFillProperties;
|
||||
return autoFillConfigs == null ? null : autoFillConfigs.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the auto-fill properties of a content item, for a specified property alias.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
public void Reset(IContentBase content, string propertyTypeAlias)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
|
||||
|
||||
// get the config, no config = nothing to do
|
||||
var autoFillConfig = GetConfig(propertyTypeAlias);
|
||||
if (autoFillConfig == null) return; // nothing
|
||||
|
||||
// reset
|
||||
Reset(content, autoFillConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the auto-fill properties of a content item, for a specified auto-fill configuration.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="autoFillConfig">The auto-fill configuration.</param>
|
||||
public void Reset(IContentBase content, IImagingAutoFillUploadField autoFillConfig)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
|
||||
public void Populate(IContentBase content, string propertyTypeAlias, string filepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
|
||||
|
||||
// no property = nothing to do
|
||||
if (content.Properties.Contains(propertyTypeAlias) == false) return;
|
||||
|
||||
// get the config, no config = nothing to do
|
||||
var autoFillConfig = GetConfig(propertyTypeAlias);
|
||||
if (autoFillConfig == null) return; // nothing
|
||||
|
||||
// populate
|
||||
Populate(content, autoFillConfig, filepath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
|
||||
/// <param name="filestream">The stream containing the file data.</param>
|
||||
/// <param name="image">The file data as an image object.</param>
|
||||
public void Populate(IContentBase content, string propertyTypeAlias, string filepath, Stream filestream, Image image = null)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (propertyTypeAlias == null) throw new ArgumentNullException("propertyTypeAlias");
|
||||
|
||||
// no property = nothing to do
|
||||
if (content.Properties.Contains(propertyTypeAlias) == false) return;
|
||||
|
||||
// get the config, no config = nothing to do
|
||||
var autoFillConfig = GetConfig(propertyTypeAlias);
|
||||
if (autoFillConfig == null) return; // nothing
|
||||
|
||||
// populate
|
||||
Populate(content, autoFillConfig, filepath, filestream, image);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item, for a specified auto-fill configuration.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="autoFillConfig">The auto-fill configuration.</param>
|
||||
/// <param name="filepath">The filesystem path to the uploaded file.</param>
|
||||
/// <remarks>The <paramref name="filepath"/> parameter is the path relative to the filesystem.</remarks>
|
||||
public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
// no file = reset, file = auto-fill
|
||||
if (filepath.IsNullOrWhiteSpace())
|
||||
{
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if anything goes wrong, just reset the properties
|
||||
try
|
||||
{
|
||||
using (var filestream = _mediaFileSystem.OpenFile(filepath))
|
||||
{
|
||||
var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
|
||||
var size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null;
|
||||
SetProperties(content, autoFillConfig, size, filestream.Length, extension);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(typeof(UploadAutoFillProperties), "Could not populate upload auto-fill properties for file \""
|
||||
+ filepath + "\".", ex);
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the auto-fill properties of a content item.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item.</param>
|
||||
/// <param name="autoFillConfig"></param>
|
||||
/// <param name="filepath">The filesystem-relative filepath, or null to clear properties.</param>
|
||||
/// <param name="filestream">The stream containing the file data.</param>
|
||||
/// <param name="image">The file data as an image object.</param>
|
||||
public void Populate(IContentBase content, IImagingAutoFillUploadField autoFillConfig, string filepath, Stream filestream, Image image = null)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
// no file = reset, file = auto-fill
|
||||
if (filepath.IsNullOrWhiteSpace() || filestream == null)
|
||||
{
|
||||
ResetProperties(content, autoFillConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
var extension = (Path.GetExtension(filepath) ?? "").TrimStart('.');
|
||||
Size? size;
|
||||
if (image == null)
|
||||
size = _mediaFileSystem.IsImageFile(extension) ? (Size?) _mediaFileSystem.GetDimensions(filestream) : null;
|
||||
else
|
||||
size = new Size(image.Width, image.Height);
|
||||
SetProperties(content, autoFillConfig, size, filestream.Length, extension);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig, Size? size, long length, string extension)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.WidthFieldAlias))
|
||||
content.Properties[autoFillConfig.WidthFieldAlias].Value = size.HasValue ? size.Value.Width.ToInvariantString() : string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.HeightFieldAlias))
|
||||
content.Properties[autoFillConfig.HeightFieldAlias].Value = size.HasValue ? size.Value.Height.ToInvariantString() : string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.LengthFieldAlias))
|
||||
content.Properties[autoFillConfig.LengthFieldAlias].Value = length;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias))
|
||||
content.Properties[autoFillConfig.ExtensionFieldAlias].Value = extension;
|
||||
}
|
||||
|
||||
private static void ResetProperties(IContentBase content, IImagingAutoFillUploadField autoFillConfig)
|
||||
{
|
||||
if (content == null) throw new ArgumentNullException("content");
|
||||
if (autoFillConfig == null) throw new ArgumentNullException("autoFillConfig");
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.WidthFieldAlias))
|
||||
content.Properties[autoFillConfig.WidthFieldAlias].Value = string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.HeightFieldAlias))
|
||||
content.Properties[autoFillConfig.HeightFieldAlias].Value = string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.LengthFieldAlias))
|
||||
content.Properties[autoFillConfig.LengthFieldAlias].Value = string.Empty;
|
||||
|
||||
if (content.Properties.Contains(autoFillConfig.ExtensionFieldAlias))
|
||||
content.Properties[autoFillConfig.ExtensionFieldAlias].Value = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -329,7 +319,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
if (property.Value is string)
|
||||
{
|
||||
var value = (string)property.Value;
|
||||
var value = (string) property.Value;
|
||||
property.Value = value.ToValidXmlString();
|
||||
}
|
||||
}
|
||||
@@ -404,8 +394,7 @@ namespace Umbraco.Core.Models
|
||||
return content.Properties
|
||||
.Where(property => propertyGroup.PropertyTypes
|
||||
.Select(propertyType => propertyType.Id)
|
||||
.Contains(property.PropertyTypeId))
|
||||
.OrderBy(x => x.PropertyType.SortOrder);
|
||||
.Contains(property.PropertyTypeId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -444,154 +433,116 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
public static IContentTypeComposition GetContentType(this IContentBase contentBase)
|
||||
{
|
||||
if (contentBase == null) throw new ArgumentNullException("contentBase");
|
||||
|
||||
var content = contentBase as IContent;
|
||||
if (content != null) return content.ContentType;
|
||||
var media = contentBase as IMedia;
|
||||
if (media != null) return media.ContentType;
|
||||
var member = contentBase as IMember;
|
||||
if (member != null) return member.ContentType;
|
||||
throw new NotSupportedException("Unsupported IContentBase implementation: " + contentBase.GetType().FullName + ".");
|
||||
}
|
||||
|
||||
#region SetValue for setting file contents
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a HttpPostedFileBase object as the property value
|
||||
/// Stores and sets an uploaded HttpPostedFileBase as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="value">The <see cref="HttpPostedFileBase"/> containing the file that will be uploaded</param>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="value">The uploaded <see cref="HttpPostedFileBase"/>.</param>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileBase value)
|
||||
{
|
||||
// Ensure we get the filename without the path in IE in intranet mode
|
||||
// ensure we get the filename without the path in IE in intranet mode
|
||||
// http://stackoverflow.com/questions/382464/httppostedfile-filename-different-from-ie
|
||||
var fileName = value.FileName;
|
||||
if (fileName.LastIndexOf(@"\") > 0)
|
||||
fileName = fileName.Substring(fileName.LastIndexOf(@"\") + 1);
|
||||
var filename = value.FileName;
|
||||
var pos = filename.LastIndexOf(@"\", StringComparison.InvariantCulture);
|
||||
if (pos > 0)
|
||||
filename = filename.Substring(pos + 1);
|
||||
|
||||
var name =
|
||||
IOHelper.SafeFileName(
|
||||
fileName.Substring(fileName.LastIndexOf(IOHelper.DirSepChar) + 1,
|
||||
fileName.Length - fileName.LastIndexOf(IOHelper.DirSepChar) - 1)
|
||||
.ToLower());
|
||||
// strip any directory info
|
||||
pos = filename.LastIndexOf(IOHelper.DirSepChar);
|
||||
if (pos > 0)
|
||||
filename = filename.Substring(pos + 1);
|
||||
|
||||
if (string.IsNullOrEmpty(name) == false)
|
||||
SetFileOnContent(content, propertyTypeAlias, name, value.InputStream);
|
||||
// get a safe filename - should this be done by MediaHelper?
|
||||
filename = IOHelper.SafeFileName(filename);
|
||||
if (string.IsNullOrWhiteSpace(filename)) return;
|
||||
filename = filename.ToLower(); // fixme - er... why?
|
||||
|
||||
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, value.InputStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a HttpPostedFile object as the property value
|
||||
/// Stores and sets an uploaded HttpPostedFile as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="value">The <see cref="HttpPostedFile"/> containing the file that will be uploaded</param>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="value">The uploaded <see cref="HttpPostedFile"/>.</param>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFile value)
|
||||
{
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase)new HttpPostedFileWrapper(value));
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) new HttpPostedFileWrapper(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a HttpPostedFileWrapper object as the property value
|
||||
/// Stores and sets an uploaded HttpPostedFileWrapper as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="value">The <see cref="HttpPostedFileWrapper"/> containing the file that will be uploaded</param>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="value">The uploaded <see cref="HttpPostedFileWrapper"/>.</param>
|
||||
[Obsolete("There is no reason for this overload since HttpPostedFileWrapper inherits from HttpPostedFileBase")]
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, HttpPostedFileWrapper value)
|
||||
{
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase)value);
|
||||
SetValue(content, propertyTypeAlias, (HttpPostedFileBase) value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets and uploads the file from a <see cref="Stream"/> as the property value
|
||||
/// Stores and sets a file as a property value.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/> to add property value to</param>
|
||||
/// <param name="propertyTypeAlias">Alias of the property to save the value on</param>
|
||||
/// <param name="fileName">Name of the file</param>
|
||||
/// <param name="fileStream"><see cref="Stream"/> to save to disk</param>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, string fileName, Stream fileStream)
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="filename">The name of the file.</param>
|
||||
/// <param name="filestream">A stream containing the file data.</param>
|
||||
/// <remarks>This really is for FileUpload fields only, and should be obsoleted. For anything else,
|
||||
/// you need to store the file by yourself using Store and then figure out
|
||||
/// how to deal with auto-fill properties (if any) and thumbnails (if any) by yourself.</remarks>
|
||||
public static void SetValue(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream)
|
||||
{
|
||||
var name = IOHelper.SafeFileName(fileName);
|
||||
if (filename == null || filestream == null) return;
|
||||
|
||||
if (string.IsNullOrEmpty(name) == false && fileStream != null)
|
||||
SetFileOnContent(content, propertyTypeAlias, name, fileStream);
|
||||
// get a safe filename - should this be done by MediaHelper?
|
||||
filename = IOHelper.SafeFileName(filename);
|
||||
if (string.IsNullOrWhiteSpace(filename)) return;
|
||||
filename = filename.ToLower(); // fixme - er... why?
|
||||
|
||||
FileSystemProviderManager.Current.MediaFileSystem.SetUploadFile(content, propertyTypeAlias, filename, filestream);
|
||||
}
|
||||
|
||||
private static void SetFileOnContent(IContentBase content, string propertyTypeAlias, string filename, Stream fileStream)
|
||||
/// <summary>
|
||||
/// Stores a file.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContentBase"/>A content item.</param>
|
||||
/// <param name="propertyTypeAlias">The property alias.</param>
|
||||
/// <param name="filename">The name of the file.</param>
|
||||
/// <param name="filestream">A stream containing the file data.</param>
|
||||
/// <param name="filepath">The original file path, if any.</param>
|
||||
/// <returns>The path to the file, relative to the media filesystem.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Does NOT set the property value, so one should probably store the file and then do
|
||||
/// something alike: property.Value = MediaHelper.FileSystem.GetUrl(filepath).</para>
|
||||
/// <para>The original file path is used, in the old media file path scheme, to try and reuse
|
||||
/// the "folder number" that was assigned to the previous file referenced by the property,
|
||||
/// if any.</para>
|
||||
/// </remarks>
|
||||
public static string StoreFile(this IContentBase content, string propertyTypeAlias, string filename, Stream filestream, string filepath)
|
||||
{
|
||||
var property = content.Properties.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
if (property == null)
|
||||
return;
|
||||
|
||||
//TODO: ALl of this naming logic needs to be put into the ImageHelper and then we need to change FileUploadPropertyValueEditor to do the same!
|
||||
|
||||
var numberedFolder = MediaSubfolderCounter.Current.Increment();
|
||||
var fileName = UmbracoConfig.For.UmbracoSettings().Content.UploadAllowDirectories
|
||||
? Path.Combine(numberedFolder.ToString(CultureInfo.InvariantCulture), filename)
|
||||
: numberedFolder + "-" + filename;
|
||||
|
||||
var extension = Path.GetExtension(filename).Substring(1).ToLowerInvariant();
|
||||
|
||||
//the file size is the length of the stream in bytes
|
||||
var fileSize = fileStream.Length;
|
||||
|
||||
var fs = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
|
||||
fs.AddFile(fileName, fileStream);
|
||||
|
||||
//Check if file supports resizing and create thumbnails
|
||||
var supportsResizing = UmbracoConfig.For.UmbracoSettings().Content.ImageFileTypes.InvariantContains(extension);
|
||||
|
||||
//the config section used to auto-fill properties
|
||||
IImagingAutoFillUploadField uploadFieldConfigNode = null;
|
||||
|
||||
//Check for auto fill of additional properties
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties != null)
|
||||
{
|
||||
uploadFieldConfigNode = UmbracoConfig.For.UmbracoSettings().Content.ImageAutoFillProperties
|
||||
.FirstOrDefault(x => x.Alias == propertyTypeAlias);
|
||||
|
||||
}
|
||||
|
||||
if (supportsResizing)
|
||||
{
|
||||
//get the original image from the original stream
|
||||
if (fileStream.CanSeek) fileStream.Seek(0, 0);
|
||||
using (var originalImage = Image.FromStream(fileStream))
|
||||
{
|
||||
var additionalSizes = new List<int>();
|
||||
|
||||
//Look up Prevalues for this upload datatype - if it is an upload datatype - get additional configured sizes
|
||||
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
||||
{
|
||||
//Get Prevalues by the DataType's Id: property.PropertyType.DataTypeId
|
||||
var values = ApplicationContext.Current.Services.DataTypeService.GetPreValuesByDataTypeId(property.PropertyType.DataTypeDefinitionId);
|
||||
var thumbnailSizes = values.FirstOrDefault();
|
||||
//Additional thumbnails configured as prevalues on the DataType
|
||||
if (thumbnailSizes != null)
|
||||
{
|
||||
foreach (var thumb in thumbnailSizes.Split(new[] { ";", "," }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
int thumbSize;
|
||||
if (thumb != "" && int.TryParse(thumb, out thumbSize))
|
||||
{
|
||||
additionalSizes.Add(thumbSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImageHelper.GenerateMediaThumbnails(fs, fileName, extension, originalImage, additionalSizes);
|
||||
|
||||
//while the image is still open, we'll check if we need to auto-populate the image properties
|
||||
if (uploadFieldConfigNode != null)
|
||||
{
|
||||
content.SetValue(uploadFieldConfigNode.WidthFieldAlias, originalImage.Width.ToString(CultureInfo.InvariantCulture));
|
||||
content.SetValue(uploadFieldConfigNode.HeightFieldAlias, originalImage.Height.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//if auto-fill is true, then fill the remaining, non-image properties
|
||||
if (uploadFieldConfigNode != null)
|
||||
{
|
||||
content.SetValue(uploadFieldConfigNode.LengthFieldAlias, fileSize.ToString(CultureInfo.InvariantCulture));
|
||||
content.SetValue(uploadFieldConfigNode.ExtensionFieldAlias, extension);
|
||||
}
|
||||
|
||||
//Set the value of the property to that of the uploaded file's url
|
||||
property.Value = fs.GetUrl(fileName);
|
||||
var propertyType = content.GetContentType()
|
||||
.CompositionPropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyTypeAlias));
|
||||
if (propertyType == null) throw new ArgumentException("Invalid property type alias " + propertyTypeAlias + ".");
|
||||
return FileSystemProviderManager.Current.MediaFileSystem.StoreFile(content, propertyType, filename, filestream, filepath);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// A boolean flag indicating if a property type has been removed from this instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
|
||||
@@ -16,6 +16,11 @@ namespace Umbraco.Core.Models.Editors
|
||||
/// </remarks>
|
||||
public class ContentPropertyData
|
||||
{
|
||||
public ContentPropertyData(object value, PreValueCollection preValues)
|
||||
: this(value, preValues, new Dictionary<string, object>())
|
||||
{
|
||||
}
|
||||
|
||||
public ContentPropertyData(object value, PreValueCollection preValues, IDictionary<string, object> additionalData)
|
||||
{
|
||||
Value = value;
|
||||
@@ -28,6 +33,9 @@ namespace Umbraco.Core.Models.Editors
|
||||
/// </summary>
|
||||
public object Value { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The pre-value collection for the content property
|
||||
/// </summary>
|
||||
public PreValueCollection PreValues { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -113,8 +113,10 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </summary>
|
||||
internal virtual void AddingEntity()
|
||||
{
|
||||
CreateDate = DateTime.Now;
|
||||
UpdateDate = DateTime.Now;
|
||||
if (IsPropertyDirty("CreateDate") == false || _createDate == default(DateTime))
|
||||
CreateDate = DateTime.Now;
|
||||
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
|
||||
UpdateDate = CreateDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -122,7 +124,8 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// </summary>
|
||||
internal virtual void UpdatingEntity()
|
||||
{
|
||||
UpdateDate = DateTime.Now;
|
||||
if (IsPropertyDirty("UpdateDate") == false || _updateDate == default(DateTime))
|
||||
UpdateDate = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing the value saved for the grid
|
||||
/// </summary>
|
||||
public class GridValue
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("sections")]
|
||||
public IEnumerable<GridSection> Sections { get; set; }
|
||||
|
||||
public class GridSection
|
||||
{
|
||||
[JsonProperty("grid")]
|
||||
public string Grid { get; set; }
|
||||
|
||||
[JsonProperty("rows")]
|
||||
public IEnumerable<GridRow> Rows { get; set; }
|
||||
}
|
||||
|
||||
public class GridRow
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
[JsonProperty("areas")]
|
||||
public IEnumerable<GridArea> Areas { get; set; }
|
||||
|
||||
[JsonProperty("styles")]
|
||||
public JToken Styles { get; set; }
|
||||
|
||||
[JsonProperty("config")]
|
||||
public JToken Config { get; set; }
|
||||
}
|
||||
|
||||
public class GridArea
|
||||
{
|
||||
[JsonProperty("grid")]
|
||||
public string Grid { get; set; }
|
||||
|
||||
[JsonProperty("controls")]
|
||||
public IEnumerable<GridControl> Controls { get; set; }
|
||||
|
||||
[JsonProperty("styles")]
|
||||
public JToken Styles { get; set; }
|
||||
|
||||
[JsonProperty("config")]
|
||||
public JToken Config { get; set; }
|
||||
}
|
||||
|
||||
public class GridControl
|
||||
{
|
||||
[JsonProperty("value")]
|
||||
public JToken Value { get; set; }
|
||||
|
||||
[JsonProperty("editor")]
|
||||
public GridEditor Editor { get; set; }
|
||||
|
||||
[JsonProperty("styles")]
|
||||
public JToken Styles { get; set; }
|
||||
|
||||
[JsonProperty("config")]
|
||||
public JToken Config { get; set; }
|
||||
}
|
||||
|
||||
public class GridEditor
|
||||
{
|
||||
[JsonProperty("alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[JsonProperty("view")]
|
||||
public string View { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
@@ -11,6 +12,9 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
int Id { get; set; }
|
||||
|
||||
[DataMember]
|
||||
Guid Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the Property
|
||||
/// </summary>
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a ContentType, which Media is based on
|
||||
/// </summary
|
||||
/// </summary>
|
||||
public interface IMediaType : IContentTypeComposition
|
||||
{
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public interface IXsltFile : IFile
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
@@ -27,11 +24,12 @@ namespace Umbraco.Core.Models
|
||||
_addedProperties = new List<string>();
|
||||
_removedProperties = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an item with pre-filled properties
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="useInEditor"></param>
|
||||
/// <param name="cacheDuration"></param>
|
||||
/// <param name="alias"></param>
|
||||
@@ -43,10 +41,11 @@ namespace Umbraco.Core.Models
|
||||
/// <param name="cacheByMember"></param>
|
||||
/// <param name="dontRender"></param>
|
||||
/// <param name="scriptPath"></param>
|
||||
public Macro(int id, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
|
||||
public Macro(int id, Guid key, bool useInEditor, int cacheDuration, string @alias, string name, string controlType, string controlAssembly, string xsltPath, bool cacheByPage, bool cacheByMember, bool dontRender, string scriptPath)
|
||||
: this()
|
||||
{
|
||||
Id = id;
|
||||
Key = key;
|
||||
UseInEditor = useInEditor;
|
||||
CacheDuration = cacheDuration;
|
||||
Alias = alias.ToCleanString(CleanStringType.Alias);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
public MacroProperty()
|
||||
{
|
||||
|
||||
_key = Guid.NewGuid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,6 +30,7 @@ namespace Umbraco.Core.Models
|
||||
_alias = alias;
|
||||
_name = name;
|
||||
_sortOrder = sortOrder;
|
||||
_key = Guid.NewGuid();
|
||||
|
||||
//try to get the new mapped parameter editor
|
||||
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false);
|
||||
@@ -45,16 +46,18 @@ namespace Umbraco.Core.Models
|
||||
/// Ctor for creating an existing property
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="alias"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="sortOrder"></param>
|
||||
/// <param name="editorAlias"></param>
|
||||
internal MacroProperty(int id, string @alias, string name, int sortOrder, string editorAlias)
|
||||
internal MacroProperty(int id, Guid key, string @alias, string name, int sortOrder, string editorAlias)
|
||||
{
|
||||
_id = id;
|
||||
_alias = alias;
|
||||
_name = name;
|
||||
_sortOrder = sortOrder;
|
||||
_key = key;
|
||||
|
||||
//try to get the new mapped parameter editor
|
||||
var mapped = LegacyParameterEditorAliasConverter.GetNewAliasFromLegacyAlias(editorAlias, false);
|
||||
@@ -66,6 +69,7 @@ namespace Umbraco.Core.Models
|
||||
_editorAlias = editorAlias;
|
||||
}
|
||||
|
||||
private Guid _key;
|
||||
private string _alias;
|
||||
private string _name;
|
||||
private int _sortOrder;
|
||||
@@ -76,6 +80,7 @@ namespace Umbraco.Core.Models
|
||||
|
||||
private class PropertySelectors
|
||||
{
|
||||
public readonly PropertyInfo KeySelector = ExpressionHelper.GetPropertyInfo<MacroProperty, Guid>(x => x.Key);
|
||||
public readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Alias);
|
||||
public readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.Name);
|
||||
public readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, int>(x => x.SortOrder);
|
||||
@@ -83,6 +88,16 @@ namespace Umbraco.Core.Models
|
||||
public readonly PropertyInfo PropertyTypeSelector = ExpressionHelper.GetPropertyInfo<MacroProperty, string>(x => x.EditorAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Key of the Property
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public Guid Key
|
||||
{
|
||||
get { return _key; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _key, Ps.Value.KeySelector); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Alias of the Property
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -9,73 +8,56 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal static class MediaExtensions
|
||||
public static class MediaExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Hack: we need to put this in a real place, this is currently just used to render the urls for a media item in the back office
|
||||
/// Gets the url of a media item.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetUrl(this IMedia media, string propertyAlias, ILogger logger)
|
||||
{
|
||||
var propertyType = media.PropertyTypes.FirstOrDefault(x => x.Alias.InvariantEquals(propertyAlias));
|
||||
if (propertyType != null)
|
||||
if (propertyType == null) return string.Empty;
|
||||
|
||||
var val = media.Properties[propertyType];
|
||||
if (val == null) return string.Empty;
|
||||
|
||||
var jsonString = val.Value as string;
|
||||
if (jsonString == null) return string.Empty;
|
||||
|
||||
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
||||
return jsonString;
|
||||
|
||||
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)
|
||||
{
|
||||
var val = media.Properties[propertyType];
|
||||
if (val != null)
|
||||
if (jsonString.DetectIsJson() == false)
|
||||
return jsonString;
|
||||
|
||||
try
|
||||
{
|
||||
var jsonString = val.Value as string;
|
||||
if (jsonString != null)
|
||||
{
|
||||
if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.ImageCropperAlias)
|
||||
{
|
||||
if (jsonString.DetectIsJson())
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
|
||||
if (json["src"] != null)
|
||||
{
|
||||
return json["src"].Value<string>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error<ImageCropperValueConverter>("Could not parse the string " + jsonString + " to a json object", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return jsonString;
|
||||
}
|
||||
}
|
||||
else if (propertyType.PropertyEditorAlias == Constants.PropertyEditors.UploadFieldAlias)
|
||||
{
|
||||
return jsonString;
|
||||
}
|
||||
//hrm, without knowing what it is, just adding a string here might not be very nice
|
||||
}
|
||||
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
|
||||
if (json["src"] != null)
|
||||
return json["src"].Value<string>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error<ImageCropperValueConverter>("Could not parse the string " + jsonString + " to a json object", ex);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// hrm, without knowing what it is, just adding a string here might not be very nice
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hack: we need to put this in a real place, this is currently just used to render the urls for a media item in the back office
|
||||
/// Gets the urls of a media item.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string[] GetUrls(this IMedia media, IContentSection contentSection, ILogger logger)
|
||||
{
|
||||
var links = new List<string>();
|
||||
var autoFillProperties = contentSection.ImageAutoFillProperties.ToArray();
|
||||
if (autoFillProperties.Any())
|
||||
{
|
||||
links.AddRange(
|
||||
autoFillProperties
|
||||
.Select(field => media.GetUrl(field.Alias, logger))
|
||||
.Where(link => link.IsNullOrWhiteSpace() == false));
|
||||
}
|
||||
return links.ToArray();
|
||||
return contentSection.ImageAutoFillProperties
|
||||
.Select(field => media.GetUrl(field.Alias, logger))
|
||||
.Where(link => string.IsNullOrWhiteSpace(link) == false)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user