Compare commits

...

15 Commits

Author SHA1 Message Date
Jakob Borg
b3c2ffc96a Test for previous commit and never ignore entire repo directory 2014-03-09 12:36:26 +01:00
Jakob Borg
b5f652a815 Do not descend into ignored directories (fixes #86) 2014-03-09 12:21:07 +01:00
Jakob Borg
9ec7de643e Refactor profiler startup / logging 2014-03-09 09:18:28 +01:00
Jakob Borg
2553ba0463 Discover & main tracing 2014-03-09 09:15:36 +01:00
Jakob Borg
52ee7d5724 Discovery tracing 2014-03-09 08:58:03 +01:00
Jakob Borg
d4ef6a6285 Document env vars, start profiler based on STPROFILER 2014-03-09 08:48:29 +01:00
Jakob Borg
56b7d3c28d Don't start browser on restart 2014-03-09 08:35:53 +01:00
Jakob Borg
ae94b726a7 Don't expose a -delay paramater 2014-03-09 08:35:38 +01:00
Jakob Borg
a88e4db1ee Option to not start browser (fixes #84) 2014-03-08 23:19:33 +01:00
Jakob Borg
0ebd4a6ba1 Fix relative path open bug 2014-03-08 23:18:50 +01:00
Jakob Borg
1448cfe66a Refactor out file scanner into separate package 2014-03-08 23:02:01 +01:00
Jakob Borg
d6c9afd07f Fix handling of default values in config (fixes #83) 2014-03-04 22:29:48 +01:00
Jakob Borg
799f55e7ae Add basic config tests 2014-03-04 22:17:39 +01:00
Jakob Borg
04a3db132f Merge pull request #81 from filoozom/patch-1
Fix isTempName to work on Windows (fixes #80)
2014-03-04 21:56:23 +01:00
Philippe Schommers
d06204959e Fix isTempName to work on Windows (fixes #80)
```path.Base()``` is for slash-separated paths, whereas Windows uses "\" to separate paths. Just convert the \ to / and it works.
2014-03-04 18:48:03 +01:00
31 changed files with 925 additions and 631 deletions

View File

@@ -2324,171 +2324,172 @@ func init() {
data, _ = ioutil.ReadAll(gr)
Assets["angular.min.js"] = data
gr, _ = gzip.NewReader(bytes.NewBuffer([]byte{
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd5, 0x5b, 0x6d, 0x6f, 0xdb, 0x38,
0x12, 0xfe, 0x9e, 0x5f, 0xc1, 0xed, 0xf6, 0x20, 0x39, 0x71, 0x6d, 0x77, 0x71, 0x58, 0x2c, 0x92,
0x26, 0x40, 0x37, 0x7d, 0x41, 0xae, 0x2f, 0x59, 0xd4, 0xbd, 0xfd, 0x12, 0xe4, 0x03, 0x6d, 0xd1,
0x36, 0x1b, 0x99, 0xd4, 0x91, 0x54, 0x12, 0x23, 0xf0, 0x7f, 0xbf, 0x21, 0x45, 0xc9, 0xa4, 0x44,
0xcb, 0x56, 0x72, 0xdd, 0xde, 0x0a, 0x48, 0x62, 0x93, 0xc3, 0x79, 0x79, 0x38, 0x1c, 0x0e, 0x47,
0xcc, 0xf0, 0xf0, 0x9b, 0x4c, 0x29, 0x53, 0x68, 0x22, 0xf8, 0x9d, 0x24, 0xe2, 0x18, 0x29, 0x91,
0x93, 0x3e, 0x9a, 0x72, 0xa6, 0x28, 0xcb, 0x49, 0xf9, 0x3d, 0x4b, 0x73, 0xa9, 0x7f, 0x8a, 0xef,
0xe8, 0x70, 0x78, 0x30, 0x3c, 0x9c, 0xa7, 0x7c, 0x82, 0x53, 0xf4, 0xfc, 0x18, 0xcd, 0x70, 0x2a,
0x81, 0x08, 0xb3, 0x79, 0x9e, 0x62, 0x61, 0xbf, 0x6b, 0xa2, 0x83, 0x28, 0x87, 0x0f, 0x52, 0x09,
0x3a, 0x55, 0xd1, 0xc9, 0xc1, 0xc1, 0x2d, 0x16, 0x48, 0xae, 0xd8, 0x54, 0x2d, 0x28, 0x9b, 0xa3,
0xd3, 0x72, 0xc4, 0x60, 0xc9, 0x93, 0x3c, 0x25, 0x71, 0x54, 0xf5, 0x45, 0x7d, 0x74, 0x75, 0xdd,
0x83, 0x11, 0x55, 0xcb, 0x40, 0xab, 0x24, 0x78, 0x9a, 0x12, 0x11, 0x47, 0xe3, 0xb2, 0xf5, 0x5c,
0x89, 0x14, 0x68, 0x67, 0x39, 0x7c, 0xa7, 0x9c, 0xa1, 0xf8, 0xb9, 0x9c, 0xf2, 0x0c, 0x74, 0x79,
0xbe, 0x50, 0x2a, 0xeb, 0xa1, 0x87, 0x03, 0x04, 0x8f, 0x16, 0x9b, 0x09, 0x72, 0xfb, 0x06, 0x2b,
0x02, 0x52, 0x47, 0x7d, 0xd3, 0xaa, 0x1f, 0x10, 0x4c, 0xd2, 0xf7, 0x44, 0x5d, 0x7e, 0x80, 0x76,
0x6d, 0x1a, 0x88, 0xd4, 0xed, 0x05, 0x1b, 0x2d, 0x93, 0x11, 0xc3, 0x59, 0x42, 0xff, 0xc3, 0xfa,
0xa4, 0xd6, 0x39, 0xa3, 0xf3, 0x66, 0xfb, 0x72, 0x75, 0xf1, 0x06, 0x5a, 0xa3, 0xc8, 0x6b, 0x65,
0x20, 0x49, 0x33, 0xb9, 0xba, 0x0e, 0x30, 0xb9, 0x60, 0xda, 0xa2, 0x4a, 0x05, 0xa7, 0x9f, 0x08,
0xc1, 0x45, 0x60, 0x9c, 0x24, 0x84, 0xbd, 0xd5, 0x7d, 0x56, 0x92, 0xe9, 0x1b, 0x0e, 0xd1, 0x18,
0xb0, 0x66, 0x73, 0x89, 0x26, 0x64, 0xc6, 0x05, 0x41, 0x13, 0xce, 0x53, 0x89, 0x52, 0xce, 0x6f,
0xa0, 0x45, 0x29, 0x22, 0x7c, 0x1e, 0x4a, 0x19, 0x62, 0xe0, 0x5e, 0x21, 0xf2, 0x40, 0x93, 0x63,
0x14, 0x7d, 0xa4, 0x52, 0x11, 0x06, 0xcc, 0x00, 0x5c, 0xd0, 0x7b, 0x0a, 0x93, 0x6a, 0x40, 0x47,
0x7f, 0x08, 0xae, 0xf8, 0x94, 0xa7, 0xa8, 0xa0, 0x40, 0xaf, 0x93, 0x44, 0x10, 0x29, 0x89, 0x04,
0x42, 0xb5, 0xca, 0xc0, 0x61, 0x22, 0x45, 0xee, 0x15, 0x7c, 0x83, 0x66, 0x85, 0x85, 0x2a, 0x3c,
0x66, 0xdd, 0xaf, 0x09, 0x78, 0xff, 0xef, 0x0b, 0x3b, 0xd4, 0x91, 0x00, 0x8d, 0x35, 0xbe, 0x1d,
0xb9, 0x7e, 0xc2, 0xf7, 0x63, 0xc2, 0x92, 0x0f, 0x93, 0xcc, 0x65, 0x7b, 0x99, 0xab, 0x39, 0xd7,
0x0e, 0xf7, 0x45, 0xcf, 0xff, 0x47, 0xba, 0xa4, 0x0a, 0xc5, 0x1f, 0x7e, 0xcf, 0x64, 0x6f, 0xc3,
0x9e, 0xe5, 0xcb, 0x09, 0x11, 0x3b, 0x05, 0x7c, 0x01, 0x96, 0x98, 0x5d, 0x30, 0x40, 0xf2, 0x16,
0xa7, 0x63, 0x47, 0x48, 0xd1, 0x83, 0xca, 0x2e, 0x14, 0x3f, 0x8a, 0xbb, 0x75, 0xb8, 0xb0, 0x00,
0xdb, 0xf9, 0x34, 0x19, 0x7f, 0x60, 0x81, 0x61, 0x11, 0xa5, 0x5f, 0xc8, 0x7f, 0x72, 0x20, 0x74,
0x71, 0x02, 0xf4, 0x10, 0x60, 0x05, 0xa3, 0x59, 0x62, 0xe0, 0xda, 0x90, 0x74, 0x93, 0x01, 0x8c,
0xce, 0x17, 0xb0, 0xb6, 0x49, 0x6d, 0x22, 0xb4, 0x80, 0x77, 0x34, 0x25, 0xa8, 0xe8, 0x2d, 0xe6,
0x63, 0xef, 0x99, 0x68, 0x80, 0x85, 0x93, 0x4b, 0x96, 0xae, 0x3c, 0x88, 0x70, 0x82, 0x6c, 0x9b,
0xe5, 0xa6, 0x17, 0xc0, 0x4e, 0x7d, 0x5f, 0xa7, 0x29, 0xbf, 0x7b, 0x43, 0x52, 0xa2, 0x88, 0xc3,
0xcd, 0xb4, 0xa2, 0xaa, 0xb9, 0x0b, 0xc3, 0x77, 0x5c, 0x8f, 0x1d, 0xaf, 0x96, 0x10, 0x5b, 0x6f,
0x5c, 0x04, 0x8a, 0x0e, 0xe4, 0xf4, 0x74, 0x61, 0xfb, 0xde, 0x44, 0xdc, 0xd7, 0x8c, 0xbd, 0x65,
0x78, 0x92, 0x92, 0xc4, 0x5d, 0x3a, 0x45, 0x30, 0x86, 0x3e, 0x0e, 0xb1, 0xb0, 0xab, 0xbe, 0x1f,
0xf9, 0x74, 0x0b, 0x5f, 0xd3, 0xd3, 0x8d, 0xed, 0xb5, 0x8d, 0x46, 0x55, 0x50, 0x2e, 0x83, 0xec,
0x38, 0x9f, 0x4e, 0x09, 0x49, 0x48, 0x12, 0x97, 0x81, 0x59, 0x3f, 0x74, 0x86, 0xe2, 0x9f, 0x36,
0x71, 0xd8, 0xed, 0x32, 0xb1, 0x2a, 0x8e, 0x7e, 0x66, 0x44, 0xdd, 0x71, 0x71, 0x63, 0xe2, 0x5d,
0xd4, 0xd3, 0xbb, 0x05, 0x4e, 0xe3, 0x68, 0x41, 0x13, 0x12, 0xf5, 0x4e, 0x3c, 0xea, 0x40, 0x3c,
0x2f, 0xbb, 0xd6, 0x07, 0xc5, 0xef, 0xb0, 0x6e, 0xef, 0x30, 0xf8, 0x65, 0x53, 0xb1, 0x47, 0xe8,
0xf5, 0x30, 0xc1, 0xd3, 0x9b, 0x44, 0xf0, 0x0c, 0x50, 0x02, 0x70, 0x14, 0x9d, 0x02, 0x4e, 0x37,
0x64, 0x35, 0xe1, 0x58, 0x24, 0x76, 0x6f, 0x5c, 0xb7, 0xa9, 0x6d, 0x28, 0x76, 0xe9, 0xad, 0xb7,
0x93, 0x73, 0xbe, 0xcc, 0xb0, 0x20, 0x31, 0xee, 0xa3, 0x49, 0x5d, 0x71, 0x3c, 0xf8, 0x0c, 0x14,
0x7a, 0x23, 0x3a, 0x3d, 0x75, 0x37, 0xa6, 0xba, 0x19, 0x82, 0xa8, 0x5c, 0x30, 0xf4, 0xe2, 0x65,
0x5d, 0x60, 0xc9, 0x68, 0xd2, 0x8d, 0xd1, 0x56, 0x3e, 0x95, 0x42, 0xaf, 0x50, 0xc9, 0xb2, 0x93,
0x2e, 0xb6, 0xb3, 0x62, 0x73, 0x56, 0xb1, 0x39, 0x71, 0x01, 0x32, 0xfb, 0xfe, 0x60, 0x4e, 0x54,
0x1c, 0x0d, 0xb5, 0x73, 0x0e, 0x6f, 0x89, 0x90, 0x00, 0x18, 0x4c, 0x8f, 0xd4, 0xee, 0x27, 0x65,
0xbc, 0x49, 0x16, 0x12, 0xac, 0xb0, 0xab, 0x84, 0x35, 0xcf, 0x0e, 0x81, 0xa9, 0xd0, 0x04, 0x96,
0xbd, 0x9d, 0xb1, 0x06, 0x7f, 0xb9, 0x82, 0xfd, 0x6a, 0xd9, 0x85, 0x7d, 0x31, 0xc2, 0xe3, 0xee,
0xf4, 0xda, 0xec, 0x41, 0xf7, 0x99, 0xcf, 0x27, 0x9b, 0xb8, 0xd7, 0x90, 0x5d, 0x24, 0x0f, 0x7b,
0xc9, 0x76, 0x24, 0x54, 0x79, 0x8b, 0x2f, 0xbf, 0x41, 0x31, 0xb8, 0xcc, 0x4c, 0xe6, 0x33, 0xa8,
0x92, 0x01, 0x74, 0xda, 0x4a, 0x61, 0xf7, 0xec, 0xc1, 0x37, 0xd8, 0x6c, 0x63, 0x70, 0x7a, 0xbd,
0x38, 0x3d, 0xf6, 0x3a, 0x0f, 0x2b, 0xf3, 0x20, 0x9f, 0xd1, 0x17, 0x92, 0x71, 0x49, 0x15, 0x17,
0x94, 0xc8, 0xab, 0xd1, 0xb5, 0x99, 0x59, 0xe9, 0xeb, 0x66, 0x06, 0x0e, 0x24, 0x17, 0x2a, 0x76,
0x9c, 0xbf, 0x17, 0x34, 0xa0, 0x14, 0xc2, 0x7c, 0x36, 0xee, 0xb2, 0xdb, 0x02, 0xe6, 0x50, 0x27,
0x9c, 0x8f, 0x43, 0xb4, 0x4a, 0xe2, 0xcc, 0xdc, 0xb9, 0x6d, 0x4d, 0x0d, 0xd6, 0x3d, 0x3f, 0xd1,
0x14, 0x64, 0x06, 0x4a, 0x2c, 0xf4, 0xea, 0xaf, 0x04, 0x7a, 0xae, 0xf3, 0x04, 0xb7, 0xdb, 0xed,
0x7a, 0xad, 0xb8, 0x98, 0xd8, 0xd4, 0x55, 0x90, 0x19, 0x14, 0x74, 0xb1, 0xc0, 0x66, 0xe0, 0x2a,
0x52, 0x24, 0xbc, 0x71, 0x18, 0x05, 0x77, 0x7c, 0x19, 0xb0, 0xf7, 0x9f, 0xdd, 0x32, 0x93, 0xdf,
0xdb, 0x96, 0xc2, 0x5d, 0xef, 0xc0, 0x0c, 0x7d, 0x70, 0x00, 0xa7, 0xba, 0x8b, 0x7b, 0x7d, 0x8f,
0x42, 0x3f, 0x2a, 0x01, 0x82, 0x58, 0xd3, 0xbd, 0xa8, 0x0e, 0x19, 0x3d, 0x34, 0x44, 0x2f, 0x47,
0xa3, 0x51, 0x93, 0x9a, 0x26, 0xb5, 0x35, 0xe1, 0x9c, 0x4b, 0x80, 0x47, 0xd0, 0x9b, 0x29, 0x83,
0x34, 0x4a, 0x9f, 0x5b, 0x82, 0xbd, 0x3c, 0x57, 0x55, 0xb7, 0xd7, 0x0f, 0x07, 0x00, 0x14, 0xd3,
0x04, 0x51, 0x86, 0x42, 0xd6, 0x19, 0x6d, 0xf4, 0xde, 0x6b, 0xdc, 0x75, 0x81, 0xe5, 0xe5, 0x1d,
0x83, 0xec, 0x3e, 0x23, 0x42, 0xad, 0x60, 0x58, 0x2f, 0x44, 0xaf, 0x9f, 0xf2, 0x68, 0x78, 0xd2,
0xe8, 0x5d, 0x37, 0xb1, 0x11, 0xab, 0x2d, 0x5c, 0xb4, 0xd0, 0x2b, 0x9a, 0x5c, 0x57, 0xc6, 0x7d,
0xc2, 0x6a, 0x31, 0x58, 0xe2, 0xfb, 0x78, 0xd4, 0x47, 0xbf, 0xa1, 0xc3, 0x62, 0x46, 0x0c, 0xc5,
0x05, 0xfb, 0x7d, 0xa5, 0x88, 0xfc, 0xca, 0x15, 0x64, 0x22, 0x2f, 0x02, 0x47, 0xb3, 0x06, 0x95,
0x86, 0x5f, 0x25, 0xbd, 0xa6, 0x86, 0x9e, 0xe4, 0x0a, 0xb8, 0xed, 0xa2, 0x21, 0x1b, 0xde, 0x47,
0xb6, 0x47, 0xb6, 0x55, 0xf8, 0x1a, 0x4d, 0xb1, 0x9a, 0x2e, 0x50, 0x4c, 0xb6, 0x41, 0xdb, 0x00,
0x65, 0xb4, 0xaf, 0x09, 0x01, 0xc2, 0xe6, 0x6c, 0x78, 0xee, 0x74, 0x74, 0x5a, 0x13, 0xd7, 0xe4,
0xe0, 0x3b, 0x98, 0x3b, 0xa0, 0x68, 0xf2, 0x47, 0xac, 0x43, 0xce, 0xe9, 0x1f, 0xa0, 0x3b, 0x44,
0x1d, 0x06, 0xb1, 0xa1, 0xd3, 0x42, 0xa5, 0x7d, 0xc4, 0xf0, 0xb2, 0xe6, 0x95, 0xc5, 0x12, 0x30,
0xf8, 0x20, 0x0a, 0x99, 0x87, 0x71, 0xf5, 0x94, 0xb0, 0xb9, 0x5a, 0x40, 0xc3, 0xd1, 0x51, 0x68,
0x22, 0x34, 0x17, 0x54, 0xda, 0x0a, 0x3b, 0x11, 0x7c, 0x1d, 0xc8, 0x2c, 0xa5, 0x5a, 0xb5, 0x7a,
0xba, 0xb9, 0x99, 0x8c, 0xeb, 0xc1, 0x78, 0x01, 0x5b, 0xd3, 0xe7, 0x62, 0xb0, 0xe6, 0x71, 0xa5,
0x7f, 0x59, 0x61, 0xe0, 0x3a, 0x2f, 0xaf, 0xdb, 0xe0, 0x32, 0x8a, 0x99, 0xbd, 0x6d, 0x63, 0x69,
0x3d, 0xad, 0x2b, 0x9f, 0x22, 0x9b, 0xda, 0x88, 0xd3, 0x09, 0x55, 0xf5, 0x6d, 0x9b, 0x6b, 0x05,
0x72, 0xab, 0xb0, 0x26, 0x21, 0x09, 0x67, 0x1d, 0x24, 0xec, 0x25, 0xc0, 0xd2, 0xd6, 0xbc, 0xb6,
0x9e, 0x15, 0x97, 0x9b, 0x39, 0xb8, 0x42, 0x37, 0xe7, 0x29, 0x8a, 0x26, 0x5d, 0xf7, 0xac, 0xaa,
0xd4, 0x12, 0x96, 0xb4, 0xf6, 0x77, 0x6c, 0x9d, 0x5a, 0x8c, 0x21, 0xbd, 0xcf, 0xa5, 0xb7, 0x69,
0x9b, 0xd4, 0x64, 0x36, 0x77, 0x45, 0x68, 0xef, 0xd4, 0xeb, 0xc0, 0x4b, 0x7a, 0xaa, 0xf8, 0x61,
0x07, 0xd8, 0x5c, 0xd6, 0x71, 0x12, 0x3d, 0x09, 0x9a, 0xb0, 0xae, 0x6d, 0xd9, 0x3e, 0xd0, 0x19,
0x10, 0x9c, 0x4d, 0x4d, 0xa6, 0x0a, 0xb9, 0x39, 0xec, 0x37, 0xa1, 0xa9, 0xb1, 0x50, 0x47, 0x17,
0x0c, 0xe9, 0x54, 0x24, 0xaa, 0x41, 0x8e, 0x88, 0x2e, 0xd5, 0x6d, 0x1f, 0xa6, 0xc7, 0xe8, 0x5a,
0x40, 0x1c, 0xa1, 0x23, 0x54, 0x17, 0x7b, 0x84, 0xa2, 0x7f, 0xf4, 0xea, 0x1c, 0x9d, 0xdc, 0xbd,
0x9e, 0xbc, 0x47, 0x6f, 0xa8, 0xb4, 0xb6, 0xc3, 0xea, 0xde, 0x0a, 0xeb, 0xc5, 0xd4, 0x24, 0xdf,
0x7f, 0x03, 0x50, 0xf9, 0x4d, 0x57, 0x3c, 0x6d, 0xa2, 0xd7, 0x05, 0xb4, 0x25, 0xec, 0xb6, 0x72,
0x3b, 0x5a, 0xe7, 0x29, 0x96, 0x7f, 0x13, 0x1f, 0xb4, 0xcb, 0xb1, 0x2b, 0x66, 0x99, 0xa0, 0x4b,
0x2c, 0x56, 0x5d, 0x30, 0xa3, 0x6c, 0xc6, 0xb7, 0x43, 0xa6, 0x0f, 0x2a, 0x7f, 0x31, 0x62, 0x56,
0x31, 0x03, 0x9a, 0x3d, 0x27, 0xb5, 0x9c, 0x72, 0xa3, 0x38, 0x67, 0x37, 0x90, 0x0e, 0xc2, 0x79,
0xb7, 0xa0, 0xed, 0xb5, 0xcc, 0xbf, 0x33, 0x05, 0x3f, 0xcc, 0xa4, 0x5a, 0x50, 0x88, 0xda, 0x6c,
0xdb, 0x6e, 0xca, 0x9f, 0x64, 0xe7, 0xb4, 0x68, 0x65, 0x7c, 0x4d, 0xf7, 0x2c, 0x4c, 0xf8, 0x67,
0xfb, 0x90, 0x7e, 0xdf, 0x13, 0x9e, 0x94, 0x12, 0xa6, 0xfe, 0xdc, 0x2e, 0xbb, 0x31, 0xef, 0x56,
0xcf, 0x96, 0x79, 0xb7, 0xa9, 0xc6, 0xfe, 0x68, 0x05, 0xf6, 0x6e, 0x2b, 0xd6, 0x25, 0x69, 0xd1,
0xce, 0x87, 0x00, 0x36, 0xd7, 0x89, 0x54, 0x42, 0x27, 0xcd, 0xbf, 0x86, 0xb7, 0x48, 0x89, 0x6f,
0xc9, 0x78, 0xf3, 0x8e, 0x61, 0xcb, 0xc9, 0x36, 0x78, 0x84, 0xae, 0xd5, 0xc0, 0xf6, 0xa8, 0x3a,
0xec, 0xa8, 0x4d, 0x8c, 0x95, 0x28, 0xb3, 0xb8, 0xbe, 0x2e, 0xd5, 0xe1, 0xcc, 0x49, 0x0a, 0xee,
0x41, 0xa1, 0xd2, 0xc8, 0xfb, 0x81, 0x82, 0x70, 0x03, 0xe7, 0xca, 0x40, 0x8a, 0x91, 0x71, 0x59,
0xaf, 0xbd, 0xf4, 0xd1, 0xbf, 0xc6, 0x97, 0x9f, 0x07, 0xd2, 0xbc, 0x76, 0xa1, 0xb3, 0x55, 0xec,
0x29, 0xd1, 0xeb, 0xa3, 0x87, 0x05, 0xc1, 0x09, 0x4c, 0xe6, 0x31, 0x7a, 0x88, 0xce, 0xe1, 0xe8,
0x04, 0x7e, 0xf0, 0xe2, 0xeb, 0x2a, 0x23, 0xd1, 0x31, 0x8a, 0x70, 0x06, 0x1a, 0xc1, 0xa1, 0x00,
0x94, 0x18, 0x7e, 0x93, 0x9c, 0x45, 0x6b, 0x4f, 0x66, 0x1c, 0xfd, 0x5c, 0xbe, 0xa2, 0xf9, 0xaa,
0xeb, 0xb2, 0xa0, 0xf7, 0x94, 0xa7, 0x29, 0xce, 0x24, 0xf1, 0xab, 0x9f, 0xeb, 0x7a, 0x31, 0xc1,
0x14, 0x65, 0x77, 0x14, 0x13, 0x5c, 0x63, 0xec, 0x08, 0x37, 0xbd, 0xdd, 0xf5, 0x7e, 0xaa, 0x26,
0x93, 0x24, 0x54, 0x69, 0xbf, 0xd8, 0xe5, 0x91, 0x25, 0xdb, 0x5c, 0x08, 0x40, 0xc2, 0x8e, 0xb0,
0x74, 0x0d, 0xe1, 0x9a, 0x29, 0x58, 0xff, 0xf6, 0x1e, 0xa6, 0xb0, 0x78, 0x61, 0xe8, 0x97, 0x74,
0x9b, 0xcc, 0x06, 0xd5, 0xfb, 0xa8, 0xa2, 0x5c, 0x55, 0xba, 0x6c, 0xd5, 0xec, 0x15, 0xa7, 0x5c,
0xa4, 0x4b, 0x03, 0x1e, 0x55, 0xc7, 0xad, 0x81, 0x01, 0x41, 0xbb, 0x81, 0xc5, 0x4e, 0x10, 0x1e,
0x8a, 0x65, 0x05, 0xd2, 0x40, 0x8e, 0x6b, 0x06, 0xb4, 0x24, 0x2b, 0x38, 0x4c, 0x80, 0x02, 0xeb,
0x3d, 0x20, 0xaa, 0x2f, 0x9d, 0xff, 0xb1, 0x6d, 0x89, 0x79, 0x1f, 0xd2, 0x6a, 0x9e, 0xa9, 0x9b,
0x90, 0xbb, 0xcf, 0xd5, 0x1b, 0xcf, 0x3e, 0xa2, 0x6e, 0x19, 0x33, 0xa4, 0x51, 0xa3, 0x9a, 0x6f,
0x4a, 0x13, 0x61, 0x23, 0xc3, 0x51, 0xcc, 0x0d, 0x5c, 0xd5, 0xc7, 0xfa, 0x01, 0xd0, 0xad, 0x0f,
0xb6, 0x1d, 0x04, 0xb5, 0x74, 0x97, 0xd6, 0x9c, 0x04, 0x8b, 0xed, 0xe6, 0xa7, 0xcd, 0x76, 0xe3,
0x3a, 0x5e, 0xb8, 0xa4, 0xad, 0x9f, 0x12, 0x8a, 0x41, 0x96, 0xcb, 0x45, 0x9d, 0x6b, 0xed, 0xc8,
0x13, 0x4c, 0x6a, 0xea, 0x45, 0x4d, 0xcb, 0x6f, 0x5b, 0x78, 0x0c, 0xd6, 0x52, 0xbd, 0x71, 0x1d,
0x83, 0xef, 0x5f, 0x1c, 0xfb, 0x02, 0xbb, 0xc8, 0x6e, 0x77, 0x2b, 0xd6, 0x79, 0x1f, 0x25, 0x9c,
0x91, 0x9a, 0xbb, 0xed, 0x65, 0xe3, 0x5e, 0x3e, 0x69, 0xa5, 0xa0, 0x90, 0x03, 0x34, 0xa8, 0x36,
0x31, 0x27, 0x14, 0x87, 0x1e, 0xb9, 0x1f, 0x55, 0x42, 0xb4, 0x9d, 0x4d, 0x33, 0xbe, 0x83, 0xbb,
0xeb, 0xec, 0xca, 0xdf, 0xf8, 0x43, 0x3e, 0x5e, 0x1b, 0x1c, 0x8a, 0xe9, 0x35, 0xcd, 0xfd, 0x48,
0x5e, 0x3e, 0x13, 0x41, 0xf0, 0xcd, 0x1e, 0x4b, 0xa2, 0x28, 0x5c, 0x02, 0xa7, 0x2d, 0x67, 0x79,
0xb6, 0x59, 0x70, 0xe5, 0x26, 0x14, 0x8c, 0x0f, 0x1e, 0x79, 0xcb, 0xbb, 0x85, 0xfd, 0x56, 0x98,
0xcb, 0xad, 0xf1, 0xde, 0xe6, 0x87, 0x2d, 0x20, 0xae, 0x16, 0x44, 0x94, 0x2a, 0xb6, 0x2f, 0xa1,
0x2a, 0x5c, 0xf7, 0x11, 0x3b, 0x79, 0x7a, 0x14, 0x65, 0x35, 0x4c, 0xc0, 0x33, 0x4e, 0x1a, 0x7e,
0xc7, 0x02, 0x81, 0x35, 0x94, 0xc7, 0x1b, 0x86, 0xce, 0xb4, 0xb6, 0x44, 0xce, 0xf2, 0x93, 0x93,
0xb7, 0xca, 0x20, 0x34, 0x6a, 0x41, 0xe5, 0xce, 0xd8, 0xf2, 0x43, 0xc0, 0xd8, 0x71, 0xa8, 0x71,
0xac, 0xbb, 0x62, 0x5b, 0x2b, 0x8b, 0xeb, 0x60, 0xa6, 0xa6, 0xcb, 0x5c, 0x3a, 0x29, 0x6e, 0x35,
0xba, 0x71, 0xef, 0xa8, 0x32, 0xdd, 0x40, 0xd2, 0x30, 0xbf, 0xa0, 0x6f, 0xb3, 0xdf, 0x70, 0xdd,
0x60, 0x50, 0x0c, 0x08, 0x82, 0x40, 0x06, 0x5f, 0xa9, 0xa9, 0x3a, 0xd6, 0xef, 0x3a, 0x85, 0x50,
0xb0, 0x82, 0x8d, 0x4f, 0xd4, 0xdf, 0x06, 0xb6, 0xf8, 0x44, 0x31, 0xac, 0x72, 0x0a, 0xfd, 0xc7,
0x05, 0x69, 0x9a, 0x12, 0x2c, 0xde, 0x96, 0x18, 0xb4, 0x66, 0x71, 0xee, 0x4d, 0x2c, 0xdf, 0xb6,
0x10, 0x34, 0xa6, 0x12, 0x6c, 0xec, 0x0b, 0x3a, 0xe4, 0x0c, 0x42, 0x0a, 0x4b, 0xd2, 0x55, 0x73,
0xb9, 0x42, 0x90, 0x70, 0x85, 0xb7, 0x4c, 0xc5, 0x4e, 0x4f, 0x34, 0x07, 0x5d, 0x77, 0x1b, 0x0b,
0xfb, 0xa3, 0x34, 0x19, 0x34, 0xfc, 0x86, 0x03, 0x45, 0x96, 0xe2, 0x29, 0x89, 0xa7, 0xd5, 0x1e,
0xd0, 0xaf, 0x9f, 0x44, 0x75, 0x5f, 0xcf, 0x8b, 0xb1, 0x35, 0xc0, 0x81, 0xcf, 0x96, 0xc3, 0x8a,
0x29, 0x88, 0x95, 0x2f, 0xf2, 0xe0, 0xc0, 0x53, 0x5e, 0x6f, 0x8a, 0x7d, 0x8a, 0xbe, 0x79, 0xa3,
0x36, 0x02, 0x3a, 0xb3, 0x11, 0x56, 0xc8, 0x24, 0x64, 0x4a, 0x97, 0xb0, 0x0d, 0x02, 0x1a, 0x29,
0xac, 0xd4, 0x7c, 0xe9, 0xde, 0xfa, 0x4b, 0xe8, 0x9c, 0x2a, 0xa9, 0xaf, 0xb3, 0x4c, 0xcb, 0x88,
0xac, 0x3d, 0x4c, 0x5f, 0x9e, 0xd2, 0x2b, 0xcc, 0xab, 0x56, 0xf9, 0x45, 0x69, 0xbb, 0x49, 0x14,
0x0c, 0xca, 0x97, 0x44, 0xb3, 0x94, 0x73, 0x11, 0x9b, 0x8f, 0x29, 0x9f, 0x17, 0x1f, 0xf0, 0xc4,
0x88, 0xee, 0xe9, 0xf7, 0x3e, 0x55, 0xcf, 0xcb, 0x51, 0x09, 0x86, 0x96, 0x5c, 0x7b, 0xc7, 0x04,
0x3a, 0x82, 0x1b, 0x14, 0x9c, 0x2d, 0x95, 0x95, 0x5d, 0xa8, 0xb9, 0x76, 0xaf, 0x3d, 0xce, 0x68,
0xaa, 0xf4, 0x95, 0x47, 0x86, 0x81, 0x02, 0xfb, 0x97, 0x1d, 0x4b, 0xed, 0xed, 0xe8, 0x4d, 0x07,
0x65, 0x59, 0xae, 0xfa, 0x80, 0x40, 0x4a, 0x93, 0x80, 0x89, 0xa6, 0x7b, 0xa0, 0xf8, 0x3b, 0x7a,
0x4f, 0x92, 0xb8, 0x02, 0xd0, 0x1b, 0xb5, 0xd9, 0x4b, 0xd6, 0xfe, 0x3d, 0xcc, 0x52, 0xa1, 0x09,
0x65, 0xba, 0x28, 0xb7, 0xbf, 0x3e, 0xf5, 0xc2, 0x84, 0x69, 0x34, 0xd3, 0x90, 0xb3, 0x84, 0xcc,
0x28, 0x23, 0x49, 0x38, 0xb3, 0x47, 0xd1, 0x08, 0x05, 0x2b, 0x4a, 0x1b, 0x2e, 0x67, 0xe0, 0x1c,
0xbf, 0xfc, 0x13, 0x1d, 0xba, 0x7f, 0x1a, 0x49, 0x8e, 0xa1, 0x1c, 0x9e, 0x06, 0x48, 0x4f, 0x42,
0x52, 0xdb, 0x51, 0xfa, 0x05, 0x26, 0xfc, 0x08, 0x45, 0xe8, 0x3d, 0xed, 0xa2, 0xda, 0x3e, 0x3a,
0x3d, 0x41, 0x99, 0x4f, 0xfb, 0x29, 0xd3, 0xaa, 0xc5, 0x13, 0xc4, 0x7f, 0x08, 0x8b, 0xb7, 0x3c,
0xcc, 0x12, 0x10, 0x1c, 0x26, 0xbb, 0xf4, 0x07, 0x3d, 0x26, 0xda, 0xe5, 0x67, 0x4b, 0xa2, 0xaf,
0x0f, 0xff, 0x1f, 0xf9, 0xd9, 0x68, 0x64, 0x26, 0xaa, 0xfa, 0xd3, 0x82, 0x66, 0x9d, 0xf4, 0x29,
0x7e, 0xd6, 0x45, 0xb3, 0x7d, 0x54, 0x7a, 0x8a, 0x9b, 0xed, 0xa5, 0x4b, 0xab, 0x12, 0x4f, 0x90,
0x7e, 0xf3, 0x1d, 0x9c, 0x4c, 0xea, 0xb7, 0x9b, 0x8f, 0xf4, 0x31, 0x4f, 0xf9, 0x60, 0x51, 0x74,
0x8b, 0x50, 0x9c, 0xde, 0xe1, 0x95, 0xfc, 0x5c, 0xde, 0x77, 0xfd, 0xfe, 0xfe, 0x3d, 0x6a, 0x01,
0xce, 0x70, 0xd9, 0xa6, 0x71, 0x42, 0x85, 0xae, 0x83, 0xdf, 0x92, 0x38, 0xe2, 0xa6, 0xac, 0xfa,
0x36, 0xd1, 0xc7, 0x9f, 0x36, 0xa5, 0x5d, 0x7c, 0x8a, 0x7f, 0x00, 0x80, 0x33, 0xcb, 0x79, 0xd4,
0x77, 0x9a, 0x4d, 0x52, 0x61, 0xff, 0xc3, 0xa0, 0x6a, 0x56, 0x02, 0x33, 0x39, 0x4d, 0xf3, 0xa4,
0xd1, 0x63, 0xb2, 0x81, 0xe3, 0x9a, 0x65, 0xb6, 0x40, 0x0a, 0xbc, 0x4f, 0xed, 0x47, 0x47, 0x84,
0x73, 0xcf, 0x55, 0x91, 0x25, 0x88, 0x53, 0xfa, 0xea, 0xea, 0xab, 0x02, 0x30, 0x7d, 0x91, 0xf5,
0xf4, 0x99, 0xbe, 0x56, 0xfe, 0x0c, 0xb1, 0xf9, 0x0b, 0x73, 0xaf, 0xe8, 0xf4, 0x99, 0x5f, 0x3c,
0xbe, 0xb2, 0x3c, 0x07, 0x34, 0xb9, 0x7e, 0x76, 0xf6, 0x6a, 0x68, 0x46, 0x9e, 0x59, 0x09, 0x16,
0xa6, 0xff, 0x02, 0xb3, 0x47, 0x5e, 0x2c, 0x43, 0x31, 0x00, 0x00,
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd5, 0x1b, 0xdb, 0x6e, 0xdb, 0x38,
0xf6, 0x3d, 0x5f, 0xc1, 0xe9, 0x74, 0x21, 0x39, 0x71, 0x6d, 0x77, 0xb0, 0x58, 0x2c, 0x92, 0x26,
0x40, 0x9b, 0x5e, 0x90, 0xed, 0x25, 0x83, 0xba, 0x3b, 0x2f, 0x41, 0x1e, 0x68, 0x89, 0xb6, 0xd9,
0xc8, 0xa4, 0x96, 0xa4, 0x93, 0x18, 0x81, 0xff, 0x7d, 0x0f, 0x29, 0x4a, 0x26, 0x25, 0x5a, 0xb6,
0x92, 0xed, 0x74, 0xc7, 0x40, 0x5b, 0x9b, 0x3c, 0x3c, 0x37, 0x1e, 0x9e, 0x1b, 0xd9, 0xe1, 0xe1,
0x77, 0x99, 0x51, 0xa6, 0xd0, 0x44, 0xf0, 0x3b, 0x49, 0xc4, 0x31, 0x52, 0x62, 0x49, 0xfa, 0x28,
0xe1, 0x4c, 0x51, 0xb6, 0x24, 0xe5, 0xef, 0x3c, 0x5b, 0x4a, 0xfd, 0xa7, 0xf8, 0x8d, 0x0e, 0x87,
0x07, 0xc3, 0xc3, 0x59, 0xc6, 0x27, 0x38, 0x43, 0xcf, 0x8f, 0xd1, 0x14, 0x67, 0x12, 0x80, 0x30,
0x9b, 0x2d, 0x33, 0x2c, 0xec, 0x6f, 0x0d, 0x74, 0x10, 0x2d, 0xe1, 0x8b, 0x54, 0x82, 0x26, 0x2a,
0x3a, 0x39, 0x38, 0xb8, 0xc5, 0x02, 0xc9, 0x15, 0x4b, 0xd4, 0x9c, 0xb2, 0x19, 0x3a, 0x2d, 0x57,
0x0c, 0x16, 0x3c, 0x5d, 0x66, 0x24, 0x8e, 0xaa, 0xb9, 0xa8, 0x8f, 0xae, 0xae, 0x7b, 0xb0, 0xa2,
0x1a, 0x19, 0x68, 0x96, 0x04, 0xcf, 0x32, 0x22, 0xe2, 0x68, 0x5c, 0x8e, 0x9e, 0x2b, 0x91, 0x01,
0xec, 0x74, 0x09, 0xbf, 0x29, 0x67, 0x28, 0x7e, 0x2e, 0x13, 0x9e, 0x03, 0x2f, 0xcf, 0xe7, 0x4a,
0xe5, 0x3d, 0xf4, 0x70, 0x80, 0xe0, 0xa3, 0xc9, 0xe6, 0x82, 0xdc, 0xbe, 0xc5, 0x8a, 0x00, 0xd5,
0x51, 0xdf, 0x8c, 0xea, 0x0f, 0x10, 0x26, 0xd9, 0x07, 0xa2, 0x2e, 0x3f, 0xc2, 0xb8, 0x16, 0x0d,
0x48, 0xea, 0xf1, 0x02, 0x8d, 0xa6, 0xc9, 0x88, 0xc1, 0x2c, 0x61, 0xfe, 0x61, 0x7d, 0x52, 0x9b,
0x9c, 0xd2, 0x59, 0x73, 0x7c, 0xb1, 0xba, 0x78, 0x0b, 0xa3, 0x51, 0xe4, 0x8d, 0x32, 0xa0, 0xa4,
0x91, 0x5c, 0x5d, 0x07, 0x90, 0x5c, 0x30, 0x2d, 0x51, 0xc5, 0x82, 0x33, 0x4f, 0x84, 0xe0, 0x22,
0xb0, 0x4e, 0x12, 0xc2, 0xde, 0xe9, 0x39, 0x4b, 0xc9, 0xcc, 0x0d, 0x87, 0x68, 0x0c, 0xba, 0x66,
0x33, 0x89, 0x26, 0x64, 0xca, 0x05, 0x41, 0x13, 0xce, 0x33, 0x89, 0x32, 0xce, 0x6f, 0x60, 0x44,
0x29, 0x22, 0x7c, 0x1c, 0x4a, 0x19, 0x60, 0xc0, 0x5e, 0x69, 0xe4, 0x81, 0xa6, 0xc7, 0x28, 0xfa,
0x44, 0xa5, 0x22, 0x0c, 0x90, 0x81, 0x72, 0x81, 0xef, 0x04, 0x36, 0xd5, 0x28, 0x1d, 0xfd, 0x2e,
0xb8, 0xe2, 0x09, 0xcf, 0x50, 0x01, 0x81, 0x5e, 0xa7, 0xa9, 0x20, 0x52, 0x12, 0x09, 0x80, 0x6a,
0x95, 0x83, 0xc1, 0x44, 0x8a, 0xdc, 0x2b, 0xf8, 0x05, 0xc3, 0x0a, 0x0b, 0x55, 0x58, 0xcc, 0xba,
0x5f, 0x23, 0xf0, 0xe1, 0xdf, 0x17, 0x76, 0xa9, 0x43, 0x01, 0x06, 0x6b, 0x78, 0x3b, 0x62, 0xfd,
0x8c, 0xef, 0xc7, 0x84, 0xa5, 0x1f, 0x27, 0xb9, 0x8b, 0xf6, 0x72, 0xa9, 0x66, 0x5c, 0x1b, 0xdc,
0x57, 0xbd, 0xff, 0x9f, 0xe8, 0x82, 0x2a, 0x14, 0x7f, 0x7c, 0x93, 0xcb, 0xde, 0x06, 0x3d, 0x5b,
0x2e, 0x26, 0x44, 0xec, 0x24, 0xf0, 0x15, 0x50, 0x62, 0x76, 0xc1, 0x40, 0x93, 0xb7, 0x38, 0x1b,
0x3b, 0x44, 0x8a, 0x19, 0x54, 0x4e, 0xa1, 0xf8, 0x51, 0xd8, 0xad, 0xc1, 0x85, 0x09, 0xd8, 0xc9,
0xa7, 0xd1, 0xf8, 0x1d, 0x0b, 0x0c, 0x87, 0x28, 0xfb, 0x4a, 0xfe, 0xb3, 0x04, 0x40, 0x57, 0x4f,
0xa0, 0x3d, 0x04, 0xba, 0x82, 0xd5, 0x2c, 0x35, 0xea, 0xda, 0x80, 0x74, 0xa3, 0x01, 0x88, 0xce,
0xe7, 0x70, 0xb6, 0x49, 0x6d, 0x23, 0x34, 0x81, 0xf7, 0x34, 0x23, 0xa8, 0x98, 0x2d, 0xf6, 0x63,
0xef, 0x9d, 0x68, 0x28, 0x0b, 0xa7, 0x97, 0x2c, 0x5b, 0x79, 0x2a, 0xc2, 0x29, 0xb2, 0x63, 0x16,
0x9b, 0x3e, 0x00, 0x3b, 0xf9, 0x7d, 0x9d, 0x65, 0xfc, 0xee, 0x2d, 0xc9, 0x88, 0x22, 0x0e, 0x36,
0x33, 0x8a, 0xaa, 0xe1, 0x2e, 0x08, 0xdf, 0x73, 0xbd, 0x76, 0xbc, 0x5a, 0x80, 0x6f, 0xbd, 0x71,
0x35, 0x50, 0x4c, 0x20, 0x67, 0xa6, 0x0b, 0xda, 0x0f, 0xc6, 0xe3, 0xbe, 0x66, 0xec, 0x1d, 0xc3,
0x93, 0x8c, 0xa4, 0xee, 0xd1, 0x29, 0x9c, 0x31, 0xcc, 0x71, 0xf0, 0x85, 0x5d, 0xf9, 0xfd, 0xc4,
0x93, 0x2d, 0x78, 0xcd, 0xcc, 0x63, 0xd1, 0x8e, 0xf5, 0xdc, 0x9b, 0x22, 0xb2, 0xb8, 0x8e, 0x44,
0x0f, 0xa3, 0xcd, 0xb8, 0x8b, 0xd2, 0xe2, 0xb8, 0xb6, 0x1e, 0xad, 0x72, 0xec, 0xa5, 0xa3, 0x1e,
0x2f, 0x93, 0x84, 0x90, 0x94, 0xa4, 0x71, 0xe9, 0xdc, 0xf5, 0x87, 0x4e, 0x51, 0xfc, 0xcb, 0xc6,
0x97, 0xbb, 0x53, 0xc6, 0xdf, 0xc5, 0xd1, 0xaf, 0x8c, 0xa8, 0x3b, 0x2e, 0x6e, 0x8c, 0xcf, 0x8c,
0x7a, 0x3a, 0xe2, 0xe0, 0x2c, 0x8e, 0xe6, 0x34, 0x25, 0x51, 0xef, 0xc4, 0x83, 0x0e, 0xc4, 0x84,
0x72, 0x6a, 0x7d, 0x50, 0xfc, 0x1d, 0xe6, 0xed, 0x3d, 0x06, 0xdb, 0x6e, 0x32, 0xf6, 0x08, 0xbe,
0x1e, 0x26, 0x38, 0xb9, 0x49, 0x05, 0xcf, 0x41, 0x2d, 0xa0, 0x60, 0x45, 0x13, 0x50, 0xd3, 0x0d,
0x59, 0x4d, 0x38, 0x16, 0xa9, 0x8d, 0xaf, 0xeb, 0x36, 0xb6, 0x0d, 0xc4, 0x2e, 0xbe, 0x75, 0x48,
0x3a, 0xe7, 0x8b, 0x1c, 0x0b, 0x12, 0xe3, 0x3e, 0x9a, 0xd4, 0x19, 0xc7, 0x83, 0x2f, 0x00, 0xa1,
0x83, 0xd9, 0xe9, 0xa9, 0x1b, 0xdc, 0xea, 0x62, 0x08, 0xa2, 0x96, 0x82, 0xa1, 0x17, 0x2f, 0xeb,
0x04, 0x4b, 0x44, 0x93, 0x6e, 0x88, 0xb6, 0xe2, 0xa9, 0x18, 0x7a, 0x85, 0x4a, 0x94, 0x9d, 0x78,
0xb1, 0x93, 0x15, 0x9a, 0xb3, 0x0a, 0xcd, 0x89, 0xab, 0x20, 0x93, 0x3b, 0x0c, 0x66, 0x44, 0xc5,
0xd1, 0x50, 0x1b, 0xf8, 0xf0, 0x96, 0x08, 0x09, 0x0a, 0x83, 0xed, 0x91, 0xda, 0xfc, 0xa4, 0x8c,
0x37, 0x09, 0x47, 0x8a, 0x15, 0x76, 0x99, 0xb0, 0xe2, 0xd9, 0x25, 0xb0, 0x15, 0x1a, 0xc0, 0xa2,
0xb7, 0x3b, 0xd6, 0xc0, 0x2f, 0x57, 0x10, 0xf3, 0x16, 0x5d, 0xd0, 0x17, 0x2b, 0x3c, 0xec, 0xce,
0xac, 0xcd, 0x40, 0xf4, 0x9c, 0xf9, 0x7e, 0xb2, 0xf1, 0x9d, 0x0d, 0xda, 0x45, 0x02, 0xb2, 0x17,
0x6d, 0x87, 0x42, 0x95, 0xfb, 0xf8, 0xf4, 0x1b, 0x10, 0x83, 0xcb, 0xdc, 0x64, 0x4f, 0x83, 0x2a,
0xa1, 0x40, 0xa7, 0xad, 0x10, 0x36, 0xee, 0x0f, 0xbe, 0x43, 0xc0, 0x8e, 0xc1, 0xe8, 0xf5, 0xe1,
0xf4, 0xd0, 0xeb, 0x5c, 0xae, 0xcc, 0xa5, 0x7c, 0x44, 0x5f, 0x49, 0xce, 0x25, 0x55, 0x5c, 0x50,
0x22, 0xaf, 0x46, 0xd7, 0x66, 0x67, 0xa5, 0xcf, 0x9b, 0x59, 0x38, 0x90, 0x5c, 0xa8, 0xd8, 0x31,
0xfe, 0x5e, 0x50, 0x80, 0x92, 0x08, 0xf3, 0xd1, 0xb8, 0xc7, 0x6e, 0x8b, 0x32, 0x87, 0x3a, 0x69,
0x7d, 0x9c, 0x46, 0xab, 0x44, 0xd0, 0xec, 0x9d, 0x3b, 0xd6, 0xe4, 0x60, 0xdd, 0xf3, 0x93, 0x55,
0x41, 0xa6, 0xc0, 0xc4, 0x5c, 0x9f, 0xfe, 0x8a, 0xa0, 0x67, 0x3a, 0x4f, 0x30, 0xbb, 0xdd, 0xa6,
0xd7, 0xaa, 0x17, 0xe3, 0x9b, 0xba, 0x12, 0x32, 0x8b, 0x82, 0x26, 0x16, 0x08, 0x06, 0x2e, 0x23,
0x45, 0xd2, 0x1c, 0x87, 0xb5, 0xe0, 0xae, 0x2f, 0x1d, 0xf6, 0xfe, 0xbb, 0x5b, 0x56, 0x03, 0x7b,
0xcb, 0x52, 0x98, 0xeb, 0x1d, 0x88, 0xa1, 0x8b, 0x0f, 0x30, 0xaa, 0xbb, 0xb8, 0xd7, 0xf7, 0x20,
0xf4, 0x47, 0xa5, 0x00, 0x10, 0x6b, 0xb8, 0x17, 0x55, 0xa1, 0xd2, 0x43, 0x43, 0xf4, 0x72, 0x34,
0x1a, 0x35, 0xa1, 0x69, 0x5a, 0x3b, 0x13, 0x4e, 0x6d, 0x03, 0x38, 0x82, 0xd6, 0x4c, 0x19, 0xa4,
0x62, 0xba, 0xf6, 0x09, 0xce, 0xf2, 0xa5, 0xaa, 0xa6, 0xbd, 0x79, 0x28, 0x22, 0x50, 0x4c, 0x53,
0x44, 0x19, 0x0a, 0x49, 0x67, 0xb8, 0xd1, 0xb1, 0xd7, 0x98, 0xeb, 0x1c, 0xcb, 0xcb, 0x3b, 0x06,
0x15, 0x42, 0x4e, 0x84, 0x5a, 0xc1, 0xb2, 0x5e, 0x08, 0x5e, 0x7f, 0xca, 0xf2, 0xf2, 0xa4, 0x31,
0xbb, 0x6e, 0xea, 0x46, 0xac, 0xb6, 0x60, 0xd1, 0x44, 0xaf, 0x68, 0x7a, 0x5d, 0x09, 0xf7, 0x19,
0xab, 0xf9, 0x60, 0x81, 0xef, 0xe3, 0x51, 0x1f, 0xfd, 0x13, 0x1d, 0x16, 0x3b, 0x62, 0x20, 0x2e,
0xd8, 0x9b, 0x95, 0x22, 0xf2, 0x1b, 0x57, 0x90, 0xcd, 0xbc, 0x08, 0x94, 0x77, 0x0d, 0x28, 0xad,
0x7e, 0x95, 0xf6, 0x9a, 0x1c, 0x7a, 0x94, 0x2b, 0xc5, 0x6d, 0x27, 0x0d, 0x19, 0xf5, 0x3e, 0xb4,
0x3d, 0xb0, 0xad, 0xc4, 0xd7, 0x28, 0xc1, 0x2a, 0x99, 0xa3, 0x98, 0x6c, 0x53, 0x6d, 0x43, 0x29,
0xa3, 0x7d, 0x45, 0x08, 0x00, 0x36, 0x77, 0xc3, 0x33, 0xa7, 0xa3, 0xd3, 0x1a, 0xb9, 0x26, 0x06,
0xdf, 0xc0, 0xdc, 0x05, 0xc5, 0x90, 0xbf, 0x62, 0x1d, 0x32, 0x4e, 0xbf, 0x08, 0xef, 0xe0, 0x75,
0x18, 0xf8, 0x86, 0x4e, 0x07, 0x95, 0xf6, 0x11, 0xc3, 0x8b, 0x9a, 0x55, 0x16, 0x47, 0xc0, 0xe8,
0x07, 0x51, 0xc8, 0x3c, 0x8c, 0xa9, 0x67, 0x84, 0xcd, 0xd4, 0x1c, 0x06, 0x8e, 0x8e, 0x42, 0x1b,
0xa1, 0xb1, 0xa0, 0x52, 0x56, 0x88, 0x44, 0xf0, 0x73, 0x20, 0xf3, 0x8c, 0x6a, 0xd6, 0xea, 0xe9,
0xe6, 0x66, 0x33, 0xae, 0x07, 0xe3, 0x39, 0x84, 0xa6, 0x2f, 0xc5, 0x62, 0x8d, 0xe3, 0x4a, 0xff,
0x65, 0x89, 0x81, 0xe9, 0xbc, 0xbc, 0x6e, 0x53, 0x97, 0x61, 0xcc, 0xc4, 0xb6, 0x8d, 0xa4, 0xf5,
0xb4, 0xae, 0xfc, 0x14, 0xd9, 0xd4, 0x86, 0x9c, 0x4e, 0xa8, 0xaa, 0x5f, 0xdb, 0x4c, 0x2b, 0x90,
0x5b, 0x85, 0x39, 0x09, 0x51, 0x38, 0xeb, 0x40, 0x61, 0x2f, 0x02, 0x16, 0xb6, 0x66, 0xb5, 0xf5,
0xac, 0xb8, 0x0c, 0xe6, 0x60, 0x0a, 0xdd, 0x8c, 0xa7, 0x68, 0xbc, 0x74, 0x8d, 0x59, 0x55, 0xbb,
0x26, 0x4c, 0x69, 0xed, 0x47, 0x6c, 0x9d, 0x5a, 0x40, 0x31, 0xa4, 0x96, 0xd2, 0x0b, 0xda, 0x26,
0x35, 0x99, 0xce, 0x5c, 0x12, 0xda, 0x3a, 0xf5, 0x39, 0xf0, 0x92, 0x9e, 0xca, 0x7f, 0xd8, 0x05,
0x36, 0x97, 0x75, 0x8c, 0x44, 0x6f, 0x82, 0x06, 0xac, 0x73, 0x5b, 0x8e, 0x0f, 0x74, 0x06, 0x04,
0xf5, 0xad, 0xc9, 0x54, 0x21, 0x37, 0x87, 0x78, 0x13, 0xda, 0x1a, 0xab, 0xea, 0xe8, 0x82, 0x21,
0x9d, 0x8a, 0x44, 0x35, 0x95, 0x23, 0xa2, 0xdb, 0x7d, 0xdb, 0x97, 0xe9, 0x35, 0xba, 0x9f, 0x10,
0x47, 0xe8, 0x08, 0xd5, 0xc9, 0x1e, 0xa1, 0xe8, 0x6f, 0xbd, 0x3a, 0x46, 0x27, 0x77, 0xaf, 0x27,
0xef, 0xd1, 0x5b, 0x2a, 0xad, 0xec, 0x70, 0xba, 0xb7, 0xaa, 0xf5, 0x22, 0x31, 0xc9, 0xf7, 0x5f,
0x40, 0xa9, 0xfc, 0xa6, 0xab, 0x3e, 0x6d, 0xa2, 0xd7, 0x45, 0x69, 0x0b, 0x88, 0xb6, 0x72, 0xbb,
0xb6, 0xce, 0x33, 0x2c, 0xff, 0x22, 0x36, 0x68, 0x8f, 0x63, 0x57, 0x9d, 0xe5, 0x82, 0x2e, 0xb0,
0x58, 0x75, 0xd1, 0x19, 0x65, 0x53, 0xbe, 0x5d, 0x65, 0xba, 0x50, 0xf9, 0x93, 0x35, 0x66, 0x19,
0x33, 0x4a, 0xb3, 0x75, 0x52, 0x4b, 0x95, 0x1b, 0xc5, 0x4b, 0x76, 0x03, 0xe9, 0x20, 0xd4, 0xbb,
0x05, 0x6c, 0xaf, 0x65, 0xff, 0x9d, 0x2d, 0xf8, 0x69, 0x22, 0xd5, 0x9c, 0x42, 0xd4, 0x26, 0xdb,
0x76, 0x51, 0xfe, 0x20, 0x3b, 0xb7, 0x45, 0x33, 0xe3, 0x73, 0xba, 0x67, 0x63, 0xc2, 0xaf, 0xed,
0x43, 0xfc, 0xfd, 0x48, 0xf5, 0x64, 0x94, 0x30, 0xf5, 0xc7, 0x76, 0xda, 0x8d, 0x7d, 0xb7, 0x7c,
0xb6, 0xec, 0xbb, 0x4d, 0x35, 0xf6, 0xd7, 0x56, 0x20, 0x76, 0x5b, 0xb2, 0x2e, 0x48, 0x0b, 0x77,
0xbe, 0x0a, 0x20, 0xb8, 0x4e, 0xa4, 0x12, 0x3a, 0x69, 0xfe, 0x47, 0x38, 0x44, 0x4a, 0x7c, 0x4b,
0xc6, 0x9b, 0x7b, 0x8a, 0x2d, 0x95, 0x6d, 0xb0, 0x84, 0xae, 0xf5, 0xc0, 0xf6, 0xe8, 0x3a, 0xec,
0xe8, 0x4d, 0x8c, 0x95, 0x28, 0xb3, 0xb8, 0xbe, 0x6e, 0xd5, 0xe1, 0xdc, 0x49, 0x0a, 0xee, 0x81,
0xa1, 0x52, 0xc8, 0xfb, 0x81, 0x02, 0x77, 0x03, 0x75, 0x65, 0x20, 0xc5, 0xc8, 0xb9, 0xac, 0xf7,
0x5e, 0xfa, 0xe8, 0x5f, 0xe3, 0xcb, 0x2f, 0x03, 0x69, 0xae, 0x6e, 0xe8, 0x74, 0x15, 0x7b, 0x4c,
0xf4, 0xfa, 0xe8, 0x61, 0x4e, 0x70, 0x0a, 0x9b, 0x79, 0x8c, 0x1e, 0xa2, 0x73, 0x28, 0x9d, 0xc0,
0x0e, 0x5e, 0x7c, 0x5b, 0xe5, 0x24, 0x3a, 0x46, 0x11, 0xce, 0x81, 0x23, 0x28, 0x0a, 0x80, 0x89,
0xe1, 0x77, 0xc9, 0x59, 0xb4, 0xf6, 0x68, 0xc6, 0xd1, 0xaf, 0xe5, 0x35, 0xcf, 0x37, 0xdd, 0xdb,
0x05, 0xbe, 0x13, 0x9e, 0x65, 0x38, 0x97, 0xc4, 0xef, 0x7e, 0xae, 0xeb, 0xcd, 0x04, 0xd3, 0xd8,
0xdd, 0xd1, 0x4c, 0x70, 0x85, 0xb1, 0x2b, 0xdc, 0xf4, 0x76, 0xd7, 0x1d, 0x57, 0x8d, 0x26, 0x49,
0xa9, 0xd2, 0x76, 0xb1, 0xcb, 0x22, 0x4b, 0xb4, 0x4b, 0x21, 0x40, 0x13, 0x76, 0x85, 0x85, 0x6b,
0x10, 0xd7, 0x48, 0x41, 0xfa, 0x77, 0xf7, 0xb0, 0x85, 0xc5, 0xa5, 0xa3, 0xdf, 0xd2, 0x6d, 0x22,
0x1b, 0x54, 0x77, 0x5a, 0x45, 0xbb, 0xaa, 0x34, 0xd9, 0x6a, 0xd8, 0x6b, 0x4e, 0xb9, 0x9a, 0x2e,
0x05, 0x78, 0x54, 0x1f, 0xb7, 0xa6, 0x0c, 0x70, 0xda, 0x0d, 0x5d, 0xec, 0x54, 0xc2, 0x43, 0x71,
0xac, 0x80, 0x1a, 0xd0, 0x71, 0xc5, 0x80, 0x91, 0x74, 0x05, 0xc5, 0x04, 0x30, 0xb0, 0xde, 0x43,
0x45, 0xf5, 0xa3, 0xf3, 0x3f, 0x96, 0x2d, 0x35, 0x77, 0x2a, 0xad, 0xe2, 0x99, 0xbe, 0x09, 0xb9,
0xfb, 0x52, 0xdd, 0x9a, 0xf6, 0x11, 0x75, 0xdb, 0x98, 0x21, 0x8e, 0x1a, 0xdd, 0x7c, 0xd3, 0x9a,
0x08, 0x0b, 0x19, 0xf6, 0x62, 0xae, 0xe3, 0xaa, 0xbe, 0xd6, 0x0b, 0x40, 0xb7, 0x3f, 0xd8, 0x56,
0x08, 0x6a, 0xea, 0x2e, 0xac, 0xa9, 0x04, 0x8b, 0x70, 0xf3, 0xcb, 0x26, 0xdc, 0xb8, 0x86, 0x17,
0x6e, 0x69, 0xeb, 0x4f, 0xa9, 0x8a, 0x41, 0xbe, 0x94, 0xf3, 0x3a, 0xd6, 0x5a, 0xc9, 0x13, 0x4c,
0x6a, 0xea, 0x4d, 0x4d, 0x8b, 0x6f, 0x9b, 0x7b, 0x0c, 0xf6, 0x52, 0xbd, 0x75, 0x1d, 0x9d, 0xef,
0x9f, 0xec, 0xfb, 0x02, 0x51, 0x64, 0xb7, 0xb9, 0x15, 0xe7, 0xbc, 0x8f, 0x52, 0xce, 0x48, 0xcd,
0xdc, 0xf6, 0x92, 0x71, 0x2f, 0x9b, 0xb4, 0x54, 0x50, 0xc8, 0x00, 0x1a, 0x50, 0x1b, 0x9f, 0x13,
0xf2, 0x43, 0x8f, 0x8c, 0x47, 0x15, 0x11, 0x2d, 0x67, 0x53, 0x8c, 0x1f, 0x60, 0xee, 0x3a, 0xbb,
0xf2, 0x03, 0x7f, 0xc8, 0xc6, 0x6b, 0x8b, 0x43, 0x3e, 0xbd, 0xc6, 0xb9, 0xef, 0xc9, 0xcb, 0xcf,
0x44, 0x10, 0x7c, 0xb3, 0xc7, 0x91, 0x28, 0x1a, 0x97, 0x80, 0x69, 0x4b, 0x2d, 0xcf, 0x36, 0x07,
0xae, 0x0c, 0x42, 0x41, 0xff, 0xe0, 0x81, 0xb7, 0xdc, 0x2d, 0xec, 0x77, 0xc2, 0x5c, 0x6c, 0x8d,
0x7b, 0x9b, 0x9f, 0x76, 0x80, 0xb8, 0x9a, 0x13, 0x51, 0xb2, 0xd8, 0x7e, 0x84, 0x2a, 0x77, 0xdd,
0x47, 0xec, 0xe4, 0xe9, 0x5e, 0x94, 0xd5, 0x74, 0x02, 0x96, 0x71, 0xd2, 0xb0, 0x3b, 0x16, 0x70,
0xac, 0xa1, 0x3c, 0xde, 0x20, 0x74, 0xb6, 0xb5, 0xc5, 0x73, 0x96, 0xdf, 0x9c, 0xbc, 0x55, 0x06,
0x55, 0xa3, 0xe6, 0x54, 0xee, 0xf4, 0x2d, 0x3f, 0x45, 0x19, 0x3b, 0x8a, 0x1a, 0x47, 0xba, 0x2b,
0xb6, 0xb5, 0xb3, 0xb8, 0x0e, 0x66, 0x6a, 0xba, 0xcd, 0xa5, 0x93, 0xe2, 0x56, 0xa1, 0x1b, 0x6f,
0x97, 0x2a, 0xd1, 0x8d, 0x4a, 0x1a, 0xe2, 0x17, 0xf0, 0x6d, 0xf2, 0x1b, 0xac, 0x1b, 0x1d, 0x14,
0x0b, 0x82, 0x4a, 0x20, 0x83, 0x6f, 0xd4, 0x74, 0x1d, 0xeb, 0xef, 0xa5, 0x42, 0x5a, 0xb0, 0x84,
0x8d, 0x4d, 0xd4, 0x6f, 0x03, 0x5b, 0x6c, 0xa2, 0x58, 0x16, 0x34, 0x8a, 0x24, 0x23, 0x58, 0xbc,
0x2b, 0xe5, 0x6f, 0xcd, 0xe0, 0xdc, 0x97, 0x5c, 0xbe, 0x5c, 0x21, 0xb5, 0x98, 0x2e, 0xb0, 0x91,
0x2d, 0x48, 0x77, 0x0a, 0xee, 0x84, 0xa5, 0xd9, 0xaa, 0x79, 0x54, 0xc1, 0x41, 0xb8, 0xc4, 0x5b,
0xb6, 0x61, 0xa7, 0x15, 0x9a, 0x22, 0xd7, 0x0d, 0x61, 0x61, 0x5b, 0x94, 0x26, 0x7b, 0x86, 0xbf,
0xa1, 0x98, 0xc8, 0x33, 0x9c, 0x90, 0x38, 0xa9, 0xfc, 0x7f, 0xbf, 0x5e, 0x85, 0xea, 0xb9, 0x9e,
0xe7, 0x5f, 0x6b, 0xca, 0x06, 0x3c, 0x5b, 0x0a, 0x15, 0xd3, 0x0c, 0x2b, 0x2f, 0xf1, 0xa0, 0xd8,
0x29, 0x9f, 0x47, 0xc5, 0x3e, 0x44, 0xdf, 0xdc, 0xa6, 0x8d, 0x00, 0xce, 0x04, 0xc1, 0x4a, 0x33,
0x29, 0x49, 0xe8, 0x02, 0x42, 0x20, 0x68, 0x23, 0x83, 0x53, 0xba, 0x5c, 0xb8, 0xaf, 0x06, 0x53,
0x3a, 0xa3, 0x4a, 0xea, 0xa7, 0x2b, 0x49, 0xe9, 0x8d, 0xb5, 0x75, 0xe9, 0xc7, 0x57, 0xfa, 0x74,
0x79, 0x9d, 0x2a, 0xbf, 0x21, 0x6d, 0x03, 0x44, 0x81, 0xa0, 0xbc, 0x20, 0x9a, 0x66, 0x9c, 0x8b,
0xd8, 0x7c, 0xcd, 0xf8, 0xac, 0xf8, 0x82, 0x27, 0x86, 0x74, 0x4f, 0xdf, 0xf9, 0x54, 0x33, 0x2f,
0x47, 0xa5, 0x32, 0x34, 0xe5, 0xda, 0xfd, 0x12, 0xf0, 0x08, 0x66, 0x50, 0x60, 0xb6, 0x50, 0x96,
0x76, 0xc1, 0xe6, 0xda, 0x7d, 0x36, 0x39, 0xa5, 0x99, 0xd2, 0x4f, 0x26, 0x19, 0x06, 0x08, 0xec,
0x3f, 0x96, 0x2c, 0xb9, 0xb7, 0xab, 0x37, 0x13, 0x94, 0xe5, 0x4b, 0xd5, 0x07, 0x0d, 0x64, 0x34,
0x0d, 0x88, 0x68, 0xa6, 0x07, 0x8a, 0xbf, 0xa7, 0xf7, 0x24, 0x8d, 0x2b, 0x05, 0x7a, 0xab, 0x36,
0x71, 0x64, 0xed, 0xbf, 0xe3, 0x2c, 0x19, 0x9a, 0x50, 0xa6, 0x1b, 0x72, 0xfb, 0xf3, 0x53, 0x6f,
0x4a, 0x98, 0x41, 0xb3, 0x0d, 0x4b, 0x96, 0x92, 0x29, 0x65, 0x24, 0x0d, 0x67, 0xf5, 0x28, 0x1a,
0xa1, 0x60, 0x37, 0x69, 0x83, 0xe5, 0x0c, 0x8c, 0xe3, 0xb7, 0xbf, 0xa3, 0x43, 0xf7, 0x9f, 0x46,
0x82, 0x63, 0x20, 0x87, 0xa7, 0x01, 0xd0, 0x93, 0x10, 0xd5, 0x76, 0x2d, 0xfd, 0x06, 0x1b, 0x7e,
0x84, 0x22, 0xf4, 0x81, 0x76, 0x61, 0x6d, 0x1f, 0x9e, 0x9e, 0xc0, 0xcc, 0xe7, 0xfd, 0x98, 0x69,
0xe5, 0xe2, 0x09, 0xe4, 0x3f, 0x86, 0xc9, 0x5b, 0x1c, 0xe6, 0x08, 0x08, 0x0e, 0x9b, 0x5d, 0xda,
0x83, 0x5e, 0x13, 0xed, 0xb2, 0xb3, 0x05, 0xd1, 0xcf, 0x8f, 0xff, 0x8f, 0xec, 0x6c, 0x34, 0x32,
0x1b, 0x55, 0xfd, 0xd3, 0xa2, 0xcd, 0x3a, 0xe8, 0x53, 0xec, 0xac, 0x0b, 0x67, 0xfb, 0xb0, 0xf4,
0x14, 0x33, 0xdb, 0x8b, 0x97, 0x56, 0x26, 0x9e, 0x40, 0xfd, 0xe6, 0x07, 0x18, 0x99, 0xd4, 0x37,
0x9b, 0x8f, 0xb4, 0x31, 0x8f, 0xf9, 0x60, 0x43, 0x74, 0x0b, 0x51, 0x9c, 0xdd, 0xe1, 0x95, 0xfc,
0x52, 0xbe, 0x97, 0xfd, 0xf1, 0xf6, 0x3d, 0x6a, 0x51, 0x9c, 0xc1, 0xb2, 0x8d, 0xe3, 0x94, 0x0a,
0xdd, 0x03, 0xbf, 0x25, 0x71, 0xc4, 0x4d, 0x4b, 0xf5, 0x5d, 0xaa, 0x4b, 0x9f, 0x36, 0xa6, 0x5d,
0xfd, 0x14, 0xff, 0x81, 0x00, 0xea, 0x95, 0xf3, 0xa8, 0xef, 0x0c, 0x9b, 0xa4, 0xc2, 0xfe, 0x0f,
0x85, 0x6a, 0x58, 0x09, 0xcc, 0x64, 0x92, 0x2d, 0xd3, 0xc6, 0x8c, 0xc9, 0x06, 0x8e, 0x6b, 0x92,
0xd9, 0xe6, 0x28, 0xe0, 0x3e, 0xb5, 0x5f, 0x1d, 0x12, 0xce, 0x83, 0x56, 0x45, 0x16, 0x40, 0x4e,
0xe9, 0x77, 0xaa, 0xaf, 0x0a, 0x85, 0xe9, 0x57, 0xab, 0xa7, 0xcf, 0xf4, 0xb3, 0xf4, 0x67, 0x88,
0xcd, 0x5e, 0x98, 0x37, 0x45, 0xa7, 0xcf, 0xfc, 0xc6, 0xf1, 0x95, 0xc5, 0x39, 0xa0, 0xe9, 0xf5,
0xb3, 0xb3, 0x57, 0x43, 0xb3, 0xf2, 0xcc, 0x52, 0xb0, 0x6a, 0xfa, 0x2f, 0x74, 0x32, 0xb7, 0xe7,
0x83, 0x31, 0x00, 0x00,
}))
data, _ = ioutil.ReadAll(gr)
Assets["app.js"] = data

View File

@@ -44,9 +44,10 @@ type OptionsConfiguration struct {
RescanIntervalS int `xml:"rescanIntervalS" default:"60" ini:"rescan-interval"`
ReconnectIntervalS int `xml:"reconnectionIntervalS" default:"60" ini:"reconnection-interval"`
MaxChangeKbps int `xml:"maxChangeKbps" default:"1000" ini:"max-change-bw"`
StartBrowser bool `xml:"startBrowser" default:"true"`
}
func setDefaults(data interface{}, setEmptySlices bool) error {
func setDefaults(data interface{}) error {
s := reflect.ValueOf(data).Elem()
t := s.Type()
@@ -56,21 +57,10 @@ func setDefaults(data interface{}, setEmptySlices bool) error {
v := tag.Get("default")
if len(v) > 0 {
if f.Kind().String() == "slice" && f.Len() != 0 {
continue
}
switch f.Interface().(type) {
case string:
f.SetString(v)
case []string:
if setEmptySlices {
rv := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1)
rv.Index(0).SetString(v)
f.Set(rv)
}
case int:
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
@@ -81,6 +71,11 @@ func setDefaults(data interface{}, setEmptySlices bool) error {
case bool:
f.SetBool(v == "true")
case []string:
// We don't do anything with string slices here. Any default
// we set will be appended to by the XML decoder, so we fill
// those after decoding.
default:
panic(f.Type())
}
@@ -89,6 +84,30 @@ func setDefaults(data interface{}, setEmptySlices bool) error {
return nil
}
// fillNilSlices sets default value on slices that are still nil.
func fillNilSlices(data interface{}) error {
s := reflect.ValueOf(data).Elem()
t := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
tag := t.Field(i).Tag
v := tag.Get("default")
if len(v) > 0 {
switch f.Interface().(type) {
case []string:
if f.IsNil() {
rv := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1)
rv.Index(0).SetString(v)
f.Set(rv)
}
}
}
}
return nil
}
func readConfigINI(m map[string]string, data interface{}) error {
s := reflect.ValueOf(data).Elem()
t := s.Type()
@@ -152,15 +171,15 @@ func uniqueStrings(ss []string) []string {
func readConfigXML(rd io.Reader) (Configuration, error) {
var cfg Configuration
setDefaults(&cfg, false)
setDefaults(&cfg.Options, false)
setDefaults(&cfg)
setDefaults(&cfg.Options)
var err error
if rd != nil {
err = xml.NewDecoder(rd).Decode(&cfg)
}
setDefaults(&cfg.Options, true)
fillNilSlices(&cfg.Options)
cfg.Options.ListenAddress = uniqueStrings(cfg.Options.ListenAddress)
return cfg, err

View File

@@ -0,0 +1,116 @@
package main
import (
"bytes"
"io"
"reflect"
"testing"
)
func TestDefaultValues(t *testing.T) {
expected := OptionsConfiguration{
ListenAddress: []string{":22000"},
ReadOnly: false,
AllowDelete: true,
FollowSymlinks: true,
GUIEnabled: true,
GUIAddress: "127.0.0.1:8080",
GlobalAnnServer: "announce.syncthing.net:22025",
GlobalAnnEnabled: true,
LocalAnnEnabled: true,
ParallelRequests: 16,
MaxSendKbps: 0,
RescanIntervalS: 60,
ReconnectIntervalS: 60,
MaxChangeKbps: 1000,
StartBrowser: true,
}
cfg, err := readConfigXML(bytes.NewReader(nil))
if err != io.EOF {
t.Error(err)
}
if !reflect.DeepEqual(cfg.Options, expected) {
t.Errorf("Default config differs;\n E: %#v\n A: %#v", expected, cfg.Options)
}
}
func TestNoListenAddress(t *testing.T) {
data := []byte(`<configuration version="1">
<repository directory="~/Sync">
<node id="..." name="...">
<address>dynamic</address>
</node>
</repository>
<options>
<listenAddress></listenAddress>
</options>
</configuration>
`)
cfg, err := readConfigXML(bytes.NewReader(data))
if err != nil {
t.Error(err)
}
expected := []string{""}
if !reflect.DeepEqual(cfg.Options.ListenAddress, expected) {
t.Errorf("Unexpected ListenAddress %#v", cfg.Options.ListenAddress)
}
}
func TestOverriddenValues(t *testing.T) {
data := []byte(`<configuration version="1">
<repository directory="~/Sync">
<node id="..." name="...">
<address>dynamic</address>
</node>
</repository>
<options>
<listenAddress>:23000</listenAddress>
<readOnly>true</readOnly>
<allowDelete>false</allowDelete>
<followSymlinks>false</followSymlinks>
<guiEnabled>false</guiEnabled>
<guiAddress>125.2.2.2:8080</guiAddress>
<globalAnnounceServer>syncthing.nym.se:22025</globalAnnounceServer>
<globalAnnounceEnabled>false</globalAnnounceEnabled>
<localAnnounceEnabled>false</localAnnounceEnabled>
<parallelRequests>32</parallelRequests>
<maxSendKbps>1234</maxSendKbps>
<rescanIntervalS>600</rescanIntervalS>
<reconnectionIntervalS>6000</reconnectionIntervalS>
<maxChangeKbps>2345</maxChangeKbps>
<startBrowser>false</startBrowser>
</options>
</configuration>
`)
expected := OptionsConfiguration{
ListenAddress: []string{":23000"},
ReadOnly: true,
AllowDelete: false,
FollowSymlinks: false,
GUIEnabled: false,
GUIAddress: "125.2.2.2:8080",
GlobalAnnServer: "syncthing.nym.se:22025",
GlobalAnnEnabled: false,
LocalAnnEnabled: false,
ParallelRequests: 32,
MaxSendKbps: 1234,
RescanIntervalS: 600,
ReconnectIntervalS: 6000,
MaxChangeKbps: 2345,
StartBrowser: false,
}
cfg, err := readConfigXML(bytes.NewReader(data))
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(cfg.Options, expected) {
t.Errorf("Overridden config differs;\n E: %#v\n A: %#v", expected, cfg.Options)
}
}

15
cmd/syncthing/debug.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"log"
"os"
"strings"
)
var (
dlog = log.New(os.Stderr, "main: ", log.Lmicroseconds|log.Lshortfile)
debugNet = strings.Contains(os.Getenv("STTRACE"), "net")
debugIdx = strings.Contains(os.Getenv("STTRACE"), "idx")
debugNeed = strings.Contains(os.Getenv("STTRACE"), "need")
debugPull = strings.Contains(os.Getenv("STTRACE"), "pull")
)

View File

@@ -4,13 +4,13 @@ import (
"bytes"
"errors"
"fmt"
"log"
"os"
"path"
"sync"
"time"
"github.com/calmh/syncthing/buffers"
"github.com/calmh/syncthing/scanner"
)
type fileMonitor struct {
@@ -18,18 +18,18 @@ type fileMonitor struct {
path string // full path
writeDone sync.WaitGroup
model *Model
global File
localBlocks []Block
global scanner.File
localBlocks []scanner.Block
copyError error
writeError error
}
func (m *fileMonitor) FileBegins(cc <-chan content) error {
if m.model.trace["file"] {
log.Printf("FILE: FileBegins: " + m.name)
if debugPull {
dlog.Println("file begins:", m.name)
}
tmp := tempName(m.path, m.global.Modified)
tmp := defTempNamer.TempName(m.path)
dir := path.Dir(tmp)
_, err := os.Stat(dir)
@@ -109,13 +109,13 @@ func (m *fileMonitor) copyRemoteBlocks(cc <-chan content, outFile *os.File, writ
}
func (m *fileMonitor) FileDone() error {
if m.model.trace["file"] {
log.Printf("FILE: FileDone: " + m.name)
if debugPull {
dlog.Println("file done:", m.name)
}
m.writeDone.Wait()
tmp := tempName(m.path, m.global.Modified)
tmp := defTempNamer.TempName(m.path)
defer os.Remove(tmp)
if m.copyError != nil {
@@ -149,14 +149,14 @@ func (m *fileMonitor) FileDone() error {
return nil
}
func hashCheck(name string, correct []Block) error {
func hashCheck(name string, correct []scanner.Block) error {
rf, err := os.Open(name)
if err != nil {
return err
}
defer rf.Close()
current, err := Blocks(rf, BlockSize)
current, err := scanner.Blocks(rf, BlockSize)
if err != nil {
return err
}

View File

@@ -5,6 +5,8 @@ import (
"sort"
"sync"
"time"
"github.com/calmh/syncthing/scanner"
)
type Monitor interface {
@@ -23,7 +25,7 @@ type FileQueue struct {
type queuedFile struct {
name string
blocks []Block
blocks []scanner.Block
activeBlocks []bool
given int
remaining int
@@ -54,7 +56,7 @@ func (l queuedFileList) Less(a, b int) bool {
type queuedBlock struct {
name string
block Block
block scanner.Block
index int
}
@@ -65,7 +67,7 @@ func NewFileQueue() *FileQueue {
}
}
func (q *FileQueue) Add(name string, blocks []Block, monitor Monitor) {
func (q *FileQueue) Add(name string, blocks []scanner.Block, monitor Monitor) {
q.fmut.Lock()
defer q.fmut.Unlock()

View File

@@ -5,6 +5,8 @@ import (
"sync"
"sync/atomic"
"testing"
"github.com/calmh/syncthing/scanner"
)
func TestFileQueueAdd(t *testing.T) {
@@ -17,8 +19,8 @@ func TestFileQueueAddSorting(t *testing.T) {
q.SetAvailable("zzz", []string{"nodeID"})
q.SetAvailable("aaa", []string{"nodeID"})
q.Add("zzz", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
q.Add("aaa", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
q.Add("zzz", []scanner.Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
q.Add("aaa", []scanner.Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
b, _ := q.Get("nodeID")
if b.name != "aaa" {
t.Errorf("Incorrectly sorted get: %+v", b)
@@ -28,12 +30,12 @@ func TestFileQueueAddSorting(t *testing.T) {
q.SetAvailable("zzz", []string{"nodeID"})
q.SetAvailable("aaa", []string{"nodeID"})
q.Add("zzz", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
q.Add("zzz", []scanner.Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
b, _ = q.Get("nodeID") // Start on zzzz
if b.name != "zzz" {
t.Errorf("Incorrectly sorted get: %+v", b)
}
q.Add("aaa", []Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
q.Add("aaa", []scanner.Block{{Offset: 0, Size: 128}, {Offset: 128, Size: 128}}, nil)
b, _ = q.Get("nodeID")
if b.name != "zzz" {
// Continue rather than starting a new file
@@ -56,12 +58,12 @@ func TestFileQueueGet(t *testing.T) {
q.SetAvailable("foo", []string{"nodeID"})
q.SetAvailable("bar", []string{"nodeID"})
q.Add("foo", []Block{
q.Add("foo", []scanner.Block{
{Offset: 0, Size: 128, Hash: []byte("some foo hash bytes")},
{Offset: 128, Size: 128, Hash: []byte("some other foo hash bytes")},
{Offset: 256, Size: 128, Hash: []byte("more foo hash bytes")},
}, nil)
q.Add("bar", []Block{
q.Add("bar", []scanner.Block{
{Offset: 0, Size: 128, Hash: []byte("some bar hash bytes")},
{Offset: 128, Size: 128, Hash: []byte("some other bar hash bytes")},
}, nil)
@@ -70,7 +72,7 @@ func TestFileQueueGet(t *testing.T) {
expected := queuedBlock{
name: "bar",
block: Block{
block: scanner.Block{
Offset: 0,
Size: 128,
Hash: []byte("some bar hash bytes"),
@@ -89,7 +91,7 @@ func TestFileQueueGet(t *testing.T) {
expected = queuedBlock{
name: "bar",
block: Block{
block: scanner.Block{
Offset: 128,
Size: 128,
Hash: []byte("some other bar hash bytes"),
@@ -109,7 +111,7 @@ func TestFileQueueGet(t *testing.T) {
expected = queuedBlock{
name: "foo",
block: Block{
block: scanner.Block{
Offset: 0,
Size: 128,
Hash: []byte("some foo hash bytes"),
@@ -150,7 +152,7 @@ func TestFileQueueDone(t *testing.T) {
}()
q := FileQueue{resolver: fakeResolver{}}
q.Add("foo", []Block{
q.Add("foo", []scanner.Block{
{Offset: 0, Length: 128, Hash: []byte("some foo hash bytes")},
{Offset: 128, Length: 128, Hash: []byte("some other foo hash bytes")},
}, ch)
@@ -181,19 +183,19 @@ func TestFileQueueGetNodeIDs(t *testing.T) {
q.SetAvailable("a-foo", []string{"nodeID", "a"})
q.SetAvailable("b-bar", []string{"nodeID", "b"})
q.Add("a-foo", []Block{
q.Add("a-foo", []scanner.Block{
{Offset: 0, Size: 128, Hash: []byte("some foo hash bytes")},
{Offset: 128, Size: 128, Hash: []byte("some other foo hash bytes")},
{Offset: 256, Size: 128, Hash: []byte("more foo hash bytes")},
}, nil)
q.Add("b-bar", []Block{
q.Add("b-bar", []scanner.Block{
{Offset: 0, Size: 128, Hash: []byte("some bar hash bytes")},
{Offset: 128, Size: 128, Hash: []byte("some other bar hash bytes")},
}, nil)
expected := queuedBlock{
name: "b-bar",
block: Block{
block: scanner.Block{
Offset: 0,
Size: 128,
Hash: []byte("some bar hash bytes"),
@@ -209,7 +211,7 @@ func TestFileQueueGetNodeIDs(t *testing.T) {
expected = queuedBlock{
name: "a-foo",
block: Block{
block: scanner.Block{
Offset: 0,
Size: 128,
Hash: []byte("some foo hash bytes"),
@@ -225,7 +227,7 @@ func TestFileQueueGetNodeIDs(t *testing.T) {
expected = queuedBlock{
name: "a-foo",
block: Block{
block: scanner.Block{
Offset: 128,
Size: 128,
Hash: []byte("some other foo hash bytes"),
@@ -246,9 +248,9 @@ func TestFileQueueThreadHandling(t *testing.T) {
const n = 100
var total int
var blocks []Block
var blocks []scanner.Block
for i := 1; i <= n; i++ {
blocks = append(blocks, Block{Offset: int64(i), Size: 1})
blocks = append(blocks, scanner.Block{Offset: int64(i), Size: 1})
total += i
}

View File

@@ -9,6 +9,7 @@ import (
"sync"
"time"
"github.com/calmh/syncthing/scanner"
"github.com/codegangsta/martini"
)
@@ -107,7 +108,7 @@ func restPostRestart(req *http.Request) {
restart()
}
type guiFile File
type guiFile scanner.File
func (f guiFile) MarshalJSON() ([]byte, error) {
type t struct {
@@ -116,7 +117,7 @@ func (f guiFile) MarshalJSON() ([]byte, error) {
}
return json.Marshal(t{
Name: f.Name,
Size: File(f).Size,
Size: scanner.File(f).Size,
})
}

View File

@@ -13,16 +13,6 @@ func init() {
logger = log.New(os.Stderr, "", log.Flags())
}
func debugln(vals ...interface{}) {
s := fmt.Sprintln(vals...)
logger.Output(2, "DEBUG: "+s)
}
func debugf(format string, vals ...interface{}) {
s := fmt.Sprintf(format, vals...)
logger.Output(2, "DEBUG: "+s)
}
func infoln(vals ...interface{}) {
s := fmt.Sprintln(vals...)
logger.Output(2, "INFO: "+s)

View File

@@ -21,8 +21,11 @@ import (
"github.com/calmh/ini"
"github.com/calmh/syncthing/discover"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner"
)
const BlockSize = 128 * 1024
var cfg Configuration
var Version = "unknown-dev"
@@ -31,26 +34,38 @@ var (
)
var (
showVersion bool
confDir string
trace string
profiler string
verbose bool
startupDelay int
showVersion bool
confDir string
verbose bool
)
const (
usage = "syncthing [options]"
extraUsage = `The following environemnt variables can be set to facilitate debugging:
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
STTRACE A comma separated string of facilities to trace. The valid
facility strings:
- "scanner" (the file change scanner)
- "discover" (the node discovery package)
- "net" (connecting and disconnecting, sent/received messages)
- "idx" (index sending and receiving)
- "need" (file need calculations)
- "pull" (file pull activity)`
)
func main() {
flag.StringVar(&confDir, "home", getDefaultConfDir(), "Set configuration directory")
flag.StringVar(&trace, "debug.trace", "", "(connect,net,idx,file,pull)")
flag.StringVar(&profiler, "debug.profiler", "", "(addr)")
flag.BoolVar(&showVersion, "version", false, "Show version")
flag.BoolVar(&verbose, "v", false, "Be more verbose")
flag.IntVar(&startupDelay, "delay", 0, "Startup delay (s)")
flag.Usage = usageFor(flag.CommandLine, "syncthing [options]")
flag.Usage = usageFor(flag.CommandLine, usage, extraUsage)
flag.Parse()
if startupDelay > 0 {
time.Sleep(time.Duration(startupDelay) * time.Second)
if len(os.Getenv("STRESTART")) > 0 {
// Give the parent process time to exit and release sockets etc.
time.Sleep(1 * time.Second)
}
if showVersion {
@@ -66,10 +81,6 @@ func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
if len(trace) > 0 {
log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds)
logger.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds)
}
confDir = expandTilde(confDir)
// Ensure that our home directory exists and that we have a certificate and key.
@@ -154,11 +165,12 @@ func main() {
var dir = expandTilde(cfg.Repositories[0].Directory)
if len(profiler) > 0 {
if profiler := os.Getenv("STPROFILER"); len(profiler) > 0 {
go func() {
dlog.Println("Starting profiler on", profiler)
err := http.ListenAndServe(profiler, nil)
if err != nil {
warnln(err)
dlog.Fatal(err)
}
}()
}
@@ -178,9 +190,6 @@ func main() {
ensureDir(dir, -1)
m := NewModel(dir, cfg.Options.MaxChangeKbps*1000)
for _, t := range strings.Split(trace, ",") {
m.Trace(t)
}
if cfg.Options.MaxSendKbps > 0 {
m.LimitRate(cfg.Options.MaxSendKbps)
}
@@ -206,7 +215,9 @@ func main() {
infof("Starting web GUI on http://%s:%d/", hostShow, addr.Port)
startGUI(cfg.Options.GUIAddress, m)
openURL(fmt.Sprintf("http://%s:%d", hostOpen, addr.Port))
if cfg.Options.StartBrowser && len(os.Getenv("STRESTART")) == 0 {
openURL(fmt.Sprintf("http://%s:%d", hostOpen, addr.Port))
}
}
}
@@ -217,7 +228,17 @@ func main() {
infoln("Populating repository index")
}
loadIndex(m)
updateLocalModel(m)
sup := &suppressor{threshold: int64(cfg.Options.MaxChangeKbps)}
w := &scanner.Walker{
Dir: m.dir,
IgnoreFile: ".stignore",
FollowSymlinks: cfg.Options.FollowSymlinks,
BlockSize: BlockSize,
Suppressor: sup,
TempNamer: defTempNamer,
}
updateLocalModel(m, w)
connOpts := map[string]string{
"clientId": "syncthing",
@@ -263,7 +284,7 @@ func main() {
for {
time.Sleep(td)
if m.LocalAge() > (td / 2).Seconds() {
updateLocalModel(m)
updateLocalModel(m, w)
}
}
}()
@@ -278,24 +299,17 @@ func main() {
func restart() {
infoln("Restarting")
args := os.Args
doAppend := true
for _, arg := range args {
if arg == "-delay" {
doAppend = false
break
}
}
if doAppend {
args = append(args, "-delay", "2")
env := os.Environ()
if len(os.Getenv("STRESTART")) == 0 {
env = append(env, "STRESTART=1")
}
pgm, err := exec.LookPath(os.Args[0])
if err != nil {
warnln(err)
return
}
proc, err := os.StartProcess(pgm, args, &os.ProcAttr{
Env: os.Environ(),
proc, err := os.StartProcess(pgm, os.Args, &os.ProcAttr{
Env: env,
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
})
if err != nil {
@@ -378,8 +392,8 @@ func printStatsLoop(m *Model) {
}
func listen(myID string, addr string, m *Model, tlsCfg *tls.Config, connOpts map[string]string) {
if strings.Contains(trace, "connect") {
debugln("NET: Listening on", addr)
if debugNet {
dlog.Println("listening on", addr)
}
l, err := tls.Listen("tcp", addr, tlsCfg)
fatalErr(err)
@@ -392,8 +406,8 @@ listen:
continue
}
if strings.Contains(trace, "connect") {
debugln("NET: Connect from", conn.RemoteAddr())
if debugNet {
dlog.Println("connect from", conn.RemoteAddr())
}
tc := conn.(*tls.Conn)
@@ -474,13 +488,13 @@ func connect(myID string, disc *discover.Discoverer, m *Model, tlsCfg *tls.Confi
}
}
if strings.Contains(trace, "connect") {
debugln("NET: Dial", nodeCfg.NodeID, addr)
if debugNet {
dlog.Println("dial", nodeCfg.NodeID, addr)
}
conn, err := tls.Dial("tcp", addr, tlsCfg)
if err != nil {
if strings.Contains(trace, "connect") {
debugln("NET:", err)
if debugNet {
dlog.Println(err)
}
continue
}
@@ -502,8 +516,8 @@ func connect(myID string, disc *discover.Discoverer, m *Model, tlsCfg *tls.Confi
}
}
func updateLocalModel(m *Model) {
files, _ := m.Walk(cfg.Options.FollowSymlinks)
func updateLocalModel(m *Model, w *scanner.Walker) {
files, _ := w.Walk()
m.ReplaceLocal(files)
saveIndex(m)
}

View File

@@ -13,16 +13,17 @@ import (
"github.com/calmh/syncthing/buffers"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner"
)
type Model struct {
dir string
global map[string]File // the latest version of each file as it exists in the cluster
gmut sync.RWMutex // protects global
local map[string]File // the files we currently have locally on disk
lmut sync.RWMutex // protects local
remote map[string]map[string]File
global map[string]scanner.File // the latest version of each file as it exists in the cluster
gmut sync.RWMutex // protects global
local map[string]scanner.File // the files we currently have locally on disk
lmut sync.RWMutex // protects local
remote map[string]map[string]scanner.File
rmut sync.RWMutex // protects remote
protoConn map[string]Connection
rawConn map[string]io.Closer
@@ -31,7 +32,7 @@ type Model struct {
// Queue for files to fetch. fq can call back into the model, so we must ensure
// to hold no locks when calling methods on fq.
fq *FileQueue
dq chan File // queue for files to delete
dq chan scanner.File // queue for files to delete
updatedLocal int64 // timestamp of last update to local
updateGlobal int64 // timestamp of last update to remote
@@ -43,8 +44,6 @@ type Model struct {
delete bool
initmut sync.Mutex // protects rwRunning and delete
trace map[string]bool
sup suppressor
parallelRequests int
@@ -77,16 +76,15 @@ var (
func NewModel(dir string, maxChangeBw int) *Model {
m := &Model{
dir: dir,
global: make(map[string]File),
local: make(map[string]File),
remote: make(map[string]map[string]File),
global: make(map[string]scanner.File),
local: make(map[string]scanner.File),
remote: make(map[string]map[string]scanner.File),
protoConn: make(map[string]Connection),
rawConn: make(map[string]io.Closer),
lastIdxBcast: time.Now(),
trace: make(map[string]bool),
sup: suppressor{threshold: int64(maxChangeBw)},
fq: NewFileQueue(),
dq: make(chan File),
dq: make(chan scanner.File),
}
go m.broadcastIndexLoop()
@@ -108,11 +106,6 @@ func (m *Model) LimitRate(kbps int) {
}()
}
// Trace enables trace logging of the given facility. This is a debugging function; grep for m.trace.
func (m *Model) Trace(t string) {
m.trace[t] = true
}
// StartRW starts read/write processing on the current model. When in
// read/write mode the model will attempt to keep in sync with the cluster by
// pulling needed files from peer nodes.
@@ -128,7 +121,6 @@ func (m *Model) StartRW(del bool, threads int) {
m.delete = del
m.parallelRequests = threads
go m.cleanTempFiles()
if del {
go m.deleteLoop()
}
@@ -260,7 +252,7 @@ func (m *Model) InSyncSize() (files, bytes int64) {
}
// NeedFiles returns the list of currently needed files and the total size.
func (m *Model) NeedFiles() (files []File, bytes int64) {
func (m *Model) NeedFiles() (files []scanner.File, bytes int64) {
qf := m.fq.QueuedFiles()
m.gmut.RLock()
@@ -278,7 +270,7 @@ func (m *Model) NeedFiles() (files []File, bytes int64) {
// Index is called when a new node is connected and we receive their full index.
// Implements the protocol.Model interface.
func (m *Model) Index(nodeID string, fs []protocol.FileInfo) {
var files = make([]File, len(fs))
var files = make([]scanner.File, len(fs))
for i := range fs {
files[i] = fileFromFileInfo(fs[i])
}
@@ -286,11 +278,11 @@ func (m *Model) Index(nodeID string, fs []protocol.FileInfo) {
m.imut.Lock()
defer m.imut.Unlock()
if m.trace["net"] {
debugf("NET IDX(in): %s: %d files", nodeID, len(fs))
if debugNet {
dlog.Printf("IDX(in): %s: %d files", nodeID, len(fs))
}
repo := make(map[string]File)
repo := make(map[string]scanner.File)
for _, f := range files {
m.indexUpdate(repo, f)
}
@@ -306,7 +298,7 @@ func (m *Model) Index(nodeID string, fs []protocol.FileInfo) {
// IndexUpdate is called for incremental updates to connected nodes' indexes.
// Implements the protocol.Model interface.
func (m *Model) IndexUpdate(nodeID string, fs []protocol.FileInfo) {
var files = make([]File, len(fs))
var files = make([]scanner.File, len(fs))
for i := range fs {
files[i] = fileFromFileInfo(fs[i])
}
@@ -314,8 +306,8 @@ func (m *Model) IndexUpdate(nodeID string, fs []protocol.FileInfo) {
m.imut.Lock()
defer m.imut.Unlock()
if m.trace["net"] {
debugf("NET IDXUP(in): %s: %d files", nodeID, len(files))
if debugNet {
dlog.Printf("IDXUP(in): %s: %d files", nodeID, len(files))
}
m.rmut.Lock()
@@ -335,13 +327,13 @@ func (m *Model) IndexUpdate(nodeID string, fs []protocol.FileInfo) {
m.recomputeNeedForFiles(files)
}
func (m *Model) indexUpdate(repo map[string]File, f File) {
if m.trace["idx"] {
func (m *Model) indexUpdate(repo map[string]scanner.File, f scanner.File) {
if debugIdx {
var flagComment string
if f.Flags&protocol.FlagDeleted != 0 {
flagComment = " (deleted)"
}
debugf("IDX(in): %q m=%d f=%o%s v=%d (%d blocks)", f.Name, f.Modified, f.Flags, flagComment, f.Version, len(f.Blocks))
dlog.Printf("IDX(in): %q m=%d f=%o%s v=%d (%d blocks)", f.Name, f.Modified, f.Flags, flagComment, f.Version, len(f.Blocks))
}
if extraFlags := f.Flags &^ (protocol.FlagInvalid | protocol.FlagDeleted | 0xfff); extraFlags != 0 {
@@ -355,8 +347,8 @@ func (m *Model) indexUpdate(repo map[string]File, f File) {
// Close removes the peer from the model and closes the underlying connection if possible.
// Implements the protocol.Model interface.
func (m *Model) Close(node string, err error) {
if m.trace["net"] {
debugf("NET: %s: %v", node, err)
if debugNet {
dlog.Printf("%s: %v", node, err)
}
if err == protocol.ErrClusterHash {
warnf("Connection to %s closed due to mismatched cluster hash. Ensure that the configured cluster members are identical on both nodes.", node)
@@ -405,8 +397,8 @@ func (m *Model) Request(nodeID, repo, name string, offset int64, size int) ([]by
return nil, ErrInvalid
}
if m.trace["net"] && nodeID != "<local>" {
debugf("NET REQ(in): %s: %q o=%d s=%d", nodeID, name, offset, size)
if debugNet && nodeID != "<local>" {
dlog.Printf("REQ(in): %s: %q o=%d s=%d", nodeID, name, offset, size)
}
fn := path.Join(m.dir, name)
fd, err := os.Open(fn) // XXX: Inefficient, should cache fd?
@@ -431,9 +423,9 @@ func (m *Model) Request(nodeID, repo, name string, offset int64, size int) ([]by
}
// ReplaceLocal replaces the local repository index with the given list of files.
func (m *Model) ReplaceLocal(fs []File) {
func (m *Model) ReplaceLocal(fs []scanner.File) {
var updated bool
var newLocal = make(map[string]File)
var newLocal = make(map[string]scanner.File)
m.lmut.RLock()
for _, f := range fs {
@@ -474,7 +466,7 @@ func (m *Model) ReplaceLocal(fs []File) {
// the local index from a cache file at startup.
func (m *Model) SeedLocal(fs []protocol.FileInfo) {
m.lmut.Lock()
m.local = make(map[string]File)
m.local = make(map[string]scanner.File)
for _, f := range fs {
m.local[f.Name] = fileFromFileInfo(f)
}
@@ -509,6 +501,9 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn Connection) {
go func() {
idx := m.ProtocolIndex()
if debugNet {
dlog.Printf("IDX(out/initial): %s: %d files", nodeID, len(idx))
}
protoConn.Index("default", idx)
}()
@@ -522,14 +517,14 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn Connection) {
for i := 0; i < m.parallelRequests; i++ {
i := i
go func() {
if m.trace["pull"] {
debugln("PULL: Starting", nodeID, i)
if debugPull {
dlog.Println("starting puller:", nodeID, i)
}
for {
m.pmut.RLock()
if _, ok := m.protoConn[nodeID]; !ok {
if m.trace["pull"] {
debugln("PULL: Exiting", nodeID, i)
if debugPull {
dlog.Println("stopping puller:", nodeID, i)
}
m.pmut.RUnlock()
return
@@ -538,8 +533,8 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn Connection) {
qb, ok := m.fq.Get(nodeID)
if ok {
if m.trace["pull"] {
debugln("PULL: Request", nodeID, i, qb.name, qb.block.Offset)
if debugPull {
dlog.Println("request: out", nodeID, i, qb.name, qb.block.Offset)
}
data, _ := protoConn.Request("default", qb.name, qb.block.Offset, int(qb.block.Size))
m.fq.Done(qb.name, qb.block.Offset, data)
@@ -560,12 +555,12 @@ func (m *Model) ProtocolIndex() []protocol.FileInfo {
for _, f := range m.local {
mf := fileInfoFromFile(f)
if m.trace["idx"] {
if debugIdx {
var flagComment string
if mf.Flags&protocol.FlagDeleted != 0 {
flagComment = " (deleted)"
}
debugf("IDX(out): %q m=%d f=%o%s v=%d (%d blocks)", mf.Name, mf.Modified, mf.Flags, flagComment, mf.Version, len(mf.Blocks))
dlog.Printf("IDX(out): %q m=%d f=%o%s v=%d (%d blocks)", mf.Name, mf.Modified, mf.Flags, flagComment, mf.Version, len(mf.Blocks))
}
index = append(index, mf)
}
@@ -583,8 +578,8 @@ func (m *Model) requestGlobal(nodeID, name string, offset int64, size int, hash
return nil, fmt.Errorf("requestGlobal: no such node: %s", nodeID)
}
if m.trace["net"] {
debugf("NET REQ(out): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
if debugNet {
dlog.Printf("REQ(out): %s: %q o=%d s=%d h=%x", nodeID, name, offset, size, hash)
}
return nc.Request("default", name, offset, size)
@@ -611,8 +606,8 @@ func (m *Model) broadcastIndexLoop() {
m.pmut.RLock()
for _, node := range m.protoConn {
node := node
if m.trace["net"] {
debugf("NET IDX(out/loop): %s: %d files", node.ID(), len(idx))
if debugNet {
dlog.Printf("IDX(out/loop): %s: %d files", node.ID(), len(idx))
}
go func() {
node.Index("default", idx)
@@ -628,7 +623,7 @@ func (m *Model) broadcastIndexLoop() {
}
// markDeletedLocals sets the deleted flag on files that have gone missing locally.
func (m *Model) markDeletedLocals(newLocal map[string]File) bool {
func (m *Model) markDeletedLocals(newLocal map[string]scanner.File) bool {
// For every file in the existing local table, check if they are also
// present in the new local table. If they are not, check that we already
// had the newest version available according to the global table and if so
@@ -658,7 +653,7 @@ func (m *Model) markDeletedLocals(newLocal map[string]File) bool {
return updated
}
func (m *Model) updateLocal(f File) {
func (m *Model) updateLocal(f scanner.File) {
var updated bool
m.lmut.Lock()
@@ -685,7 +680,7 @@ func (m *Model) updateLocal(f File) {
/*
XXX: Not done, needs elegant handling of availability
func (m *Model) recomputeGlobalFor(files []File) bool {
func (m *Model) recomputeGlobalFor(files []scanner.File) bool {
m.gmut.Lock()
defer m.gmut.Unlock()
@@ -702,7 +697,7 @@ func (m *Model) recomputeGlobalFor(files []File) bool {
*/
func (m *Model) recomputeGlobal() {
var newGlobal = make(map[string]File)
var newGlobal = make(map[string]scanner.File)
m.lmut.RLock()
for n, f := range m.local {
@@ -761,12 +756,12 @@ func (m *Model) recomputeGlobal() {
type addOrder struct {
n string
remote []Block
remote []scanner.Block
fm *fileMonitor
}
func (m *Model) recomputeNeedForGlobal() {
var toDelete []File
var toDelete []scanner.File
var toAdd []addOrder
m.gmut.RLock()
@@ -785,8 +780,8 @@ func (m *Model) recomputeNeedForGlobal() {
}
}
func (m *Model) recomputeNeedForFiles(files []File) {
var toDelete []File
func (m *Model) recomputeNeedForFiles(files []scanner.File) {
var toDelete []scanner.File
var toAdd []addOrder
m.gmut.RLock()
@@ -805,7 +800,7 @@ func (m *Model) recomputeNeedForFiles(files []File) {
}
}
func (m *Model) recomputeNeedForFile(gf File, toAdd []addOrder, toDelete []File) ([]addOrder, []File) {
func (m *Model) recomputeNeedForFile(gf scanner.File, toAdd []addOrder, toDelete []scanner.File) ([]addOrder, []scanner.File) {
m.lmut.RLock()
lf, ok := m.local[gf.Name]
m.lmut.RUnlock()
@@ -823,14 +818,14 @@ func (m *Model) recomputeNeedForFile(gf File, toAdd []addOrder, toDelete []File)
// Don't have the file, so don't need to delete it
return toAdd, toDelete
}
if m.trace["need"] {
debugf("NEED: lf:%v gf:%v", lf, gf)
if debugNeed {
dlog.Printf("need: lf:%v gf:%v", lf, gf)
}
if gf.Flags&protocol.FlagDeleted != 0 {
toDelete = append(toDelete, gf)
} else {
local, remote := BlockDiff(lf.Blocks, gf.Blocks)
local, remote := scanner.BlockDiff(lf.Blocks, gf.Blocks)
fm := fileMonitor{
name: gf.Name,
path: path.Clean(path.Join(m.dir, gf.Name)),
@@ -865,8 +860,8 @@ func (m *Model) WhoHas(name string) []string {
func (m *Model) deleteLoop() {
for file := range m.dq {
if m.trace["file"] {
debugln("FILE: Delete", file.Name)
if debugPull {
dlog.Println("delete", file.Name)
}
path := path.Clean(path.Join(m.dir, file.Name))
err := os.Remove(path)
@@ -878,18 +873,18 @@ func (m *Model) deleteLoop() {
}
}
func fileFromFileInfo(f protocol.FileInfo) File {
var blocks = make([]Block, len(f.Blocks))
func fileFromFileInfo(f protocol.FileInfo) scanner.File {
var blocks = make([]scanner.Block, len(f.Blocks))
var offset int64
for i, b := range f.Blocks {
blocks[i] = Block{
blocks[i] = scanner.Block{
Offset: offset,
Size: b.Size,
Hash: b.Hash,
}
offset += int64(b.Size)
}
return File{
return scanner.File{
Name: f.Name,
Size: offset,
Flags: f.Flags,
@@ -899,7 +894,7 @@ func fileFromFileInfo(f protocol.FileInfo) File {
}
}
func fileInfoFromFile(f File) protocol.FileInfo {
func fileInfoFromFile(f scanner.File) protocol.FileInfo {
var blocks = make([]protocol.BlockInfo, len(f.Blocks))
for i, b := range f.Blocks {
blocks[i] = protocol.BlockInfo{

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/calmh/syncthing/protocol"
"github.com/calmh/syncthing/scanner"
)
func TestNewModel(t *testing.T) {
@@ -27,27 +28,27 @@ func TestNewModel(t *testing.T) {
}
}
var testDataExpected = map[string]File{
"foo": File{
var testDataExpected = map[string]scanner.File{
"foo": scanner.File{
Name: "foo",
Flags: 0,
Modified: 0,
Size: 7,
Blocks: []Block{{Offset: 0x0, Size: 0x7, Hash: []uint8{0xae, 0xc0, 0x70, 0x64, 0x5f, 0xe5, 0x3e, 0xe3, 0xb3, 0x76, 0x30, 0x59, 0x37, 0x61, 0x34, 0xf0, 0x58, 0xcc, 0x33, 0x72, 0x47, 0xc9, 0x78, 0xad, 0xd1, 0x78, 0xb6, 0xcc, 0xdf, 0xb0, 0x1, 0x9f}}},
Blocks: []scanner.Block{{Offset: 0x0, Size: 0x7, Hash: []uint8{0xae, 0xc0, 0x70, 0x64, 0x5f, 0xe5, 0x3e, 0xe3, 0xb3, 0x76, 0x30, 0x59, 0x37, 0x61, 0x34, 0xf0, 0x58, 0xcc, 0x33, 0x72, 0x47, 0xc9, 0x78, 0xad, 0xd1, 0x78, 0xb6, 0xcc, 0xdf, 0xb0, 0x1, 0x9f}}},
},
"empty": File{
"empty": scanner.File{
Name: "empty",
Flags: 0,
Modified: 0,
Size: 0,
Blocks: []Block{{Offset: 0x0, Size: 0x0, Hash: []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}}},
Blocks: []scanner.Block{{Offset: 0x0, Size: 0x0, Hash: []uint8{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}}},
},
"bar": File{
"bar": scanner.File{
Name: "bar",
Flags: 0,
Modified: 0,
Size: 10,
Blocks: []Block{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}},
Blocks: []scanner.Block{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}},
},
}
@@ -63,7 +64,8 @@ func init() {
func TestUpdateLocal(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
if fs, _ := m.NeedFiles(); len(fs) > 0 {
@@ -105,7 +107,8 @@ func TestUpdateLocal(t *testing.T) {
func TestRemoteUpdateExisting(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
newFile := protocol.FileInfo{
@@ -122,7 +125,8 @@ func TestRemoteUpdateExisting(t *testing.T) {
func TestRemoteAddNew(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
newFile := protocol.FileInfo{
@@ -139,7 +143,8 @@ func TestRemoteAddNew(t *testing.T) {
func TestRemoteUpdateOld(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
oldTimeStamp := int64(1234)
@@ -157,7 +162,8 @@ func TestRemoteUpdateOld(t *testing.T) {
func TestRemoteIndexUpdate(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
foo := protocol.FileInfo{
@@ -190,7 +196,8 @@ func TestRemoteIndexUpdate(t *testing.T) {
func TestDelete(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
if l1, l2 := len(m.local), len(fs); l1 != l2 {
@@ -201,10 +208,10 @@ func TestDelete(t *testing.T) {
}
ot := time.Now().Unix()
newFile := File{
newFile := scanner.File{
Name: "a new file",
Modified: ot,
Blocks: []Block{{0, 100, []byte("some hash bytes")}},
Blocks: []scanner.Block{{0, 100, []byte("some hash bytes")}},
}
m.updateLocal(newFile)
@@ -292,7 +299,8 @@ func TestDelete(t *testing.T) {
func TestForgetNode(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
if l1, l2 := len(m.local), len(fs); l1 != l2 {
@@ -345,7 +353,8 @@ func TestForgetNode(t *testing.T) {
func TestRequest(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
bs, err := m.Request("some node", "default", "foo", 0, 6)
@@ -367,7 +376,8 @@ func TestRequest(t *testing.T) {
func TestIgnoreWithUnknownFlags(t *testing.T) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
valid := protocol.FileInfo{
@@ -410,7 +420,8 @@ func genFiles(n int) []protocol.FileInfo {
func BenchmarkIndex10000(b *testing.B) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
files := genFiles(10000)
@@ -422,7 +433,8 @@ func BenchmarkIndex10000(b *testing.B) {
func BenchmarkIndex00100(b *testing.B) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
files := genFiles(100)
@@ -434,7 +446,8 @@ func BenchmarkIndex00100(b *testing.B) {
func BenchmarkIndexUpdate10000f10000(b *testing.B) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
files := genFiles(10000)
m.Index("42", files)
@@ -447,7 +460,8 @@ func BenchmarkIndexUpdate10000f10000(b *testing.B) {
func BenchmarkIndexUpdate10000f00100(b *testing.B) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
files := genFiles(10000)
m.Index("42", files)
@@ -461,7 +475,8 @@ func BenchmarkIndexUpdate10000f00100(b *testing.B) {
func BenchmarkIndexUpdate10000f00001(b *testing.B) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
files := genFiles(10000)
m.Index("42", files)
@@ -506,7 +521,8 @@ func (FakeConnection) Statistics() protocol.Statistics {
func BenchmarkRequest(b *testing.B) {
m := NewModel("testdata", 1e6)
fs, _ := m.Walk(false)
w := scanner.Walker{Dir: "testdata", IgnoreFile: ".stignore", BlockSize: 128 * 1024}
fs, _ := w.Walk()
m.ReplaceLocal(fs)
const n = 1000

View File

@@ -1,6 +1,7 @@
package main
import (
"os"
"sync"
"time"
)
@@ -51,6 +52,11 @@ func (h *changeHistory) append(size int64, t time.Time) {
h.changes = append(h.changes, c)
}
func (s *suppressor) Suppress(name string, fi os.FileInfo) bool {
sup, _ := s.suppress(name, fi.Size(), time.Now())
return sup
}
func (s *suppressor) suppress(name string, size int64, t time.Time) (bool, bool) {
s.Lock()

28
cmd/syncthing/tempname.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
"path"
"path/filepath"
"runtime"
"strings"
)
type tempNamer struct {
prefix string
}
var defTempNamer = tempNamer{".syncthing"}
func (t tempNamer) IsTemporary(name string) bool {
if runtime.GOOS == "windows" {
name = filepath.ToSlash(name)
}
return strings.HasPrefix(path.Base(name), t.prefix)
}
func (t tempNamer) TempName(name string) string {
tdir := path.Dir(name)
tname := fmt.Sprintf("%s.%s", t.prefix, path.Base(name))
return path.Join(tdir, tname)
}

View File

@@ -22,18 +22,14 @@ func optionTable(w io.Writer, rows [][]string) {
tw.Flush()
}
func usageFor(fs *flag.FlagSet, usage string) func() {
func usageFor(fs *flag.FlagSet, usage string, extra string) func() {
return func() {
var b bytes.Buffer
b.WriteString("Usage:\n " + usage + "\n")
var options [][]string
fs.VisitAll(func(f *flag.Flag) {
var dash = "-"
if len(f.Name) > 1 {
dash = "--"
}
var opt = " " + dash + f.Name
var opt = " -" + f.Name
if f.DefValue != "false" {
opt += "=" + f.DefValue
@@ -48,5 +44,9 @@ func usageFor(fs *flag.FlagSet, usage string) func() {
}
fmt.Println(b.String())
if len(extra) > 0 {
fmt.Println(extra)
}
}
}

View File

@@ -1,238 +0,0 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/calmh/syncthing/protocol"
)
const BlockSize = 128 * 1024
type File struct {
Name string
Flags uint32
Modified int64
Version uint32
Size int64
Blocks []Block
}
func (f File) String() string {
return fmt.Sprintf("File{Name:%q, Flags:0x%x, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}",
f.Name, f.Flags, f.Modified, f.Version, f.Size, len(f.Blocks))
}
func (f File) Equals(o File) bool {
return f.Modified == o.Modified && f.Version == o.Version
}
func (f File) NewerThan(o File) bool {
return f.Modified > o.Modified || (f.Modified == o.Modified && f.Version > o.Version)
}
func isTempName(name string) bool {
return strings.HasPrefix(path.Base(name), ".syncthing.")
}
func tempName(name string, modified int64) string {
tdir := path.Dir(name)
tname := fmt.Sprintf(".syncthing.%s.%d", path.Base(name), modified)
return path.Join(tdir, tname)
}
func (m *Model) loadIgnoreFiles(ign map[string][]string) filepath.WalkFunc {
return func(p string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
rn, err := filepath.Rel(m.dir, p)
if err != nil {
return nil
}
if pn, sn := path.Split(rn); sn == ".stignore" {
pn := strings.Trim(pn, "/")
bs, _ := ioutil.ReadFile(p)
lines := bytes.Split(bs, []byte("\n"))
var patterns []string
for _, line := range lines {
if len(line) > 0 {
patterns = append(patterns, string(line))
}
}
ign[pn] = patterns
}
return nil
}
}
func (m *Model) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.WalkFunc {
return func(p string, info os.FileInfo, err error) error {
if err != nil {
if m.trace["file"] {
log.Printf("FILE: %q: %v", p, err)
}
return nil
}
if isTempName(p) {
return nil
}
rn, err := filepath.Rel(m.dir, p)
if err != nil {
return nil
}
if _, sn := path.Split(rn); sn == ".stignore" {
// We never sync the .stignore files
return nil
}
if ignoreFile(ign, rn) {
if m.trace["file"] {
log.Println("FILE: IGNORE:", rn)
}
return nil
}
if info.Mode()&os.ModeType == 0 {
modified := info.ModTime().Unix()
m.lmut.RLock()
lf, ok := m.local[rn]
m.lmut.RUnlock()
if ok && lf.Modified == modified {
if nf := uint32(info.Mode()); nf != lf.Flags {
lf.Flags = nf
lf.Version++
}
*res = append(*res, lf)
} else {
if cur, prev := m.sup.suppress(rn, info.Size(), time.Now()); cur {
if m.trace["file"] {
log.Printf("FILE: SUPPRESS: %q change bw over threshold", rn)
}
if !prev {
log.Printf("INFO: Changes to %q are being temporarily suppressed because it changes too frequently.", rn)
}
if ok {
lf.Flags = protocol.FlagInvalid
lf.Version++
*res = append(*res, lf)
}
return nil
} else if prev && !cur {
log.Printf("INFO: Changes to %q are no longer suppressed.", rn)
}
if m.trace["file"] {
log.Printf("FILE: Hash %q", p)
}
fd, err := os.Open(p)
if err != nil {
if m.trace["file"] {
log.Printf("FILE: %q: %v", p, err)
}
return nil
}
defer fd.Close()
blocks, err := Blocks(fd, BlockSize)
if err != nil {
if m.trace["file"] {
log.Printf("FILE: %q: %v", p, err)
}
return nil
}
f := File{
Name: rn,
Size: info.Size(),
Flags: uint32(info.Mode()),
Modified: modified,
Blocks: blocks,
}
*res = append(*res, f)
}
}
return nil
}
}
// Walk returns the list of files found in the local repository by scanning the
// file system. Files are blockwise hashed.
func (m *Model) Walk(followSymlinks bool) (files []File, ignore map[string][]string) {
ignore = make(map[string][]string)
hashFiles := m.walkAndHashFiles(&files, ignore)
filepath.Walk(m.dir, m.loadIgnoreFiles(ignore))
filepath.Walk(m.dir, hashFiles)
if followSymlinks {
d, err := os.Open(m.dir)
if err != nil {
return
}
defer d.Close()
fis, err := d.Readdir(-1)
if err != nil {
return
}
for _, info := range fis {
if info.Mode()&os.ModeSymlink != 0 {
dir := path.Join(m.dir, info.Name()) + "/"
filepath.Walk(dir, m.loadIgnoreFiles(ignore))
filepath.Walk(dir, hashFiles)
}
}
}
return
}
func (m *Model) cleanTempFile(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode()&os.ModeType == 0 && isTempName(path) {
if m.trace["file"] {
log.Printf("FILE: Remove %q", path)
}
os.Remove(path)
}
return nil
}
func (m *Model) cleanTempFiles() {
filepath.Walk(m.dir, m.cleanTempFile)
}
func ignoreFile(patterns map[string][]string, file string) bool {
first, last := path.Split(file)
for prefix, pats := range patterns {
if len(prefix) == 0 || prefix == first || strings.HasPrefix(first, prefix+"/") {
for _, pattern := range pats {
if match, _ := path.Match(pattern, last); match {
return true
}
}
}
}
return false
}

12
discover/debug.go Normal file
View File

@@ -0,0 +1,12 @@
package discover
import (
"log"
"os"
"strings"
)
var (
dlog = log.New(os.Stderr, "discover: ", log.Lmicroseconds|log.Lshortfile)
debug = strings.Contains(os.Getenv("STTRACE"), "discover")
)

View File

@@ -15,7 +15,6 @@ import (
const (
AnnouncementPort = 21025
Debug = false
)
type Discoverer struct {
@@ -111,8 +110,8 @@ func (d *Discoverer) sendAnnouncements() {
}
}
if len(srcAddr) == 0 {
if Debug {
log.Println("discover: debug: no source address found on interface", intf.Name)
if debug {
dlog.Println("no source address found on interface", intf.Name)
}
continue
}
@@ -131,8 +130,8 @@ func (d *Discoverer) sendAnnouncements() {
continue
}
if Debug {
log.Println("discover: debug: send announcement from", conn.LocalAddr(), "to", remote, "on", intf.Name)
if debug {
dlog.Println("send announcement from", conn.LocalAddr(), "to", remote, "on", intf.Name)
}
_, err = conn.WriteTo(buf, remote)
@@ -140,8 +139,8 @@ func (d *Discoverer) sendAnnouncements() {
// Some interfaces don't seem to support broadcast even though the flags claims they do, i.e. vmnet
conn.Close()
if Debug {
log.Println("discover/write: debug:", err)
if debug {
log.Println(err)
}
errCounter++
@@ -173,8 +172,8 @@ func (d *Discoverer) sendExtAnnouncements() {
var errCounter = 0
for errCounter < maxErrors {
if Debug {
log.Println("send announcement -> ", remote)
if debug {
dlog.Println("send announcement -> ", remote)
}
_, err = d.conn.WriteTo(buf, remote)
if err != nil {
@@ -200,8 +199,8 @@ func (d *Discoverer) recvAnnouncements() {
continue
}
if Debug {
log.Printf("read announcement:\n%s", hex.Dump(buf[:n]))
if debug {
dlog.Printf("read announcement:\n%s", hex.Dump(buf[:n]))
}
var pkt AnnounceV2
@@ -212,8 +211,8 @@ func (d *Discoverer) recvAnnouncements() {
continue
}
if Debug {
log.Printf("read announcement: %#v", pkt)
if debug {
dlog.Printf("parsed announcement: %#v", pkt)
}
errCounter = 0
@@ -229,8 +228,8 @@ func (d *Discoverer) recvAnnouncements() {
}
addrs = append(addrs, nodeAddr)
}
if Debug {
log.Printf("register: %#v", addrs)
if debug {
dlog.Printf("register: %#v", addrs)
}
d.registryLock.Lock()
_, seen := d.registry[pkt.NodeID]
@@ -287,8 +286,8 @@ func (d *Discoverer) externalLookup(node string) []string {
return nil
}
if Debug {
log.Printf("read external:\n%s", hex.Dump(buf[:n]))
if debug {
dlog.Printf("read external:\n%s", hex.Dump(buf[:n]))
}
var pkt AnnounceV2
@@ -298,8 +297,8 @@ func (d *Discoverer) externalLookup(node string) []string {
return nil
}
if Debug {
log.Printf("read external: %#v", pkt)
if debug {
dlog.Printf("parsed external: %#v", pkt)
}
var addrs []string

View File

@@ -32,6 +32,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
{id: 'FollowSymlinks', descr: 'Follow Symlinks', type: 'bool', restart: true},
{id: 'GlobalAnnEnabled', descr: 'Global Announce', type: 'bool', restart: true},
{id: 'LocalAnnEnabled', descr: 'Local Announce', type: 'bool', restart: true},
{id: 'StartBrowser', descr: 'Start Browser', type: 'bool'},
];
function modelGetSucceeded() {
@@ -317,7 +318,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
}
return errors;
};
$scope.clearErrors = function () {
$scope.seenError = $scope.errors[$scope.errors.length - 1].Time;
};

View File

@@ -1,4 +1,4 @@
package main
package scanner
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package main
package scanner
import (
"bytes"

12
scanner/debug.go Normal file
View File

@@ -0,0 +1,12 @@
package scanner
import (
"log"
"os"
"strings"
)
var (
dlog = log.New(os.Stderr, "scanner: ", log.Lmicroseconds|log.Lshortfile)
debug = strings.Contains(os.Getenv("STTRACE"), "scanner")
)

25
scanner/file.go Normal file
View File

@@ -0,0 +1,25 @@
package scanner
import "fmt"
type File struct {
Name string
Flags uint32
Modified int64
Version uint32
Size int64
Blocks []Block
}
func (f File) String() string {
return fmt.Sprintf("File{Name:%q, Flags:0x%x, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}",
f.Name, f.Flags, f.Modified, f.Version, f.Size, len(f.Blocks))
}
func (f File) Equals(o File) bool {
return f.Modified == o.Modified && f.Version == o.Version
}
func (f File) NewerThan(o File) bool {
return f.Modified > o.Modified || (f.Modified == o.Modified && f.Version > o.Version)
}

0
scanner/testdata/.foo/bar vendored Normal file
View File

2
scanner/testdata/.stignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.*
quux

1
scanner/testdata/bar vendored Normal file
View File

@@ -0,0 +1 @@
foobarbaz

1
scanner/testdata/baz/quux vendored Normal file
View File

@@ -0,0 +1 @@
baazquux

0
scanner/testdata/empty vendored Normal file
View File

1
scanner/testdata/foo vendored Normal file
View File

@@ -0,0 +1 @@
foobar

268
scanner/walk.go Normal file
View File

@@ -0,0 +1,268 @@
package scanner
import (
"bytes"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/calmh/syncthing/protocol"
)
type Walker struct {
// Dir is the base directory for the walk
Dir string
// If FollowSymlinks is true, symbolic links directly under Dir will be followed.
// Symbolic links at deeper levels are never followed regardless of this flag.
FollowSymlinks bool
// BlockSize controls the size of the block used when hashing.
BlockSize int
// If IgnoreFile is not empty, it is the name used for the file that holds ignore patterns.
IgnoreFile string
// If TempNamer is not nil, it is used to ignore tempory files when walking.
TempNamer TempNamer
// If Suppressor is not nil, it is queried for supression of modified files.
Suppressor Suppressor
previous map[string]File // file name -> last seen file state
suppressed map[string]bool // file name -> suppression status
}
type TempNamer interface {
// Temporary returns a temporary name for the filed referred to by path.
TempName(path string) string
// IsTemporary returns true if path refers to the name of temporary file.
IsTemporary(path string) bool
}
type Suppressor interface {
// Supress returns true if the update to the named file should be ignored.
Suppress(name string, fi os.FileInfo) bool
}
// Walk returns the list of files found in the local repository by scanning the
// file system. Files are blockwise hashed.
func (w *Walker) Walk() (files []File, ignore map[string][]string) {
w.lazyInit()
if debug {
dlog.Println("Walk", w.Dir, w.FollowSymlinks, w.BlockSize, w.IgnoreFile)
}
t0 := time.Now()
ignore = make(map[string][]string)
hashFiles := w.walkAndHashFiles(&files, ignore)
filepath.Walk(w.Dir, w.loadIgnoreFiles(w.Dir, ignore))
filepath.Walk(w.Dir, hashFiles)
if w.FollowSymlinks {
d, err := os.Open(w.Dir)
if err != nil {
return
}
defer d.Close()
fis, err := d.Readdir(-1)
if err != nil {
return
}
for _, info := range fis {
if info.Mode()&os.ModeSymlink != 0 {
dir := path.Join(w.Dir, info.Name()) + "/"
filepath.Walk(dir, w.loadIgnoreFiles(dir, ignore))
filepath.Walk(dir, hashFiles)
}
}
}
if debug {
t1 := time.Now()
d := t1.Sub(t0).Seconds()
dlog.Printf("Walk in %.02f ms, %.0f files/s", d*1000, float64(len(files))/d)
}
return
}
// CleanTempFiles removes all files that match the temporary filename pattern.
func (w *Walker) CleanTempFiles() {
filepath.Walk(w.Dir, w.cleanTempFile)
}
func (w *Walker) lazyInit() {
if w.previous == nil {
w.previous = make(map[string]File)
w.suppressed = make(map[string]bool)
}
}
func (w *Walker) loadIgnoreFiles(dir string, ign map[string][]string) filepath.WalkFunc {
return func(p string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
rn, err := filepath.Rel(dir, p)
if err != nil {
return nil
}
if pn, sn := path.Split(rn); sn == w.IgnoreFile {
pn := strings.Trim(pn, "/")
bs, _ := ioutil.ReadFile(p)
lines := bytes.Split(bs, []byte("\n"))
var patterns []string
for _, line := range lines {
if len(line) > 0 {
patterns = append(patterns, string(line))
}
}
ign[pn] = patterns
}
return nil
}
}
func (w *Walker) walkAndHashFiles(res *[]File, ign map[string][]string) filepath.WalkFunc {
return func(p string, info os.FileInfo, err error) error {
if err != nil {
if debug {
dlog.Println("error:", p, info, err)
}
return nil
}
rn, err := filepath.Rel(w.Dir, p)
if err != nil {
if debug {
dlog.Println("rel error:", p, err)
}
return nil
}
if w.TempNamer != nil && w.TempNamer.IsTemporary(rn) {
if debug {
dlog.Println("temporary:", rn)
}
return nil
}
if _, sn := path.Split(rn); sn == w.IgnoreFile {
if debug {
dlog.Println("ignorefile:", rn)
}
return nil
}
if rn != "." && w.ignoreFile(ign, rn) {
if debug {
dlog.Println("ignored:", rn)
}
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
if info.Mode()&os.ModeType == 0 {
modified := info.ModTime().Unix()
pf := w.previous[rn]
if pf.Modified == modified {
if nf := uint32(info.Mode()); nf != pf.Flags {
if debug {
dlog.Println("new flags:", rn)
}
pf.Flags = nf
pf.Version++
w.previous[rn] = pf
} else if debug {
dlog.Println("unchanged:", rn)
}
*res = append(*res, pf)
return nil
}
if w.Suppressor != nil && w.Suppressor.Suppress(rn, info) {
if debug {
dlog.Println("suppressed:", rn)
}
if !w.suppressed[rn] {
w.suppressed[rn] = true
log.Printf("INFO: Changes to %q are being temporarily suppressed because it changes too frequently.", p)
}
f := pf
f.Flags = protocol.FlagInvalid
f.Blocks = nil
*res = append(*res, f)
return nil
} else if w.suppressed[rn] {
log.Printf("INFO: Changes to %q are no longer suppressed.", p)
delete(w.suppressed, rn)
}
fd, err := os.Open(p)
if err != nil {
if debug {
dlog.Println("open:", p, err)
}
return nil
}
defer fd.Close()
t0 := time.Now()
blocks, err := Blocks(fd, w.BlockSize)
if err != nil {
if debug {
dlog.Println("hash error:", rn, err)
}
return nil
}
if debug {
t1 := time.Now()
dlog.Println("hashed:", rn, ";", len(blocks), "blocks;", info.Size(), "bytes;", int(float64(info.Size())/1024/t1.Sub(t0).Seconds()), "KB/s")
}
f := File{
Name: rn,
Size: info.Size(),
Flags: uint32(info.Mode()),
Modified: modified,
Blocks: blocks,
}
w.previous[rn] = f
*res = append(*res, f)
}
return nil
}
}
func (w *Walker) cleanTempFile(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.Mode()&os.ModeType == 0 && w.TempNamer.IsTemporary(path) {
os.Remove(path)
}
return nil
}
func (w *Walker) ignoreFile(patterns map[string][]string, file string) bool {
first, last := path.Split(file)
for prefix, pats := range patterns {
if len(prefix) == 0 || prefix == first || strings.HasPrefix(first, prefix+"/") {
for _, pattern := range pats {
if match, _ := path.Match(pattern, last); match {
return true
}
}
}
}
return false
}

View File

@@ -1,4 +1,4 @@
package main
package scanner
import (
"fmt"
@@ -22,8 +22,12 @@ var correctIgnores = map[string][]string{
}
func TestWalk(t *testing.T) {
m := NewModel("testdata", 1e6)
files, ignores := m.Walk(false)
w := Walker{
Dir: "testdata",
BlockSize: 128 * 1024,
IgnoreFile: ".stignore",
}
files, ignores := w.Walk()
if l1, l2 := len(files), len(testdata); l1 != l2 {
t.Fatalf("Incorrect number of walked files %d != %d", l1, l2)
@@ -75,8 +79,9 @@ func TestIgnore(t *testing.T) {
{"foo/bazz/quux", false},
}
w := Walker{}
for i, tc := range tests {
if r := ignoreFile(patterns, tc.f); r != tc.r {
if r := w.ignoreFile(patterns, tc.f); r != tc.r {
t.Errorf("Incorrect ignoreFile() #%d; E: %v, A: %v", i, tc.r, r)
}
}