Simple file versioning (fixes #218)

This commit is contained in:
Jakob Borg committed 2014-05-25 20:49:08 +02:00
1 parent dd971b56e5
commit 3d055bbb79
17 files changed
+350 -120

No files matched your search

+2 -2
View File
@@ -18,7 +18,7 @@ func init() {
bs, _ = ioutil.ReadAll(gr)
Assets["angular.min.js"] = bs
bs, _ = hex.DecodeString("1f8b080000096e8800ffd43c5f73dbb693eff914882e2da944a69cde4de7c68ad3699db4e7cb3f4f9ce4c5f503254212638a5448d0b6c6d577ffed022009100045db499b6a26b10d2c16fb0f8bdd05a0f1e3cf4512a78c4cf3ecaaa0f901617949476496a52c4e4b5afdbd4eca02ff89bfc9e3f183f1e345924dc3843c3a20f3302900284c176512e6f26f047ae095f04bc1f278c6bcc9830797614e8a4d3a63cb385d90c36a44b0caa232a1be57f7792372763e9cf001659e4c43407348bc9c161c4f0d1720a179962434f7bdd3aaf588e509609897f0779ca5c47f54ccb23550f868c9d87a486e1e10f820ee754e2f5f840c91ef4fead60565ef5e4113f2dab4e2e461ce04e59c45a0043b05762425a57cc202006eb69356e73c5e98edabcdf10be4ccd35ad32ca288e4ecdc82e438454635f2643fcdf32cb78c2b284d5f629f3913489e262659395d671517bc7d3c26a7a0c6745190299d673925d32c4b0a9264d905b43046737d46c63830d0c2db6fe2e88078afe382d11410817680c319d80ad71a39c93396cdb2840808f26b1481b80b5a0020dbacc10e3d46af19fc25b5200c713b5290bf09af4f691abd9aae0b05fdbb922d32d4d97b54f3eb781533e2bf8a7f1b17c306775aaea634efc4fe1ef085e9710a8c5e86c9a93283e8215517f16f8d595a8e1db9ecbc3bfe93300f618124efe997128054e180c808080846a611975103d21f3f20395ac24aa62dc923f2dfe38412d12b14d05ff4ca0c7f704ff36b9abe4cc36942236512d1455ec460779734df3488d13e3be97e9dcd1c4879cfbd709e6439732324b2bbbf8c4fb1fd37e1a3d5b583cda46957c9d4c67f3c494f4c3e450bc14efbe073ddc32dcaf8d4beb0e57a5515f3f1f83e8bf9a3ce2862fbb5644b0adbd22ce44efda3c6f24e842761515c6579d48d54819288d74d4b27b51f5eabcb161ac8ff7df870724ac0571298a8970d55e2ae372ed8884ecbd98cd288467eb56fe1279e13ff21dfa6d4564555711a337f38d1bb7cefbf52ca809b0bbe1b784374ff61e27bcb38a25e0bdadc04f1b3d54868b6c45b92d10cec2602618b65c9a2ec2aed86b4edce3ad15b53b8bf87e09f4cc9bad9ca292bf3d4250ebb425c52bf9986b38b28cfd66014301fd82098c505dd4cb3308f6410b57528a58bc17a0b9f031f4b04aee32095361e0b0580d0af02ac27c41b171b58b22ba0b140bb2b0abf191c852c6c33a71be8c4660202239081e3159287225cf1edd449e49582d471cdefefa69f61670c406685af062ec30056ddcb70b65490c791a118ab007840f40ba239f4a081a63368f8f8fef8285badb3145c0562ea2b1e450c1cef591c9d1b926873a5fe6ea75189347b6b0a23d834bb82d931de8508f3ca1f8e34081641a78f307b755c3c2463f2747f7f5f878c23e9a8aa8f1246c3789d357480203312a7c42522eecdb0335886c5bbab14a2c135cdd9868bda028f9f2a439918bd5ba385c1966bc78293a252c05141f002e4bf09d9325885d7fefe88fc2f792c64c9218ed3df368c161f32065bf99e25e837a050782c1a9a146a336725db353584687de6d6c09c936f09ec75b325f1a94bb48650f6fbb26001d4b5b1b579083d716abb895d8b41243dbdd7413b55b24fb79d3c68a743106bb1b2d0dc2936b7f70edce5b3b9beea110ed6fd21645f651ad1799c421866df5a2074482f52dced54efaecd60a206454162006bec214ee1c47ccab2f51a66b662e6a939136bd843cf679986f737a32b70fef36cff3c60d94798203f02d580337f223a402fd382e5fed3e144e7430e3f24034c0161b31d90bffe224deb7194d0419b17d1fd04ba893f68a8c482c009cd67e09fc305958a7942063f0c07566ea5481486da9be73a3b4a20f4fbf6fa8ed379f60d941d61ce957f355d6b0af362508d7366b90e3d5794d4a02984de9d98d679bc0a21ffb261b2884f57a16e12df5c8fb04bba18b64857d4ceb8b7e6f8f7fb61d534b89e31e00a2060afb09a092e2b31c5d806a0d030698b95ef45f32483f80ca6b13b452c52599c22361fcdb5c819a945270f7096ad4b0e08dec2cfe317e7bacd21605b36557b80315942f9b4284410856d4bab1deb9ab08c473f5e2bf82214eb95ee91d23f111fd74a7b66d8837e18b63176f81c8f970138fbb52fb648f678865cfd1be49a5ddc569e3235b98dd05610eb156e6999befabb1597e91e7bc9ccf4853b65a6ba4653645895f99b252609e3429345a12ed7fe4b87be1591ff632cb49c40e736e566e513dda9062446a7941b9a7284e02053425cd2bc00dc36fabea5789218f6de4feeb977e819b6db082736a4a3b38b1cac30a3e12726ea010a604818552b0c486f356dda889208ac132ddb401625de20a1e9822d21e2224f1d1cd7f14107a3121b44ca4e63781bae4c7e2dd620a315d9db3344190c5c114aad6798de315805e9e051b7982af6878cf6677b10c19671f1d6c6785bc5a9b084d630bfa530452e7d85e29722e1e283869d22da259f3ec2e92d191ac54ca9b6db25331e43a876414948b0be88310a8cddd4dddab9612041deadab54bb3a7ec531be0e2b81d4ecdb860b4be89d780040c581e55cc953df3a28af8f0fad117e78497b4ba82873f82f5b5171f63ce3675291ee07451ba07a58b344bf946152d88533ea94ef10b2596337179f1de841660ed4ba34e3b92f491e5a6b1c3ab5dd7a6ad16eade436e4f5c225887523aacfb15bc56c270781384f929183b6639950a72c0f8a751233df1ba1a9856b651fb856f681eb804168058e84b48beda2deb4ce0aa3fa0ad3818dfeffe9bbb741c14fc5e3794b08c311b959d23082ddef80dc7847590a34b1bd0fe09f3cb0f3700da48933a7f1e7224bbdad56017ed0bd62067800327095aaf8d985733d68e724fad98ee360e6b6e7140eb149cc9ecba558ae35b458ab0e82eec49b9d28e56cc92c1fda4e728cc3a8db4a677b77fe7147b00644ad00a1425be639d89c1cf128a0d76082917fb31d55bbb4490a4e01e27b790d6bc82a460dec9426737166d08a4db5d0d4e4b7212ca8cf8661bd92c33a7ca89b83cf599cc2122616bb41e097404996078f608d9ce49c682d2640955562bbd386a36b203603524df0305b1c7d510e568b6576e5d97185d10e64362ddea8f202f2a34d1aae80fe6d0f4db6dcac55957698bf5fd01185c48aee14b631a37134cc8f96ec22d97db66b9343bf24c31a2156ebe361b340d4a5207a7b780a0ed9a643d969f9891b0634e2d44d3b19b59f86f03e7e94d342ddeae8c1ec3d186e336d2aa03b70f847f6ebf61e053169a7d956b90c78b91189b2948e483cb92da3bd4cbf4e0e2db237a06adfddcee0c002927046fd311903c11e4c57b7ec552ddab94f154e1933d4aecbe6e7ef18aed593a0244d4189b3677e304962f24c5fb822a9878e274f6ca53d15f62c3e570b2fba883a0efdabc10dc713035452aeefb3d5679ad3f0a247c94f9ca00326c702171cafcb62e99b7bLine truncated
bs, _ = hex.DecodeString("1f8b080000096e8800ffd43c6b73dbb6b2dff32b10ddb4a41299727aefed9c89e2745a27edf1c9cb1327f9e2fa03254216638a5448d08f71f4dfcf2e00527852b49db4a96612dbc0625f582c761780c60f3f55599a33322d8b8b8a964f082b6b3a22b32267695ed3e6ef555657f84ffc4d1e8eef8d1f9e66c534cec88327641e671500c5f9699dc5a5fc1b81ee0535fc52b1329db16072efde795c92ea2a9fb1459a9f92bd6644b42c923aa361d0f60523727c329cf00175994d6340b3478292561c4f0b1721a3659165b40c83a3a6759f95196098d7f0775ae4247c50cd8a1570f860c1d86a48aeef11f820ee5549cf9fc70c91ef4edad653cadebe84269475d38ac4e39209ceb988c009760aecc84a4e39c10a00aed713a3739e9edaedcbab83e72859a0b5e6454211c9f18903c9418e826aecc97e5a9645e91857519abfc03e9b12689e66365b255d158d14bc7d3c2647308df96945a6745e94944c8b22ab48561467d0c2182d758a8c7160e085b75fa7c91312bc4a2b46734004b30312cec056f8ac91c3b260c5acc8888020bf2609a8bba21500b2ab15d861c0e82583bfe42c08435c8f14e4afe3cb239a272fa7ab4a41ffb666a705ced93b9ce657e93265247c99fe36ae861bdc79bd9cd2b213fb3bc017e70739087a1e67470a05d1439a2e12de18b3b41c3772d9797bfc877119c302c9ded1cf3500a9ca01951150108ccc13aea30d487ffc80647f012b991a9a47e4bfa71925a2574c407fd52b14fee09ee6d73c7f91c7d38c260a11d1459ea76077e7b4bcda2046fbece4fb5531f320e53d77c2795894cc8f90c8eefe3a3ec2f6df848f56d70e36934dbbcaa636fec3617e68cb295a0876ba079fe81eeeb44e8fdc0b5bae5775623e1cdc65317fd005456cbfd66c41615b9ac5dca97fd044de8af030aeaa8ba24cba912a5012f16ad3d2c9edfb57eab28506f2eff7ef0f8f08f84a02847ad950a3ee76e3828de8a89ecd284d681236fb167ed23909eff36d4a6d55a62acd53160e277a5718fc4f4e194873c677836088ee3fcec26091263430a0ed4d103f6b8d85cd967843363603bb9940d86a51b3a4b8c8bb215dbbb3cef4da56eeef31f8275bb37eb14acaea32f7a9c33d213ead5f4fe3d95952162b300aa0073608667146afa6455c2632885a7b26a54bc0760b9f831c0b046ee32095371e0b4580306c02ac4724185757b06497c06385765755e1667012b3d8144e37d089cb0404466003c72b2c0f45b812bab993c89b0952c76d7e7f3bfd043b63043aab4235701946b0ea5ec4b385823c4dac89712a800744bf209abd001a683e83860fef0ef68be5aac8c15520a6beea51d4c0f11ea7c989a509532af577378f4aa4d97ba63082cd8b0ba08ef12e449817e170a441b0043a4384d969e3e2211993c7bbbbbb3a649a4847d57c94301ac6eba2a103049d9134273e15716f869dd122aede5ee4100dae68c9aeb8aa1df0f869329489d5bbb65a186cb96e2c481427051c15042fc0feeb982da2657c19ee8ec8bfc843a14b0e7190ff76c568f5be60b095ef38827e0b0a95c792a1cda146b9a8d936d210a2f5a1ad817989af09ec75b30509a94fb5965276fb8ae000d46763edf2107ae264ba896d8b41243dbdd781992ab9c9ad27f7cc7408622d56579a3bc56673efc05dbe98ebab1ee160ddef41f655e7099da7398461eead054287fc2cc7dd4ef5ee1a051b354c142406b0c6ee23092fe62356ac5640d98999a7e64cace1003d9f830cefdf8c6ec0f9cfe3dd9388151f8040b90f5303cefc91e880799956ac0c1f0f27ba1c72f81e19600a089bed807cf94236ad07494607a62ca2fb11749370b0e1120b0287b49c817f8e4fa99c984764f0c370e09456aa4411c8dc3c57c57e06a1dfb79fef349f17df60b213ccb9caaf36d7da8405294c8d97b25c87812f4adaa0a9c4bc7b31adca741943fee5c2e4509f3e85ba497cf379845dd227b043bba276c6bd35c7bfdb0fab3683ab1903a90002f60aa799e0b21224c62e00858789a956be17cdb302e23320e3768a58a47238456cde9f6b9133728b4e1ee01c5b971c10bd819f07cf4f749b434053374d7b8431594639595422a8c2b5a5b58e754558c1a39fc008be08c57aa57fa4f44f24c4b56252863de887a189b1c3e704bc0cc0c56f7db143b3073394ea9fa0d7e2eca6fa94a9c94d94b68458aff26bcbf6d5dfadba6cf7d84b67b62fdcaa33d535da2ac3aacc5fac31c918579a2c0a75b9f65f3ae65b51f9df2682e1043ab729bf281fe9d6694066744eb9a12947081e3625c4392d2bc0ede2ef5baa274b61effde8a7bd659e61bb4d90b0a51d5d5c946089190d3f31510f500043c6a85a61407e1bb2f9469544609d68d9068a28f14619cd4fd902222ef2d823711b1f74082ab141a4ec358637f1d296d7610d325a91bd3d4394c1c017a1b4f30ce43d8355900e19758b69627fc8687f7607116c91566f5c829b539c0b4b308685c684297ae9ab94b01609171f34ec54d136fdf4514e6fcdd024654ab5ddad99f11842b5334a6282f5458c5160ec55dbad9d1b4612e4edaa49b59be3571c13eab01248cdbe5db8b084de890700541c58ce9532f5ad83f2faf8d019e1c7e7b4b786aaba84ff8a251567cf337e2695e87e50b401aafbad48f4731d67955b39a34efd0e219bb57673f1d9821e74e641ad6b339d8792e5a1b3c6a173db3d4f06efce4aee86bd5eb804b37e44ed39b651ccf64a1089f3241939683b960d75c4caa85a65290b83119a5abc52f6814b651fb88c188456e04888596c17f5a6555159d557200736fa9fa3b76fa28a9f8aa7734309c311b95ed03881ddef09b90ef68b1c78623befc13f0560e7f10a5813674ee34f5591076bad027caf7bc50cf00064e02b55f1b30bef7ad0ce49f4b31dcfc1cc4dcf293c6a9398039f4b715c6b30446b0e826e259b9b29e56cc92e1fba4e72acc3a89b6a677d7bf971477006444680d0a0adcb126c4e8e7810d14b30c124bc5e8f9a5dda66054980fa5e5cc21a72aa51033ba2d95c9c1918b1a9169adaf26e188bdab36158af64af0d1fdae6e85391e6b08489c36e10f805705294d10358238725675a8b0970ca1ab5dd6ac3d16720b503524df1402d4d3e2b07abd5a2b808dcb8e2640b32d72c5eabfa02f693ab3c5e02ffeb1e3369b859e754ba61fe7a452714122bba55d91645eb68981f2db955b2fd6cd7a5877e498633426cd6c7fdcd02519782e8ede12938a4c987b2d3f213370c68c4a99b7632ea3e0de17dfc28c7406d74f410f60e029b42db13d01d38fc2dfbb5b947414cda69b64d2e035e6e449222a723924e6e2a682fd36f934387ee2da8d6779b191c584016cf68382663603800726dcb4ed3a29dfb34e19445a1755d2e3f7fcb70ad25829ab41525ce9ef9c12449c9537de18aa41e3a1e3d7295f654d8e3f4442dbce82aea38f46f066f249e58a092737d9f6d3ed392c6673d4a7ee2041d307916b8907855578bd0deLine truncated
gr, _ = gzip.NewReader(bytes.NewBuffer(bs))
bs, _ = ioutil.ReadAll(gr)
Assets["app.js"] = bs
@@ -63,7 +63,7 @@ func init() {
bs, _ = ioutil.ReadAll(gr)
Assets["favicon.png"] = bs
bs, _ = hex.DecodeString("1f8b080000096e8800ffec5deb73dc3692ffeebf02e6def9512772643bd9db924753e548f646b5f1a32cfb725ba97cc09098212290a00150f29cacfddbaf1be07bc81135926c6d920ff69004d0001add8d5f371e9ade3f7c7bf0e19fef5e92d82462766f8a3f44d074b9efb1d423e9d2a759b6efe9551a9a98a74bfb2994a9515208a6f6bde332e5c028e1915050adf73dcc24243df19024a3d1ec1e21d384194ac2982acdccbe979b85ff37af4e888dc97cf629e7a7fbdefffa1f5ff80732c9a8e173c1802cd4c8522875f4729f454bd62897d284ed7ba79c9d65529946d6331e99783f62a73c64be7dd9213ce58653e1eb900ab6ff24d85d2314311d2a9e192ed306adb56c3437b1546b39044f4f8862023806c926cc0de121528a155bec7b0b7a8aaf41068c9cddc302861bc1661513c917727e0e4ffa8d8cd81ba8e9d1e38b8be9c4e5aa2a70c4e6521a6d14cd26a1d693ea2d48781ac017af68875909a663c68c6ba17d27669541270cfb6cb0b04d21642ea31539b78f8464348aa041fe5c1a23933df2dfbbd9e7e745da023aed2f68c2c56a8f783f3271ca0c0f2979c372e6ed90eac30e79a180db3b44d354fb9a29be70242eeed99f5cfc57fc7d556342d592a7be91d91e79127ccf9256de001beb2732953aa321ab4ab5daf29aa542ee90d732a521fc1ec8544b10c81de21dc85c71a6a08967d0c28a4ca70a0ac286d21db154b36866ace81a3543d1594f457e0da72e604086532bcad146cabda915e5a8e2c25caa8829c7bb54a69d7e09b9941d36ef91dde7ed916e7cb164fcefeb01cfa4e6a8117b2853a093a7dd0ab8367e2afd790e46c15455d9cf56e07c1438d7b44e41e898c893b42a13719d090a63c9539075e6cf850c4fca7680683b4d06792ce5a3121cc597b101d1a913e6343c592a99a711d62215347f397ff4f4d95f77c8d3ef76f1bf278f9fb739a868c473bd479e353a5ff2e749f6997c577f2f19f9143e3f2d3f5f74fb056296061105c3d19173c116d0dadd5ad05bdd7bb25b7fb6924f055f22ffb193cf2fe755c9e0d210aff317c782dce7095a4d9a9a1e4d20266eabd919732cfe6e77b7377bb4663d5c279f82ede8a90aacd1c44a074c1213374bdc9b5ad1b7a6f1beef930f32835154c4f7ddb7949e56530c3dc514f783925f3e466c41730133014c51cce6e34b6aedb93373d3885744d07c53609d2ad2d040c280b5ebf0e78aa691379bf26459a6a04a7944ab102dac8f6ffe93a77fb3869db889c77bf614ccbee5987b9ecc4865e7a73aa14294c4621e816efb9fc110b7eb77362f370c6aff02cc82a459ef0461c9cd8a1c654f72d1e847c99dc6a395a5aae37672290b444a66913c4b1ba9904e8b99e72f5e371ff07fb9c4891a25bd786952791971f3209debecf9745ef19e2a9c94a69339fca3ad8aea965715242ccd5badb1ed9d556d72c044f0f00426e7287acf32f9e831506fb2732956598c1330a99efc38c291758c7bc01268e18b2822581c4c9e542b6cda7402358dae1a476654d5d0fd33e658d0a91e490c565cb1869ff20825f70acd63300ec7cc1810413daa8da15c76da5716bf2263f878be7c522164ed561bcb337274782b5cd1716e50c846b54e2e166b4d73c5afc811c5b4a1ca8c149505648f3b15bf7714faea9d4e72d1d0ec466a9d329d00b32c289c8045700676c036bafc68915f71a50d98d6b31d2253b1228073cf52c217246521d39aaad57352b48b9c519522a82dac77411e38c08115f781fe822f8f52348a954101c2b5296e3546f8490436b669af1ae9c0142688fddf2faa6ddbae6e5e1f271c9b6b1a3f6ba758b4edcdca5ebc612c6211cc50cf6615c786c9e2fcd5b553d9ec43cc88eb6faeec5c4462aac99c310008f49445640e9e422a0da121a02b0a063fa8270b92e4c8f2a239465699608266c0f7b336e9603ac9daa27069a3115136e6c0221bb4c9404b9dafe05eaa719a9b94c03f5f27f6a79873490600b098576e47d25d333a0d6dca8960542df867af67acda1f5aaf8d97e2714de2d93217803740403bf2dc92dc921e16ac27118b84c923042988881ed7147aa5fcaf9b84dc47449b798447c8d8a206cef466712f54e3fc1c8b1c60c2237c0a8e0e013fd8b18257468da3098092e0ef4fd06c1cb541767754a9950f720e6856279b45162de0007c1034d3259ec80029a08ffd97567f4b30821f8b8e4177a037ebf43bb0ee321c400a6a875cb1d08edf1762fdf977d4c417179791af958034909da3796ca8c99bbc6f63b6a6b4d23576a2096a0f4557c68bd12965a3c596f650942c2615afd7aa1b63dbba199db30a1a9c81ef0d7e62ff60387fa1598474bcdce25d1bc53344bf3d54908ef316fad230550d2561627cb92d32b40b7e1a2a8d5804886ca8216ac1f8026d374604ca47838d9f0cb7fefa1d5b4881ceaecc58dae9e02b9b729d9e554af32d3b18ca041c862eb4c619355632e5ff6727ca6bf4b2478dbf55579742cebb60f9eff08d8a9617b3555713c0e1e297a297bf064b4bf615174c8345a4e28caef49b3c993375714116f879870c14fa61656ca1394f298ac60fdf8e61b14cbafcfa4986b7c02e8154afc82d5be60e312b14328f7cf46e84a45d4ff92d8056b9b040f566389602d4be22c3b0c81de21746e03a5c7a4d3580ebeb4ad760fe127c146e9565cc7b00646fc13bf366ff647a08610c10b8dfa1f0465e46e0db713b077bcec213d615cca3652a1523ef984ab8d660eaf5edf3dcd58955eaedd9de22729739af63c0e43e156b532c7e8fc8cfdcc45b9a044b59dbf81db264ebb9155286d0212421baec03dd6b78da7eee47f71ec675fafde14cf1044c91371082ab7b37c22306901672d1e13306545ddca74f42fafc822ddce1a6338bd1bb861b6b1970637e6c0ac4c739b05570bfe1b162e983c5129dd65fcad0f8a3c7bfde1db7d575aff457f10d1cb3a2d501b6d6b9ad57432ec491b06b00052dab2bd7741b875a7789f708ccffd3815c7720d7e2582f5e938f868beb79207a05882209e0e76e801e43f589eef4f4e0ddc79beb6998e5302786a04d1d6008af2978628a8abd271717ff794771f261f199bca7866dc909a82405bf1ab1cc2f0f8d34543cfc35e0e93c4319481888797871816f8f06f21ea516267fc0d7a6d83cfed64ccbb31e967dcc6e85613237e33906eecdcdb3acc47b856443935fa4a90420cbdefe83dcdf2739d8af054f074dd668e6e25e8c58aa6e80a7ac8d1c3375ba65a4e7524c5baec5e1b6075b50e721ae0e799b7aefcddeda6d0c6381f35a25114d974c5575dc1fa864b11853cbb7b4a6dda8e3ff30a5b7b7a1a7aef4ef11475ba457829f6f0ea547adbfd8d6daf5971ab40da1596962a6b08ffa2e2dc28c44b3db2cc2747744f4d0e845bddbaec820852294db84d0b7b422b325b4fe1357af47d978da8db2bd8822e8dc76711e372648a12506bfc7c58b0189ff43c0e6b6e28d42cfdd227f6410dde5c5182cdd2d732b90Line truncated
bs, _ = hex.DecodeString("1f8b080000096e8800ffec5deb73dbb696ff9ebf02e1ddcd63d6a49ca4b77bc7913593dae9ada7cd63e264bb9d4e3f402424a226090600ed681ddfbf7dcf01f87e48b42c27be6d3fb41609e000382ffcce01c04cef1fbf397affcbdb9724d47134bb37c53f24a2c9f2d061894392a54bd3f4d051abc4d7214f96e6952f122d45143179e89c1625475a460ef123aad4a183952241cf1c24c96830bb47c834669a123fa452317de8647ae1fec3a90a42ad53977dccf8f9a1f3bfee8717ee918853aaf93c6240167a6409b43a7979c88225abb54b68cc0e9d73ce2e522175adea050f747818b073ee33d73cec119e70cd69e42a9f46ecf089b7df211430e54b9e6a2e921aad4e359ae950c84e8d88276744b2083806c5dacf34e13e520a255b1c3a0b7a8e8f5e0a8c9cddc3069aeb88cd4a2692cfe4f2127ea9d72260afa1a7478fafaea6135babecc0129b0ba19596349df84a4dca272fe689076f9c7c1c7a15311532a6ed08cd33d1ab1426a1d9278d8d4d09217311acc8a5f949484a830006e4ce85d6223e20ffbd9f7e7a9e972d60d2ee82c63c5a1d10e707169d33cd7d4a5eb38c397ba47cb1475e48e0f61e513451ae62922f2c89ab7be64f16fd57f8f7b2c798ca254f5c2dd203f2c4fb3b8b1b753d1cac1b8b44a894faac6cd518cb2b9644628fbc1209f5e1ef9148940085dc23ce91c8246712867801232cc9b4baa0a06ca8dd014b140b66daa8ae9633549d6e29f26bb8740102192e2d29076b29f796969483920b732103262def1291b4e61589a568b1f980ec3f6f4abaf6c69071ff5e093c158aa3451ca04e814d9eb73be04abb8970e71938055d76655e1b857351e1ecd05a0d6162511627659b80ab34a2204b9e80ae33771e09ffac1807a8b6b564d0c7423f4ac5917c196a509daa604efdb3a5145912602f42c2f097f3474f9f7dbb479e7eb38fff7bf2f879938392063c5307e4596df2057f9ea49fc837d5fb82914fe1f5d3e2f5557b5ea066891750701c2d3d8fd80246bb5f297a637a4ff6abd746f369c497c87f9ce4f3cdbc2a185c38e22e7f5116e43e8fd16bd244f75802d161d3cc2e9865f137fbfbbdd5838ef7b0937c0abea3a72bf04613a31db0484cec2a716f6a54dfb8c6fbae4bde8b14a42889ebda77093d2f97187a8e25f60f6a7ef133600b9a45b012c012c54c3dbea4c69f5b37370d784904dd3705d6c9bc0c1d2408acd9873b9734099cd994c7cba2044dca214afae8615d7c729f3cfd8771ecc42e3cceb3a7e0f60dc7ecefc98c947e7eaa621a4505b1900760dbee2770c4cdfeadcfcb3483de3f03b3a068d6bb401872b3bc4631932caacda3e04eeda7d1a572e26671291a0452a481b8486aa5504ef395e76f4ebb1ef07fb9c4851a353d7fa853791970fd2099abf4f9745ef29e4a5c94a69339fc471b1d55232f3b885992354663c63b2bc7648149c4fd33589c83e01d4bc5a3c740bdcece65b44a435c8049f9cb0d0394ac65dc0316c3085f0401c1e6e0f2845ce1d0a613e86974d72899515dc3f42f986541ab7b2431d871c91a7ece03d4dc6b0c8f811c4e99d6a0826ad4187db16c8daf687e4dc6f0f17cf9287da8daee361417e4e4f856b8a2c24ca3928d1a9d582c3a43b3cdafc911c994a6528f549505540f5b1dbfb314fafa9d4eb2a866d9b5d2aa643a0166195038018f601dec806fb4f5d1237fcfa5d2e05a2ff68848a215019c7b9110be2009f3995254ae9e937c5ce482ca04416deebd73f2c0010eacb80ff4177c7992a0532c1d0a10ae5c716330911b07e063ebfeaa560e4c611131ff77f36e9bbeab5dd7c505c7d49a86cf9a25066d3bb36216af190b58002bd4b359c9b161b2b87eb5fd543a7b1f3262e79b49b31691902a32670c00023d67019943a490084da80fe88a82c3f7aac582c419b23c1f8e16652558a019f0fda249da9b4ed2a62a6c1c3422cada1a985783316918a98d15ec4329a7b94e08fce7aad8fcc9d75c920200ccd795dbd1743b8cd640eb7a12312a17fc93d323abe68bc663ed21ffd9d178b6cc22c01ba0a02d7d6e686e410f1b568b8841c2e411821444448f2b0abd5afeed3a257711d1a60ee1013236ef8133b55edd73d3b8bcc4264758f0087f7927c7801f8cace091516d6902a024f8f72718364a6d90dd2d536ad4839a0396d5aa6690450338001f229aaa024fa4801430c6fe5b63be0518c197f9c4603a309b2efd16acdb8403484eed984be61bf97d26269e7f4b757875b5897c6504a486ec2ccd534d7556e77d13b3d5b59576d8892ea8298ab68ee7d22974a3c196a6280a169392d79deec6f8b676451bac8205a7107b439cd82f0c1b2fd49b9056949b3f2b2d798ae8b7870ad2b1d1425f1996caa1222c0c37fb224ddbe0a766d2884580c89a1e82068ccfd1764d22d03e181cfc6478f4379fd8424418ec8a9425ad097e6f4a6e32b3d268bee6047d1143c0d086d6b8a2865224fcffcc42798359f698f1d79aea3212f33658fe27bca351238ad96aaa31e0f0e8d77c96bf794b43f67b1e31051e91461774a55e67f19cc9ab2bb2c0d77b64a0d1772b6d1acd79425135befb7a0c0b45dce6d74fc2bf05764548f59adc326dee10b3fc4864818bd14d24683b527e03a0552c0c50dd0dc71280dad7641836b943fcc20c5c8b4bafa802707d53ed1aac5f808f3cac328c790780ec0d4467ceec17a68610c60081fb2d0aafc526025f8fdb19f873e69fb1b6629e2c13211979cb64cc950257af6e9fe7b64fec526dcff60691bbcc791502267769d45962f17d407ee63adcd22518cacae4ef90255bafad5032840ea108d1651fe8eee069f3ba1fdd3b98d7e98f8753c9637045ce400aae9add888818409acfa3169f31a16af33e7d1ad217176c110ed78359ccded5c258c3809dc5b109101f17c096c9fd5ac48aad8f164b0c5a7f2d52e38f1eff7677c2563bbd225ec52708ccf2517b385a1bb65e0fb9104bc2ec01e4b48caddc306c1c1add86e81198ff5700d90d203b79ac17afc807cda39b45206a058822f6e0cfdd003d9aaa33d59ae9d1db0fbb9ba99f66b026fa604d2d60088f0944629246074faeaefef38ee2e4e3fc35794735db9213d04902713562995f1f6aa169f4f0378f27f314752066a0e6fed5153e3d1aa87b921898fc1e1feb6af3f86b332d4b7b58f621bd1586894c8fe7188437bb675981f772cd8621bf4812014096bdf991dc3f2419f8af054f065dd668e6e2598c50c87682a7e88d9c3279be65a66723a62df6e2f0d88369a8321f77879c75b377666fcc3186b1c0b9d34940932593651ff7073a592cc6f4f235bd693bebf83f4caaed7de8b96dfd47c4d106e915e0e7ab43e951fb2f66b466ffa5026d436856e890499ca3ba4b9b3023d1ec369b30ed13113d347a51efb63b3248214fe5d621f42dedc86c09adffc2d5dd2c1b4fda59b617410093db2ecf636582141a6af047dcbc18d0f83f056c6e1ade28f4dc6ef26706d16d5e8cc1d2ed36b7Line truncated
gr, _ = gzip.NewReader(bytes.NewBuffer(bs))
bs, _ = ioutil.ReadAll(gr)
Assets["index.html"] = bs
+2 -1
View File
@@ -24,6 +24,7 @@ import (
"github.com/calmh/syncthing/discover"
"github.com/calmh/syncthing/logger"
"github.com/calmh/syncthing/model"
"github.com/calmh/syncthing/osutil"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/upnp"
"github.com/juju/ratelimit"
@@ -498,7 +499,7 @@ func saveConfigLoop(cfgFile string) {
continue
}
err = model.Rename(cfgFile+".tmp", cfgFile)
err = osutil.Rename(cfgFile+".tmp", cfgFile)
if err != nil {
l.Warnln(err)
}
+50 -7
View File
@@ -27,13 +27,56 @@ type Configuration struct {
}
type RepositoryConfiguration struct {
ID string `xml:"id,attr"`
Directory string `xml:"directory,attr"`
Nodes []NodeConfiguration `xml:"node"`
ReadOnly bool `xml:"ro,attr"`
IgnorePerms bool `xml:"ignorePerms,attr"`
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
nodeIDs []string
ID string `xml:"id,attr"`
Directory string `xml:"directory,attr"`
Nodes []NodeConfiguration `xml:"node"`
ReadOnly bool `xml:"ro,attr"`
IgnorePerms bool `xml:"ignorePerms,attr"`
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
Versioning VersioningConfiguration `xml:"versioning"`
nodeIDs []string
}
type VersioningConfiguration struct {
Type string `xml:"type,attr"`
Params map[string]string
}
type InternalVersioningConfiguration struct {
Type string `xml:"type,attr,omitempty"`
Params []InternalParam `xml:"param"`
}
type InternalParam struct {
Key string `xml:"key,attr"`
Val string `xml:"val,attr"`
}
func (c *VersioningConfiguration) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
var tmp InternalVersioningConfiguration
tmp.Type = c.Type
for k, v := range c.Params {
tmp.Params = append(tmp.Params, InternalParam{k, v})
}
return e.EncodeElement(tmp, start)
}
func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var tmp InternalVersioningConfiguration
err := d.DecodeElement(&tmp, &start)
if err != nil {
return err
}
c.Type = tmp.Type
c.Params = make(map[string]string, len(tmp.Params))
for _, p := range tmp.Params {
c.Params[p.Key] = p.Val
}
return nil
}
func (r *RepositoryConfiguration) NodeIDs() []string {
+20 -1
View File
@@ -405,10 +405,16 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
}
$scope.editRepo = function (nodeCfg) {
$scope.currentRepo = $.extend({selectedNodes: {}}, nodeCfg);
$scope.currentRepo = angular.copy(nodeCfg);
$scope.currentRepo.selectedNodes = {};
$scope.currentRepo.Nodes.forEach(function (n) {
$scope.currentRepo.selectedNodes[n.NodeID] = true;
});
if ($scope.currentRepo.Versioning && $scope.currentRepo.Versioning.Type === "simple") {
$scope.currentRepo.simpleFileVersioning = true;
$scope.currentRepo.simpleKeep = +$scope.currentRepo.Versioning.Params.keep;
}
$scope.currentRepo.simpleKeep = $scope.currentRepo.simpleKeep || 5;
$scope.editingExisting = true;
$scope.repoEditor.$setPristine();
$('#editRepo').modal({backdrop: 'static', keyboard: true});
@@ -436,6 +442,19 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
}
delete repoCfg.selectedNodes;
if (repoCfg.simpleFileVersioning) {
repoCfg.Versioning = {
'Type': 'simple',
'Params': {
'keep': '' + repoCfg.simpleKeep,
}
};
delete repoCfg.simpleFileVersioning;
delete repoCfg.simpleKeep;
} else {
delete repoCfg.Versioning;
}
$scope.repos[repoCfg.ID] = repoCfg;
$scope.config.Repositories = repoList($scope.repos);
+65 -37
View File
@@ -455,47 +455,75 @@
</div>
<div class="modal-body">
<form role="form" name="repoEditor">
<div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
<label for="repoID">Repository ID</label>
<input name="repoID" placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo></input>
<p class="help-block">
<span ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
<span ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
<span ng-if="repoEditor.repoID.$error.required && repoEditor.repoID.$dirty">The repository ID cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
<label for="repoPath">Repository Path</label>
<input name="repoPath" placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
<p class="help-block">
<span ng-if="repoEditor.repoPath.$valid || repoEditor.repoPath.$pristine">Path to the repository on the local computer. Will be created if it does not exist. The tilde character <code>~</code> can be used as a shortcut for <code>{{system.tilde}}</code>.</span>
<span ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
</p>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentRepo.ReadOnly"> Repository Master
</label>
<div class="row">
<div class="col-md-12">
<div class="form-group" ng-class="{'has-error': repoEditor.repoID.$invalid && repoEditor.repoID.$dirty}">
<label for="repoID">Repository ID</label>
<input name="repoID" placeholder="documents" ng-disabled="editingExisting" id="repoID" class="form-control" type="text" ng-model="currentRepo.ID" required unique-repo></input>
<p class="help-block">
<span ng-if="repoEditor.repoID.$valid || repoEditor.repoID.$pristine">Short identifier for the repository. Must be the same on all cluster nodes.</span>
<span ng-if="repoEditor.repoID.$error.uniqueRepo">The repository ID must be unique.</span>
<span ng-if="repoEditor.repoID.$error.required && repoEditor.repoID.$dirty">The repository ID cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-class="{'has-error': repoEditor.repoPath.$invalid && repoEditor.repoPath.$dirty}">
<label for="repoPath">Repository Path</label>
<input name="repoPath" placeholder="~/Documents" id="repoPath" class="form-control" type="text" ng-model="currentRepo.Directory" required></input>
<p class="help-block">
<span ng-if="repoEditor.repoPath.$valid || repoEditor.repoPath.$pristine">Path to the repository on the local computer. Will be created if it does not exist. The tilde character <code>~</code> can be used as a shortcut for <code>{{system.tilde}}</code>.</span>
<span ng-if="repoEditor.repoPath.$error.required && repoEditor.repoPath.$dirty">The repository path cannot be blank.</span>
</p>
</div>
</div>
<p class="help-block">Files are protected from changes made on other nodes, but changes made on <em>this</em> node will be sent to the rest of the cluster.</p>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentRepo.IgnorePerms"> Ignore Permissions
</label>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentRepo.ReadOnly"> Repository Master
</label>
</div>
<p class="help-block">Files are protected from changes made on other nodes, but changes made on <em>this</em> node will be sent to the rest of the cluster.</p>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentRepo.IgnorePerms"> Ignore Permissions
</label>
</div>
<p class="help-block">File permission bits are ignored when looking for changes. Use on FAT filesystems.</p>
</div>
<div class="form-group">
<label for="nodes">Nodes</label>
<div class="checkbox" ng-repeat="node in otherNodes()">
<label>
<input type="checkbox" ng-model="currentRepo.selectedNodes[node.NodeID]"> {{nodeName(node)}}
</label>
</div>
<p class="help-block">Select the nodes to share this repository with.</p>
</div>
</div>
<p class="help-block">File permission bits are ignored when looking for changes. Use on FAT filesystems.</p>
</div>
<div class="form-group">
<label for="nodes">Nodes</label>
<div class="checkbox" ng-repeat="node in otherNodes()">
<label>
<input type="checkbox" ng-model="currentRepo.selectedNodes[node.NodeID]"> {{nodeName(node)}}
</label>
<div class="col-md-6">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentRepo.simpleFileVersioning"> Simple File Versioning
</label>
</div>
<p class="help-block">Files are moved to date stamped versions in a <code>.stversions</code> 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}">
<label 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">
<span ng-if="repoEditor.simpleKeep.$valid || repoEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span ng-if="repoEditor.simpleKeep.$error.required && repoEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span ng-if="repoEditor.simpleKeep.$error.min && repoEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
</div>
<p class="help-block">Select the nodes to share this repository with.</p>
</div>
</form>
<div ng-show="!editingExisting">
+2 -1
View File
@@ -17,6 +17,7 @@ import (
"github.com/calmh/syncthing/config"
"github.com/calmh/syncthing/files"
"github.com/calmh/syncthing/lamport"
"github.com/calmh/syncthing/osutil"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner"
)
@@ -716,7 +717,7 @@ func (m *Model) saveIndex(repo string, dir string, fs []protocol.FileInfo) {
gzw.Close()
idxf.Close()
Rename(name+".tmp", name)
osutil.Rename(name+".tmp", name)
}
func (m *Model) loadIndex(repo string, dir string) []protocol.FileInfo {
+40 -9
View File
@@ -10,8 +10,10 @@ import (
"github.com/calmh/syncthing/buffers"
"github.com/calmh/syncthing/cid"
"github.com/calmh/syncthing/config"
"github.com/calmh/syncthing/osutil"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner"
"github.com/calmh/syncthing/versioner"
)
type requestResult struct {
@@ -71,6 +73,7 @@ type puller struct {
requestSlots chan bool
blocks chan bqBlock
requestResults chan requestResult
versioner versioner.Versioner
}
func newPuller(repoCfg config.RepositoryConfiguration, model *Model, slots int, cfg *config.Configuration) *puller {
@@ -86,6 +89,14 @@ func newPuller(repoCfg config.RepositoryConfiguration, model *Model, slots int,
requestResults: make(chan requestResult),
}
if len(repoCfg.Versioning.Type) > 0 {
factory, ok := versioner.Factories[repoCfg.Versioning.Type]
if !ok {
l.Fatalf("Requested versioning type %q that does not exist", repoCfg.Versioning.Type)
}
p.versioner = factory(repoCfg.Versioning.Params)
}
if slots > 0 {
// Read/write
for i := 0; i < slots; i++ {
@@ -221,6 +232,10 @@ func (p *puller) fixupDirectories() {
return nil
}
if filepath.Base(rn) == ".stversions" {
return nil
}
cur := p.model.CurrentRepoFile(p.repoCfg.ID, rn)
if cur.Name != rn {
// No matching dir in current list; weird
@@ -284,10 +299,10 @@ func (p *puller) fixupDirectories() {
l.Debugln("delete dir:", dir)
}
err := os.Remove(dir)
if err != nil {
l.Warnln(err)
} else {
if err == nil {
deleted++
} else if p.versioner == nil { // Failures are expected in the presence of versioning
l.Warnln(err)
}
}
@@ -385,7 +400,7 @@ func (p *puller) handleBlock(b bqBlock) bool {
}
return true
}
defTempNamer.Hide(of.temp)
osutil.HideFile(of.temp)
}
if of.err != nil {
@@ -524,7 +539,11 @@ func (p *puller) handleEmptyBlock(b bqBlock) {
}
os.Remove(of.temp)
os.Chmod(of.filepath, 0666)
if err := os.Remove(of.filepath); err == nil || os.IsNotExist(err) {
if p.versioner != nil {
if err := p.versioner.Archive(of.filepath); err == nil {
p.model.updateLocal(p.repoCfg.ID, f)
}
} else if err := os.Remove(of.filepath); err == nil || os.IsNotExist(err) {
p.model.updateLocal(p.repoCfg.ID, f)
}
} else {
@@ -540,8 +559,8 @@ func (p *puller) handleEmptyBlock(b bqBlock) {
delete(p.openFiles, f.Name)
return
}
defTempNamer.Show(of.temp)
if Rename(of.temp, of.filepath) == nil {
osutil.ShowFile(of.temp)
if osutil.Rename(of.temp, of.filepath) == nil {
p.model.updateLocal(p.repoCfg.ID, f)
}
}
@@ -614,11 +633,23 @@ func (p *puller) closeFile(f scanner.File) {
l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err)
}
}
defTempNamer.Show(of.temp)
osutil.ShowFile(of.temp)
if p.versioner != nil {
err := p.versioner.Archive(of.filepath)
if err != nil {
if debug {
l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err)
}
return
}
}
if debug {
l.Debugf("pull: rename %q / %q: %q", p.repoCfg.ID, f.Name, of.filepath)
}
if err := Rename(of.temp, of.filepath); err == nil {
if err := osutil.Rename(of.temp, of.filepath); err == nil {
p.model.updateLocal(p.repoCfg.ID, f)
} else {
l.Debugf("pull: error: %q / %q: %v", p.repoCfg.ID, f.Name, err)
-8
View File
@@ -23,11 +23,3 @@ func (t tempNamer) TempName(name string) string {
tname := fmt.Sprintf("%s.%s", t.prefix, filepath.Base(name))
return filepath.Join(tdir, tname)
}
func (t tempNamer) Hide(path string) error {
return nil
}
func (t tempNamer) Show(path string) error {
return nil
}
-31
View File
@@ -6,7 +6,6 @@ import (
"fmt"
"path/filepath"
"strings"
"syscall"
)
type tempNamer struct {
@@ -24,33 +23,3 @@ func (t tempNamer) TempName(name string) string {
tname := fmt.Sprintf("%s.%s.tmp", t.prefix, filepath.Base(name))
return filepath.Join(tdir, tname)
}
func (t tempNamer) Hide(path string) error {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}
attrs, err := syscall.GetFileAttributes(p)
if err != nil {
return err
}
attrs |= syscall.FILE_ATTRIBUTE_HIDDEN
return syscall.SetFileAttributes(p, attrs)
}
func (t tempNamer) Show(path string) error {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}
attrs, err := syscall.GetFileAttributes(p)
if err != nil {
return err
}
attrs &^= syscall.FILE_ATTRIBUTE_HIDDEN
return syscall.SetFileAttributes(p, attrs)
}
-14
View File
@@ -2,26 +2,12 @@ package model
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner"
)
func Rename(from, to string) error {
if runtime.GOOS == "windows" {
os.Chmod(to, 0666) // Make sure the file is user writeable
err := os.Remove(to)
if err != nil && !os.IsNotExist(err) {
l.Warnln(err)
}
}
defer os.Remove(from) // Don't leave a dangling temp file in case of rename error
return os.Rename(from, to)
}
func fileFromFileInfo(f protocol.FileInfo) scanner.File {
var blocks = make([]scanner.Block, len(f.Blocks))
var offset int64
+11
View File
@@ -0,0 +1,11 @@
// +build !windows
package osutil
func HideFile(path string) error {
return nil
}
func ShowFile(path string) error {
return nil
}
+35
View File
@@ -0,0 +1,35 @@
// +build windows
package osutil
import "syscall"
func HideFile(path string) error {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}
attrs, err := syscall.GetFileAttributes(p)
if err != nil {
return err
}
attrs |= syscall.FILE_ATTRIBUTE_HIDDEN
return syscall.SetFileAttributes(p, attrs)
}
func ShowFile(path string) error {
p, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}
attrs, err := syscall.GetFileAttributes(p)
if err != nil {
return err
}
attrs &^= syscall.FILE_ATTRIBUTE_HIDDEN
return syscall.SetFileAttributes(p, attrs)
}
+18
View File
@@ -0,0 +1,18 @@
package osutil
import (
"os"
"runtime"
)
func Rename(from, to string) error {
if runtime.GOOS == "windows" {
os.Chmod(to, 0666) // Make sure the file is user writeable
err := os.Remove(to)
if err != nil && !os.IsNotExist(err) {
return err
}
}
defer os.Remove(from) // Don't leave a dangling temp file in case of rename error
return os.Rename(from, to)
}
+1 -9
View File
@@ -144,15 +144,7 @@ func (w *Walker) walkAndHashFiles(res *[]File, ign map[string][]string) filepath
return nil
}
if _, sn := filepath.Split(rn); sn == w.IgnoreFile {
// An ignore-file; these are ignored themselves
if debug {
l.Debugln("ignorefile:", rn)
}
return nil
}
if w.ignoreFile(ign, rn) {
if sn := filepath.Base(rn); sn == w.IgnoreFile || sn == ".stversions" || w.ignoreFile(ign, rn) {
// An ignored file
if debug {
l.Debugln("ignored:", rn)
+13
View File
@@ -0,0 +1,13 @@
package versioner
import (
"os"
"strings"
"github.com/calmh/syncthing/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "versioner") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)
+84
View File
@@ -0,0 +1,84 @@
package versioner
import (
"os"
"path/filepath"
"sort"
"strconv"
"time"
"github.com/calmh/syncthing/osutil"
)
func init() {
// Register the constructor for this type of versioner with the name "simple"
Factories["simple"] = NewSimple
}
// The type holds our configuration
type Simple struct {
keep int
}
// The constructor function takes a map of parameters and creates the type.
func NewSimple(params map[string]string) Versioner {
keep, err := strconv.Atoi(params["keep"])
if err != nil {
keep = 5 // A reasonable default
}
s := Simple{
keep: keep,
}
if debug {
l.Debugf("instantiated %#v", s)
}
return s
}
// 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 Simple) Archive(path string) error {
_, err := os.Stat(path)
if err != nil && os.IsNotExist(err) {
return nil
}
if debug {
l.Debugln("archiving", path)
}
file := filepath.Base(path)
dir := filepath.Join(filepath.Dir(path), ".stversions")
err = os.MkdirAll(dir, 0755)
if err != nil && !os.IsExist(err) {
return err
} else {
osutil.HideFile(dir)
}
ver := file + "~" + time.Now().Format("20060102-150405")
err = osutil.Rename(path, filepath.Join(dir, ver))
if err != nil {
return err
}
versions, err := filepath.Glob(filepath.Join(dir, file+"~*"))
if err != nil {
l.Warnln(err)
return nil
}
if len(versions) > v.keep {
sort.Strings(versions)
for _, toRemove := range versions[:len(versions)-v.keep] {
err = os.Remove(toRemove)
if err != nil {
l.Warnln(err)
}
}
}
return nil
}
+7
View File
@@ -0,0 +1,7 @@
package versioner
type Versioner interface {
Archive(path string) error
}
var Factories = map[string]func(map[string]string) Versioner{}