add staggered versioner

This commit is contained in:
Alexander Graf committed 2014-08-22 00:41:17 +02:00
1 parent 63203aa14c
commit 2b3359dff3
5 files changed
+395 -12

No files matched your search

+2 -2
View File
@@ -28,7 +28,7 @@ func init() {
bs, _ = ioutil.ReadAll(gr)
Assets["angular.min.js"] = bs
bs, _ = hex.DecodeString("1f8b080000096e8800ffec7d7b73db4692f8fffa1413febc016953a06c6753fbb34ce712d9ded3c6b15d969dbd2a457b059243111108307848e6d9faeed7dd3300e60942b292ddad3a566249404f4f4f4f4fbfa6673899b0a36cb3cde3f355c9864723f6e8e0e137ec6fd14536633f64f9398bd20540a4651ecfaa32cb0b362c3867e58ab3a337afdfbf3bfee1c3fb37ef4ed8324ef828dc0374df2709237405cb79c1f34bbe08d98782b36c09cde282155995cf399b670bcee0cff3ec92e7295fb0d9163a633f1dbfdf2fca6dc2115712cf795a607751c9e6f076c6d932ab80a438251a5e1d1fbd787df282ba0ff7f626f77f2d92382dd92ccfaea0ef27accc2b3e86bed2324e2b5effbd49aa02ff177fb3fb1368799e64b32861f79eb06594140014a5e75512e5f26f04da0b2af8a5005ecccbe0706fef32ca59b14de730acf49c4deb16e13a5b54091f06cdbb60cc4e834d54cca36493f3f9aa0ccb3c4a8b242a7970363a2444559ecc22403f6501f08df037ed4318c0323e1f2e2b781067291bde5b95e5e66d9e5dc60b9e8fd9bd065ffd6cc43eed31f86880e1822fa32a298bf063912fff9347f0ec75b4a64eff6bffe8e4ddcbfdf7d9054fa1f31d6d8fb2ec22e6755bada5686a111402ef4ecaa88ce72f61b28a5719763e1444e20718b38c3f3e6141026cdc0fc6cd8ba25a8a17e1af459606f4fc1a9886ffeb2c2af32c490069f0e292a7e5519927c0788567c53cdbc0c4d2b01afed0c330898a925ac170d22a490403705eb214240a474962d03ec716c7cfe1f9413de4e1559c2eb2ab5138839fc360c69759ceab3481912a740ceb9ef1e3424ee3aa7b29aaf99c17c5cb14619a912ca23252d1c44b36fc4ae0521f2be3bbc7d771390c3e1cbf21a86074a8413574e07a685f5def695dc8213f6307662f485008c37d11cd578a9472e4a8098b1f98ac2283259b64e7c301410dc68c7e86f1a2feaddce26c89df69c487161e6d74562ba3c1b5f2f7f5613b3487086077a734a884a7e7e58aedb387676deb66eecda640bd82b8e0e5fb78cdb3aa5478624d104a6378cecb61ad011eb06042f417df15713ae7d3001e892e475ad3504ac7b09112e33dcff32c1fd2bff04e19fd98fdf9e0403eb856c44d82ee14b69eb2b65c760a9b22f444c81d322e898186e9c3e0ee18f6f0c0e2d80d29e8eaddeab943bb9dd44fbb359c6a15e0f7249b470855b311a71b74eee573782db458fd1406f4e6474d17e053344a515e0a4b27674ed5a0f36cbd4938d131659fae0ff57768be7ccf8f531c91d661fb3ee534b6c26e4cbcc2e7a767da73b0be3cb1c1d75b5ab141a03d4d01d8816393676536cf92a3151823f04d3459953039df647909ec8beccec4bbb7c0de985f795b3b0605ce55fa020766935a6dce73b099c7e93293eddc42f8603041133a1835f2d60a08be28d485044ed64bb056e44c2de3bc28198254d139af5dac2486871b61c4c949c38760cbf3a0a8dd2c151bb96ae0d645d2a0a3db17b548af385b45979c4597519c44333001ecbd683166039e0e545405ea62d9a1ec895dc5e05eaea372be2270068c829ffb1f4ec07e5cadc0b16083ff59edbffffb8020556ca211e89e6d0382be6d9a95f4f7d1eb41d86a6e61e0d3f3b1680642dfea0230736c8800312d1bf8f194600b692be0c98307a6b242000027b8d3f84c5788751f00701925f1e21561039fb684e5ae4c9ecb92e6bcacf29410d7b6ead9943d625f7fcd921066967f7cb3a4691fb169b3caebcfb5a19951b5d7c428d81eba3a6e950b7a764db3d38333879d9ee53cba30fa36f4fef5485327f7c0531a344a4bacc31321cf0355e5a9a4d5b38686b96e092b2acac1291985f8e6d0f067902d8655d1874520a6896a3cb45a8970bef8be55530d7490af03f05b9ff3447167e1e122cee573b0ae713e52dfa2cac797a812d5e76556cd57f8e2c36681618378756d92713cf71091f335045a4e3aec573511e0cd4af7d5410838243c8f8b8b861473fa5a6f539d30328f105de51aeb5587d0e9a54abc710aae85f2d865959a26c3e0ffa5bcbccaf20b52a9c1086d43940c8315e8320d3940b698bae18a5555225fdc50a668d47c909ed0cd1961bb50e4ecb7d45a02ec1bf54e31265a3144e3d2ecf5201797dc42984078436eba4ea96a9485478d96efcce33a9a6061510af7841e9559af21bc82a59f1ca3ea136be58bc701860cf8bd7a07140d1bda468aa30fe64574c5142f0849af0a321711182de1699079042358aca29c8b7c0822036b55ae4253d8c94750b9f11a7138e22cc47db4b44441d27dd4d0540312a2e3e763a68ea6e16c9728bf035d51f23f82b9fe4110148ec4a6df49330ef6795c4867b217c520851c66d376434feb0140a477b6a3cfa31b74488b7a47779e35e303d7ac40d34f3adb144fd8c1d87a03c196efd571fac3b6e4c5fbac8c1227c09baadc01f1fd6201b308e81b0a2378a2c35d1fba87574ffceed1fd7729088058cd8bba43711c5138429929c79c99d3a5c530e19b0dcd40f8e1ddf7e0a46c4a0c1aa676a20434c5f112dc67f485459080a66b15812f0d9e3f4b39e8017073a31a470ada63c1e7680316631313e891ab087ce432635171d138e6f8f73aba00279bcd57593c071ffb878ab4ce224b8392da98a8a0c9ac3a47146bb6a872240a4d6d1c25188c57104c1619e92d5e225acafe91d2b210011125c4ee22dd5b07159771119721fbfb8aa732992b3080fadb6096d82628924109e102b07546fa324a31019cb315fc0341c6793646aae4e84d1cbf556025d107dcd3dea03a22b27e46aad0b864f36a8dd91b41152a94249af3e164f8dd13f8ef1f9fc3fb87bf14f7476d23f8eb9729fc333cfdc7e1d9fd5178ffdee8f33fe0df094431f71e0e1c6ef5576d6397376d9000440dda06d3017bc0304e07ad77051eef0336385c471ff74188e8d5e383fb8fbeb9fff8db03d3bd671c33d70e2d00043d5058f054c5becf04aefb94f1b01be3079d8ccae95a347df772f6c932f059062b8d2f5ec2bf851ada360b50b511a4ed0de372c1b7c831050cd9829086dba4f7750aed2cadea004111914f3bf251c417674688bc9aef901c4ae5f114f73e3ebc3b469b96a530eb6250ae80dd4cbe699da93e133908d251b267033f5ace5565d59838258ca96b26cd34aac8858d295d6339951e16d62272ed9ed9932d04136b8da56e56160418f465959efb21db81bf3b0d8d40ed64a18b7582e481c5b6ebee911ace98e0bd695af00d988fa94abfeddc61e0af70bf6345b49de2ba20ecff42cba3b5f0df2169b446248dc1d777be6a744fabf12db0436bb00e29319b28293cf373ddb56a4d3cda0256de1eee39b1e01c83b38319a8319bd39ec5819b8a265d355f6f30a7778ba123cbb0354ae440f858832e78fcd45bae6ea2fcecc10f0eecc1d4cb2920e5cc8f1699018d1fdec19c8462ac9899068a2688da331d2e05a12cba316bd7fad83b03ff1cd57bd4840f18fa17Line truncated
bs, _ = hex.DecodeString("1f8b080000096e8800ffec7dff73db36b2f8effe2b507d72a594c8949df43af789a3f4a56e72cfd77c9b38e9bd19d7f7869220893545aa2465c72ff5fffe76172089af1465bbbdbb99a7696389582c168bc56277b1004723769cadaff378b12c59ff78c01e1f1c7ec3fe165d6413f67d962f5894ce00222df378b229b3bc60fd8273562e393b7ef7f6e38793ef3f7d7cf7e194cde3840fc23d40f7224918a12b58ce0b9e5ff259c83e159c6573a81617acc836f994b36936e30c7e2eb24b9ea77cc626d7d0187b73f271bf28af138eb89278cad3029b8b4a3685d20967f36c0324c529d1f0fae4f8e5dbd397d47cb8b7377af84b91c469c9267976056d3f6565bee143682b2de374c3abdfeb6453e0ffe2377b38829a8b249b44097bf094cda3a400a0285d6c922897bf11682fd8c0970278312d83a3bdbdcb2867c5753a856ea50b36ae6a84ab6cb649783fa8cb82213b0bd651318d9275cea7cb322cf3282d92a8e4c1f9e088106df2641201fa310b806f84bfae1f4207e6f1a23fdfc083384b59ffc1b22cd7eff3ec329ef17cc81ed4f8aa6703f6658fc147030c677c1e6d92b2083f17f9fc3f7904cfde462b6af4bff68f4f3fbcdaff985df0141adf52f738cb2e625ed5d56a8aaa164121f0eeb48cca78fa0a06ab789d61e37d41247e8031f3f8f3531624c0c6fd605817149bb928087f29b234a0e737c034fc5f675199674902488397973c2d8fcb3c01c62b3c2ba6d91a0696ba55f3871e86495494540bba936e92443000c7254b41a2b0972406cd73ac71f2033c3fa8badcbf8ad35976350827f0b71f4cf83ccbf9264da0a70a1dfdaa65fcb89053bfaa568acd74ca8be2558a30754f665119a968e239eb7f2570a98f95fe3de0abb8ec079f4ede11543038d2a06a3a703e3445377b5a13b2cbcfd981d90a121442775f46d3a522a51c396ac2e20706abc860ca26d9a2df23a8de90d1df309e55dfca6b1c2df19d7a7c64e1d17a67d5322adc28bf6f8e9aae3944009b3ba34e253c5d944bb6cf0ecf9bdaf5d89b55817a0571c1cb8ff18a679b52e1893540288de18297fd4a033c62c188e82fbe2be274cac7013c124d0eb4aaa1948e7e2d254639cff32cefd3bf50a6f47ec8fe7c70201fdc28e22641b70a5b47599bcf5b854d117a22e41e1997c440c3f830b83f861d1e581cdb9182b6d6ad965bb4db69f5b45dc3a9ab027c4fb2698450151b71b841e75efe00c5428b554fa143ef7ed474013ec54529ca4bb1d2c9915335e8345bad134e748cd9979b23bd0c972fdff393147ba435d894a79cfa56d8958957f8fcec5c7b0eab2f4f6cf0d535cdd820d09ea600ecc0b1ceb3329b66c9f1121623b04d34599530395f677909ec8becc644d97b606fccafbcb51d9d02e32a7d891db349ddac1739ac9927e93c93f5dc42f8a837c225b437a8e5ad11102c28d4890446d62b58adc8989ac779513204d9440b5e9958490c0fd7621127230d1fc25a9e07456566a9d8c85403b32e920b3a9a7d5183f48ab36574c9597419c549348125807d143586acc7d39e8aaa405d2c1b942db1ab18cccb55544e9704ce8051f077ffd329ac1f574b302c58ef7f96fb1fffde2348159ba804bae7ba0641db36cd4afa7dfcb617369a5b2cf0e96228aa81d037ba009639d6478098a60dfc7946b0855c2be0c9a347a6b242000027b8b3f85c5788551b00701925f1ec3561039bb684e9ae0c9e6b25cd79b9c953425cad55cfc7ec31fbfa6b968430b2fcf3bb390dfb808deb595e7d6e0ccd8caabd2246c176e86ab8512e68d9d5d5ce0ece1debf424e7d185d1b6a1f76f069a3a79009652af565a621e9e0a79eea92a4f25ad1a355c98ab9a30a3a21c8c9241882547863d836c315615bd5b04622e51b585562911ce672f1a35554307f92a00bbf5079e28e62c3c9cc5b97c0eab6b9c0fd45254f958882a517d5e669be9120b3ead67e83688a21b938c93a987889cafc0d172d26117554480352bcd5707216090f03c2e2e6a52cce16bac4d75c0687904ef2ad758af1a844e2b55e28d53302d94c7ae55a9aed20ffe5fcacbab2cbf20951a0c706d88927eb0045da62107c806533b5cb1dc94c8173794291a151fa425b43b236c138a8cfd865a4b807dbdde2ac6442bba685c2e7b1dc8c52937134b20949099ae53aa2ecac2a2c695efdc633a9a6061510af3841e9559a72ebc86a99f9ca0ea1373e5cefd80850cf8bdfc0014f56bda068aa10fcb8b688a29561092be2968b98860d11296062d8fb00816cb28e7221e82c860b52a97a129ec6423a8dc788b381c7e16e23e9e5ba220e93eae69aa0009d1c90f43a6f6a6e66c9b287f005d51f23f82b9fe4e1014f6c4a6df493376f687b890c664278a410a398ca66d869e551d004fef7c4b9bc73b3448937a4b739e39e303d75681ba9d74b22e9eb283a15502ce96afe824fdfebae4c5c7ac8c1227c0bb4db905e2c56c06a308e86b0a2378a2c3dd1cb9bb570dfcf6defd772908005fcd8bba45711c933b429129c79899c3a5f930e1bb358d40f8e9c30b3052d6253a0d633b50029ae2640ee633dac2c249c0a56b19812d0d963f4b39e8013073a30a470ada63c6a7b806cc862626d0235711d8c865c6a2e2a236ccf1f72aba00239b4d97593c051bfbfb0d699d59960625d531514195c9668128566cb6c991285c6ae32841677c03ce649191dee225a2a5e81f292d0b11105182ef2ec2bd95537119177119b2bf2f792a83b90203a8bf3546896d8222e994102e005b65a42fa31403c0395bc23fe0642cb22152257b6fe2f87503ab24da807b5a09aa2322eb27a40a17976cba5961f44650850a2589a6bc3fea7ff714fefbc76fe1c3a39f8b8783a612fcfa790cfff4cffe7174fe70103e7c30f8ed1ff0ef08bc9807873d8759fd5553d9654d1b240051bda6c2b8c71e31f4d341eb5d81c5fb88f58e56d1e77d10222a7a72f0f0f1370f9f7c7b609af78c63e4daa10580a0470a0b9ea9d8f799c0f590221e7665fca091b1719a1675db9d8c7d5a19f8248399c667afe0df42756deb09a8ae11a4ed8dc5e5825f23c71430640b421a6693ded619d4b3b4aa030445443e6d8947115f9c1121b26abe43722894c753dcfbf8f4e104d7b42c8551179d7239ec66f04d6b4cb599c8409086923d1af8d162ae2aab86c429b198ba46d20ca38a58d890c2359651e96161252237ee913dbd066762a5b1d4cdca820083aeacd2633fb476e077e74223503b59e8629d20b967b1eda6bda7863126786f2e2d5802cbc758a5df36eed0f157b8df32239a46715e10f67fa1e9d1acf0df21693447248dc1d7f73e6b744babb62db041abb30e2931ab28213cf373d3366b4d3cda04564a8ff69c58708cc1d8c108d4904d69cfe2c04d451dae9aaed618d3bb45d79165581b25b2276cac5e1b3c7eaa2d5737517ef6e0073bf668ece5149072ee478bcc80ca87f73026a1e82b46a681a211a2f60c874b4128936ec89ab93ef48ec03f47f51ed7Line truncated
gr, _ = gzip.NewReader(bytes.NewBuffer(bs))
bs, _ = ioutil.ReadAll(gr)
Assets["app.js"] = bs
@@ -73,7 +73,7 @@ func init() {
bs, _ = ioutil.ReadAll(gr)
Assets["favicon.png"] = bs
bs, _ = hex.DecodeString("1f8b080000096e8800ffec3dfb7bdb3692bff7af40b46d63ef99929d57ef5cdb7b8e9d6edd368fcf4eb6d7ebd7db0f22210935453000694775bd7ffbcd0cf87e8894adc4deddfe9098225e338399c1cc0018ee3d387e7df4f6a7372fd82c9afb079fed3d709ccf462376a4c28596d359c4368e36d9a3ed9d27ec3b7eaec6ecb9d253c6030f6a049196e33852dab00d23048b66821dbd7ef5f6f4e4f9bbb7af4fcfd844fa627388dd1dfa3ea3ee0cd3c2087d21bc217b6704531368260d332ad6ae60aef204839f53752174203c365ec060ece5c95bc7440b5f605fbe74456070381e31174ac7824d540c20c98060f8e1e4e8c5abb31734fcf033c701ac1039e6f360ba3f10c18005538787e1fec02c0217860fa6f4ca458c94ef0bbd3f384b4b8e22ed0f98eb7363f60758c957fc7c805d0aee1d7cc6d8de5c449cb933ae8d88f607713471fe739017cca22874c4fb585eec0ffec77977e81ca979c82339f605740b238a005a9dbcd817de5414da057c2ef60717525c864a4785aa97d28b66fb9eb8003238f4630b109791e4be635cee8bfd9de176ad234f1857cb30922a28f455abc6e368a674ad862f837398381f2806c5911b474cbad8d34c8bc9fe60c22ff0e73004421e7c860d2219f9e2202322fb9d5d5de13cbf82f97d05236d6c5e5fef8d6cad6c00dbd958a9c8449a8723d79851f66b3897c110de0c1238901bcc4c886850ed40030d2ef9624965facda245081847e243842351096363e52dd8153d321672cf03e89db18a2235df655f6d871fbe4eca26402167c2e7d25fecb2c1b7c2bf109174397b256231d862d98b2d76a8616ab698e1817180f5e5c47671fd19fd99ed6cb1d923f8f718fe3d817f4fb3e1cb439c5ab4a0ef5b8c16fbff511860cef554064ea4c25db6337c2ae6a5ba43248d335781322107d96c04eba5087cb5c55eaa80bbf017948251202b00e41148b4141a40bc0408b36e2a43709003143c0f45da3b8848aa227d805c5d2fc5d9692f9d00afb497663d7b4b7b6e2ccd7af6322a8c95f684b6b40b5450c1cb57535521f32edbfebacc578537d48df33467af501989c2ba8b1c0ceae2a23a8034911328671c83be8ab2a1e835b1b783ec6d41ab3404c4fc781e646d3c69429fc35cca00a44838635fb9e7291c207556c900f7a7fc91310e6974609dbc60ccddf3a946658ca3280de04fc71b8f1e3fdb628f9e6ce37f3b9b5f9729a8b92763b3cb1e17904fe9b3137e604ff2f729211fc1eb47e9ebeb2a5ec066c1d0e3a0d32a7cee8b0940bb9d337a09bd9dedfc35713ef7e514e98f487edd4dab94c0e91a51a72fce057b20e7a8d079103548022c5f59b3cb998c844332834d2f41079674cfa5b0d47fb2bdddd89357536316ff47a0c4baa0f08666ce7ddf2951b115a0a4f17fcf852739db98f30f094dbf7af655f86133eb20912b3000425013c0d207f64d51fab2ca8c8dfe0cbc0f6640c438cb9601068b6c842b8a184e87bb595df667208a062d3386359f8533a0b401a1628084ba643855632df8b94113c157d05c8b502b68e3030b8e0c2cde606b5cca6856ecd1f29319b23f8fb2d715226820533a2bd7193bee8d4804c14818592be1b33dc2108b3c7951b5355e5cc0524b76c6c1de08caed120a86187bab421029cdd086c17701bfc84c117e8125f60faaa1f4d113131efb603140f782eac929a775dfae700441d20942c181383a29c3b511a4a73c8633d660ef016c723e4d4b50bf0d98d1ae7d7448609e3d210b80cd8833f7078f1f0d98b556063b3b5f0d46801df69e0d1556c6c14ed84c7aa0759d0fb02037990c61d63af60bcd53e40b8f24b7195e642220e1255808713805ad234e82891a06e2b2803e55049b365241621fd81f9905388e8274007c847f39c1adf9614d493052cfb3813636cb0314693cf517e10cad27963d39ee4c5c68f81b878394665f8ab9c9e43fef04442230b03e88fcc9b9e07e0cff0b6d60d2f70757574574b18689aeaf0707efec5be03176f54552fb8bebf214d140234b82022547be2cd335c1c5d32af4d46550c6962784f9d3a05a0fd8763a453b18f54cf2a3d84b05c783179e8c2c80efbf0cc6408fbd71c6c920c111526b0cff7869fc9c51b271e722884b40121a0719a8c53904e5792a428573d8397133cfabcc58158543cf63d81dacee4a2f92aa087199a85d10a150f48208a872292c653aa0c22ebbe0c908292fa48762b302d4b038446756799b5ea0bb6ada0176daddcdc828fb53f1bd46c7b40b9a192c3527c71f9386661647c8c0bd8056934927c4b6bb9bd10fd6f188eba8271b4ea0faac039e53dbe3c7a4201fabb81fc8b072ebc811f3305a74490ff6b904e8bd51ec376bcfbc2459f8f1019617bbdab72cd4b63e9a07df486d2258e72fb7980afc0503e7fc326072c202e10a63b85e7ccd129ab24bae03b49b125322b74470417c00fd4fe4f424408f3d53d3d0716e179480f19db9e7ec3c2aaeae8572a083f019fdef24c39657846a5da4b547b5f6668f0b6b5aa90e050b06298f805b293ce18181f5f820a35dfb00687e55157e5898c2b7338c3d210d624dc6129b71c3c642803bc12f300e154760ee8129ea822f062dbc21cba31bf318a721010cadcea41245a3c0c028773d2c5831a5a96f071ffdcf8a95d2c34e41e3c4cc8b360a0bc15d4c2ca34f2ac95513a2c6523e48db447e18344c66f945e967e1476e399785434c631fec64e0e50aeb97983ced0f1be6cb33b9d36c038d6bf49d36f31e1a05e2d9327970d02d0e074c7a48ef640429cc72c948a4e8ea0a9b1c61c1063e0d4f8ec11aa629849f8247b64ff46ef0ef0f0076c5e65c2a75a57a501384b049f42ad5c8b42b596e40079f872635e842b0c93086f8a712bea935882f01b1cf65e0890f6890d6baef6129170d2e66a904b4b9beeeea2b978382c381e434e004ba334bceb38847714ef04608eb8678d689733913607fc7c17940d6ec3bfb50b7b0fbf76580154301289fd9875bf5e5f2c0aae6b3e4a9bbb75a1fa0029b38a805060a08770cc4d8c6d515f6fb466817d8874f4581e5bfd86c866e65c8a5d7c8d1cd609f40e54f08735b49c5b1b1af60fd2b8b79557f26929fea9da2c895a53c955e9689716db43eeb6ab56235e8d32ce836f0546cc22a51d8e4b789b4241168266a94065a9a4b755b1116ceba573f98dace952f5b3d0abe00f4bd64602f431cc32849e0a2a0cda0bdd78ad3a81da9dbe36b03648e0a45d081f73754730d081f4b2d5ca4dfadf04e2ddb39f86dfecf09257f19cae082fbb28d7b7a9325b16a1d23a75d7479a1b5ba25599a71b84bb698fa6adce50eff15ea70bf29d8b10e224ca9f76fa42f0cfb9d71ff922fccab783e16fafabaa6bf65041026c36fb17fb474f67c115167631970e4bee77747df999a7791f707e57e3ceafad8f99a884b7ddd23dababe8a3d07c31ebee25de1bad7e0f6bd9e90ab771b02b7d7e72d8a2a00f7d64ec001db2e8780d425fabeb9599aLine truncated
bs, _ = hex.DecodeString("1f8b080000096e8800ffec7d697723b7aee0f7fc0ab65e92b6efb824bbb7cc38b6dfb8edce8d93f472ecee97c9e464eea1aa2889716d5d64d9561cdfdf3e0058fba22a59f272dfcd876eab8a1b0002200092a8bd27c7ef8f3efef2e10d9b69cf3df862ef89657d311ab1a3209c47723ad36ce368933ddbde79c17ee0e7c198bd0ea229e3be03357c1dc971ac8348b10d2504d333c18edebffb787af2fad3c7f7a7676c225db139c4ee0e5d9751778a454289e8423843f64909164ca099544c0571640b66078e60f0380d2e44e40b878de730187b7bf2d1527aee0aeccb95b6f0150ec735b3a1742cd824880124e9130c3f9d1cbd7977f686861f7e6159801522c75cee4ff707c21f307f6af130dc1fa8b96fc3f0fe945ed98851e0ba22da1f9ca525473a7207cc76b952fb03ace406fc7c805d0aee1c7cc1d89e273467f68c474ae8fd41ac27d6ff1ce40533ad434b7c8ee5c5fee0ff589f0eada3c00bb996635740b730a2f0a1d5c99b7de14c45a19dcf3db13fb890e2320c225da87a291d3ddb77c40590c1a2872d405c6ac95d4bd9dc15fb3bc3ed5a478e507624432d03bfd057ad1a8ff52c886a355ce99fc3c4b9403128d676ac99b4b1a7592426fb8309bfc0c76108843cf8021b68a95d71901191fdc9aeaf719edfc1fcbe839136366f6ef646a6563680e96c1c045ae98887235ba951f634f4a43f843783040ee4063513420faa1d4440834b3e5f50999e999e8780b116571a47a212c6c6813367d7f493b1903b0e406f8d03ad036f977db31d5e7d9b944d8042d6847bd29defb2c1f7c2bd105ada9cbd13b1186cb1ecc5163b8c606ab698e2beb280f5e5c47471f305fd99ed6cb1d933f8f71cfebd807f2fb3e1cb439c1ab4a0ef15468bddff5118c0e3d154fa960ec25db6337c29bc52dd2192c6f2023f502107d96c04ebadf0dd608bbd0d7c6ec35f500a2a005901208f40a2a58800c44b8030eba63204073940c17350a49d034d52a5a303e4ea7a29ce4e7be90478a5bd34ebd959d8736369d6b39351611c448e880cedfcc0afe0e506d3a042e65db6fd6d99af0a6fa81beb65ce5e61a0240aeb2e7230a88b8bea005269cb0fac710cfa4a6743d16b626f0bd9db8056690888b9b1e7676d1ca94297c35c4a1fa448586337b0cf533840ea8c9201ee4ff923631cd2e8c03a79c198dbe7d30895318e124400fe74bcf1ecf9ab2df6ecc536feb7b3f96d9982117764ac76d9f302f2297d76c22bf6227f9f12f219bc7e96bebea9e2056ce60f1d0e3aadc2e7ae9800b4db39a397d0dbd9ce5f13e773574e91fe88e4b7ddb44a099cae1175fae25cb027d24385ce7ddd2009b07c65cd2e67520b8b64069b5e820e2ce99e4b61a8ff627bbbb127a7a6c60cfecf40897541e10c95c75dd72a51b115a0a4f1fff6842339dbf0f85542d36f5e7d135e6d661d2472050640086a0258fac0bc294a5f5699b1d1df80f7c10cd08cb36c1960b0c86a5c51c4703adccdeab2bf015122d0326358f35938034a2b102a064804970ca76a1c097eaed0447003681e89300aa08d0b2c3852b07883ad7129f5acd8a3e12735647f1b65af2b4488804ce9acdc64ecb837221104236164ac842ff608432c72e445d5d67873014b2dd919077b2328374b281862ec63108248450c6d187ce7f38bcc14e1175862fea01a4a7f3a62c263172c06e85e503d39e5b4ee9b158e20483a41283810274aca706d04e9298f618d23b0f70036e94dd312d46f03a622dbfcb448605ebd200b80cd8833f707cf9f0d98b156063b3bdf0c46801df69e0d1556c6c14ed84c3aa075ad2b58909b4c86306b1dbb85e629f2859f24b7195e642220e1255808713805ad234efc4930f4c565017daa0836ad0efcc43e300f990538d67e3a00fe847f39c18df9614c493052cfb3813636cb0314693c75e7e10cad2796fdb2ec99b888e06f1c0e529a7d2d3c95c97fde098884af607d10f92feb82bb31fc2f220593be3fb8be2ea28b3594beb9191c7c326f81c7d8f55749edaf6eca5344038d0c090a941cb9b24cd70417270a4227b8f4cbd8f28430ff31a8d603b69d4ed10e463d933c147ba9e078f0c691da00f8f96b7f0cf4d81b679c0c12ac915a63f8c74be3e78c928deb093f2e0149681c64a016e71094e7a908039cc3ce899b394e65c6aa281c3a0ec3ee60750fa2795215212e13b50b22148a5e1001552e85a14c0754d865173c1921e58574506c96801a16077d6694b7ea05ba1d4c3bc04ebbbb1d19657f2a7e8ed031ed8266064bcdc9f15dd250cd628d0cdc0be86032e984d874773bfac13aae79a47bb2e104aacf3ae039353dde2505f93888fb810c2b77a42de1857ade253dd8e702a0f746b1dbac3df39264e1c71fb0bc98d5be65a136f5d13cf84e464ac33a7fb9c502df9d3370ce2f7d2627cc17b6508a47f36f59425376c9231feda6c494c82d115c109f40ff13393df1d163cfd434749cdb0525605ccb73ac9d67c5d5b5500e74102ea3ffad64d8f28a50ad8bb476a8d6deec79614d2bd5a160c120e511702b85231c30b09e1f64b46b1f00cdafaac20f0b53f87186b127a4411c91b1c4665cb1b110e04ef00b8c43c51acc3d30456df0c5a08533647974c38b711a12c0d0ea4c2a51340a0c8c72d7c38215539afa76f0d1ffac58293dec14344e9457b4515808ee626219ddab24574d881a4bb9206d1379356898ccf28bd263e121b79ccbc221a6b10b7632f07285f54b4c9ef6870df3e599dc69b681c635fa4e9b790f8d02f16a913c58e8168703261da4773282146ab1642452747d8d4d8eb060037f0d4f8ec11aa6298447c1b5e913bd1bfcfb13805db139174a5da91ed404216c12bd4a3532ed4a961bd0c1e5a14a0dba106c328c21fe4709dfd41ac49780d897d277c4151aa4b5ee7b58ca45838b192a016d6e6ebafacae5a0e0702039153881f6cc90f34c731de7046f84b06e88679d58973301f677ec9ffb64cd7e323fea1676ffbe14b0622800e533f363a5be6cee1bd57c96fceaeeadd607a8c0260e6a818102c21d0331b6717d8dfd7e10910decc3a7a2c0f25f6d3643b734e4d269e4e866b04fa0f23dc2dc5652716ccc2b58ffca625ed59f89e4a77aa7287265294fa59765625c1badcfba5aad580dfa340bba093c159bb04a143679563a922402cd44d569a0a5b9346a2bc2c259f7ea0753dbb9f265ab47c11780be170cec6488631825095c14b419b4775a711ab523b53abe26406605a1f03bf0fe8e6aae01e16319091be9b712dea965eb81dfe6fe9a50f2b7a1f42fb82bdbb8a7375912abd65272da4597375114ac4896661c1e922da66e30ee7287ff0e75b8db14ec580711a6d4fb77d2158afdc9b87bc9e7ea5dec8d45747353d3df520384c9f05bec9f2d9dbd9e6bea6c2c7d8edcf7fae1e83b0bbc2ef2fe14d877475d173b5f1371a9af47445bdb0d62c7c2b0871bf0ae70dd7b70fbde4fc8d55b85c0edf5798ba2f2c1bd351370c0b6cb21a0e0127ddfdcLine truncated
gr, _ = gzip.NewReader(bytes.NewBuffer(bs))
bs, _ = ioutil.ReadAll(gr)
Assets["index.html"] = bs
+33 -1
View File
@@ -686,9 +686,22 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
});
if ($scope.currentRepo.Versioning && $scope.currentRepo.Versioning.Type === "simple") {
$scope.currentRepo.simpleFileVersioning = true;
$scope.currentRepo.FileVersioningSelector = "simple";
$scope.currentRepo.simpleKeep = +$scope.currentRepo.Versioning.Params.keep;
} else if ($scope.currentRepo.Versioning && $scope.currentRepo.Versioning.Type === "staggered") {
$scope.currentRepo.staggeredFileVersioning = true;
$scope.currentRepo.FileVersioningSelector = "staggered";
$scope.currentRepo.staggeredMaxAge = +$scope.currentRepo.Versioning.Params.maxAge;
$scope.currentRepo.staggeredCleanInterval = +$scope.currentRepo.Versioning.Params.cleanInterval;
$scope.currentRepo.staggeredVersionsPath = $scope.currentRepo.Versioning.Params.versionsPath;
} else {
$scope.currentRepo.FileVersioningSelector = "none";
}
$scope.currentRepo.simpleKeep = $scope.currentRepo.simpleKeep || 5;
$scope.currentRepo.staggeredMaxAge = $scope.currentRepo.staggeredMaxAge || 31536000;
$scope.currentRepo.staggeredCleanInterval = $scope.currentRepo.staggeredCleanInterval || 3600;
$scope.currentRepo.staggeredVersionsPath = $scope.currentRepo.staggeredVersionsPath || "";
$scope.editingExisting = true;
$scope.repoEditor.$setPristine();
$('#editRepo').modal();
@@ -696,6 +709,11 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
$scope.addRepo = function () {
$scope.currentRepo = {selectedNodes: {}};
$scope.currentRepo.FileVersioningSelector = "none";
$scope.currentRepo.simpleKeep = 5;
$scope.currentRepo.staggeredMaxAge = 31536000;
$scope.currentRepo.staggeredCleanInterval = 3600;
$scope.currentRepo.staggeredVersionsPath = "";
$scope.editingExisting = false;
$scope.repoEditor.$setPristine();
$('#editRepo').modal();
@@ -715,7 +733,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
}
delete repoCfg.selectedNodes;
if (repoCfg.simpleFileVersioning) {
if (repoCfg.FileVersioningSelector === "simple") {
repoCfg.Versioning = {
'Type': 'simple',
'Params': {
@@ -724,6 +742,20 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
};
delete repoCfg.simpleFileVersioning;
delete repoCfg.simpleKeep;
} else if (repoCfg.FileVersioningSelector === "staggered") {
repoCfg.Versioning = {
'Type': 'staggered',
'Params': {
'maxAge': '' + repoCfg.staggeredMaxAge,
'cleanInterval': '' + repoCfg.staggeredCleanInterval,
'versionsPath': '' + repoCfg.staggeredVersionsPath,
}
};
delete repoCfg.staggeredFileVersioning;
delete repoCfg.staggeredMaxAge;
delete repoCfg.staggeredCleanInterval;
delete repoCfg.staggeredVersionsPath;
} else {
delete repoCfg.Versioning;
}
+39 -5
View File
@@ -540,14 +540,25 @@
</div>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label translate>File Versioning</label>
<div class="radio">
<label>
<input type="checkbox" ng-model="currentRepo.simpleFileVersioning"> <span translate>File Versioning</span>
<input type="radio" ng-model="currentRepo.FileVersioningSelector" value="none"> <span translate>No File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentRepo.FileVersioningSelector" value="simple"> <span translate>Simple File Versioning</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" ng-model="currentRepo.FileVersioningSelector" value="staggered"> <span translate>Staggered File Versioning</span>
</label>
</div>
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</p>
</div>
<div class="form-group" ng-if="currentRepo.simpleFileVersioning" ng-class="{'has-error': repoEditor.simpleKeep.$invalid && repoEditor.simpleKeep.$dirty}">
<div class="form-group" ng-if="currentRepo.FileVersioningSelector=='simple'" ng-class="{'has-error': repoEditor.simpleKeep.$invalid && repoEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentRepo.simpleKeep" required min="1"></input>
<p class="help-block">
@@ -556,7 +567,30 @@
<span translate ng-if="repoEditor.simpleKeep.$error.min && repoEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentRepo.FileVersioningSelector=='staggered'" ng-class="{'has-error': repoEditor.staggeredMaxAge.$invalid && repoEditor.staggeredMaxAge.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing. Versions are automatically deleted if they are over the set maximum age or too many files are in a interval.</p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentRepo.staggeredMaxAge" required></input>
<p class="help-block">
<span translate ng-if="repoEditor.staggeredMaxAge.$valid || repoEditor.staggeredMaxAge.$pristine">The maximum time to keep a version in seconds, -1 keeps versions forever.</span>
<span translate ng-if="repoEditor.staggeredMaxAge.$error.required && repoEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="currentRepo.FileVersioningSelector=='staggered'" ng-class="{'has-error': repoEditor.staggeredCleanInterval.$invalid && repoEditor.staggeredCleanInterval.$dirty}">
<label translate for="staggeredCleanInterval">Clean Interval</label>
<input name="staggeredCleanInterval" id="staggeredCleanInterval" class="form-control" type="number" ng-model="currentRepo.staggeredCleanInterval" required min="1"></input>
<p class="help-block">
<span translate ng-if="repoEditor.staggeredCleanInterval.$valid || repoEditor.staggeredCleanInterval.$pristine">The interval to clean versions in seconds.</span>
<span translate ng-if="repoEditor.staggeredCleanInterval.$error.required && repoEditor.staggeredCleanInterval.$dirty">The clean interval must be a number and cannot be blank.</span>
<span translate ng-if="repoEditor.staggeredCleanInterval.$error.min && repoEditor.staggeredCleanInterval.$dirty">Clean Interval has to be greater than 0.</span>
</p>
</div>
<div class="form-group" ng-if="currentRepo.FileVersioningSelector=='staggered'">
<label translate for="staggeredVersionsPath">Versions Path</label>
<input name="staggeredVersionsPath" placeholder="" id="staggeredVersionsPath" class="form-control" type="text" ng-model="currentRepo.staggeredVersionsPath"></input>
<p translate class="help-block">Path where versions should be stored, leave empty to put them in .stversions folder in the repository.</p>
</div>
</div>
</div>
</form>
+8 -4
View File
@@ -47,11 +47,15 @@ func NewSimple(repoID, repoPath string, params map[string]string) Versioner {
// nil, the named file does not exist any more (has been archived).
func (v Simple) Archive(filePath string) error {
_, err := os.Stat(filePath)
if err != nil && os.IsNotExist(err) {
if debug {
l.Debugln("not archiving nonexistent file", filePath)
if err != nil {
if os.IsNotExist(err) {
if debug {
l.Debugln("not archiving nonexistent file", filePath)
}
return nil
} else {
return err
}
return nil
}
versionsDir := filepath.Join(v.repoPath, ".stversions")
+313
View File
@@ -0,0 +1,313 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package versioner
import (
"fmt"
"github.com/syncthing/syncthing/osutil"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
)
func init() {
// Register the constructor for this type of versioner with the name "staggered"
Factories["staggered"] = NewStaggered
}
type Interval struct {
step int64
end int64
}
// The type holds our configuration
type Staggered struct {
versionsPath string
cleanInterval int64
repoPath string
interval [4]Interval
mutex *sync.Mutex
}
// Check if file or dir
func isFile(path string) bool {
fileordir := path
file, err := os.Open(fileordir)
if err != nil {
l.Infoln("versioner isFile:", err)
return false
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
l.Infoln("versioner isFile:", err)
return false
}
return fileInfo.Mode().IsRegular()
}
// The constructor function takes a map of parameters and creates the type.
func NewStaggered(repoID, repoPath string, params map[string]string) Versioner {
maxAge, err := strconv.ParseInt(params["maxAge"], 10, 0)
if err != nil {
maxAge = 31536000 // Default: ~1 year
}
cleanInterval, err := strconv.ParseInt(params["cleanInterval"], 10, 0)
if err != nil {
cleanInterval = 3600 // Default: clean once per hour
}
// Use custom path if set, otherwise .stversions in repoPath
var versionsDir string
if params["versionsPath"] == "" {
if debug {
l.Debugln("using default dir .stversions")
}
versionsDir = filepath.Join(repoPath, ".stversions")
} else {
if debug {
l.Debugln("using dir", params["versionsPath"])
}
versionsDir = params["versionsPath"]
}
var mutex sync.Mutex
s := Staggered{
versionsPath: versionsDir,
cleanInterval: cleanInterval,
repoPath: repoPath,
interval: [4]Interval{
Interval{30, 3600}, // first hour -> 30 sec between versions
Interval{3600, 86400}, // next day -> 1 h between versions
Interval{86400, 2592000}, // next 30 days -> 1 day between versions
Interval{604800, maxAge}, // next year -> 1 week between versions
},
mutex: &mutex,
}
if debug {
l.Debugf("instantiated %#v", s)
}
go func() {
s.clean()
for _ = range time.Tick(time.Duration(cleanInterval) * time.Second) {
s.clean()
}
}()
return s
}
func (v Staggered) clean() {
if debug {
l.Debugln("Versioner clean: Waiting for lock on", v.versionsPath)
}
v.mutex.Lock()
defer v.mutex.Unlock()
if debug {
l.Debugln("Versioner clean: Cleaning", v.versionsPath)
}
_, err := os.Stat(v.versionsPath)
if err != nil {
if os.IsNotExist(err) {
if debug {
l.Debugln("creating versions dir", v.versionsPath)
}
os.MkdirAll(v.versionsPath, 0755)
osutil.HideFile(v.versionsPath)
} else {
l.Warnln("Versioner: can't create versions dir",err)
}
}
// Using keys of map as set
clean_filelist := make(map[string]struct{})
clean_emptyDirs := make(map[string]struct{})
err = filepath.Walk(v.versionsPath, func(path string, f os.FileInfo, err error) error {
switch mode := f.Mode(); {
case mode.IsDir():
files, _ := ioutil.ReadDir(path)
if len(files) == 0 {
clean_emptyDirs[path] = struct{}{}
}
case mode.IsRegular():
extension := filepath.Ext(path)
name := path[0 : len(path)-len(extension)]
clean_filelist[name] = struct{}{}
}
return nil
})
if err != nil {
l.Warnln("Versioner: error scanning versions dir",err)
}
for k, _ := range clean_filelist {
versions, err := filepath.Glob(k + ".v[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]")
if err != nil {
l.Warnln("Versioner: error finding versions for", k, err)
}
sort.Strings(versions)
expire(versions, v)
}
for k, _ := range clean_emptyDirs {
if k == v.versionsPath {
if debug {
l.Debugln("Cleaner: versions dir is empty, don't delete", k)
}
continue
}
if debug {
l.Debugln("Cleaner: deleting empty directory", k)
}
err = os.Remove(k)
if err != nil {
l.Warnln("Versioner: can't remove directory", k, err)
}
}
if debug {
l.Debugln("Cleaner: Finished cleaning", v.versionsPath)
}
}
func expire(versions []string, v Staggered) {
if debug {
l.Debugln("Versioner: Expiring versions", versions)
}
now := time.Now().Unix()
var prevAge int64
firstFile := true
for _, file := range versions {
if isFile(file) {
versiondate, err := strconv.ParseInt(strings.Replace(filepath.Ext(file), ".v", "", 1), 10, 0)
if err != nil {
l.Warnln("Versioner expire: file", file, "is invalid")
continue
}
age := now - versiondate
var usedInterval Interval
for _, usedInterval = range v.interval { // Find the interval the file fits in
if age < usedInterval.end {
break
}
}
if lastIntv := v.interval[len(v.interval)-1]; lastIntv.end != -1 && age > lastIntv.end {
if debug {
l.Debugln("Versioner: File over maximum age -> delete ", file)
}
err = os.Remove(file)
if err != nil {
l.Warnln("Versioner: can't remove file", file, err)
}
continue
}
if firstFile {
prevAge = age
firstFile = false
continue
}
if prevAge-age < usedInterval.step {
if debug {
l.Debugln("too many files in step -> delete", file)
}
err = os.Remove(file)
if err != nil {
l.Warnln("Versioner: can't remove file", file, err)
}
continue
}
prevAge = age
} else {
l.Warnln("Versioner: folder", file, "is named like a file version")
}
}
}
// Move away the named file to a version archive. If this function returns
// nil, the named file does not exist any more (has been archived).
func (v Staggered) Archive(filePath string) error {
if debug {
l.Debugln("Waiting for lock on ", v.versionsPath)
}
v.mutex.Lock()
defer v.mutex.Unlock()
_, err := os.Stat(filePath)
if err != nil {
if os.IsNotExist(err) {
if debug {
l.Debugln("not archiving nonexistent file", filePath)
}
return nil
} else {
return err
}
}
_, err = os.Stat(v.versionsPath)
if err != nil {
if os.IsNotExist(err) {
if debug {
l.Debugln("creating versions dir", v.versionsPath)
}
os.MkdirAll(v.versionsPath, 0755)
osutil.HideFile(v.versionsPath)
} else {
return err
}
}
if debug {
l.Debugln("archiving", filePath)
}
file := filepath.Base(filePath)
inRepoPath, err := filepath.Rel(v.repoPath, filepath.Dir(filePath))
if err != nil {
return err
}
dir := filepath.Join(v.versionsPath, inRepoPath)
err = os.MkdirAll(dir, 0755)
if err != nil && !os.IsExist(err) {
return err
}
ver := file + ".v" + fmt.Sprintf("%010d", time.Now().Unix())
dst := filepath.Join(dir, ver)
if debug {
l.Debugln("moving to", dst)
}
err = osutil.Rename(filePath, dst)
if err != nil {
return err
}
versions, err := filepath.Glob(filepath.Join(dir, file+".v[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"))
if err != nil {
l.Warnln("Versioner: error finding versions for", file, err)
return nil
}
sort.Strings(versions)
expire(versions, v)
return nil
}