mirror of
				https://github.com/scottlamb/moonfire-nvr.git
				synced 2025-10-29 15:55:01 -04:00 
			
		
		
		
	schema comparison in new upgrade tests, "moonfire-nvr check"
The .sql files here are copied from earlier revisions: v0.sql fee4141:src/schema.sql v1.sql 0d69f4f:src/schema.sql v3.sql 422cd2a:db/schema.sql
This commit is contained in:
		
							parent
							
								
									f1112031c2
								
							
						
					
					
						commit
						d7a918d397
					
				
							
								
								
									
										238
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										238
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -13,6 +13,19 @@ dependencies = [ | |||||||
|  "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |  "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ansi_term" | ||||||
|  | version = "0.9.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ansi_term" | ||||||
|  | version = "0.11.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "arc-swap" | name = "arc-swap" | ||||||
| version = "0.3.11" | version = "0.3.11" | ||||||
| @ -40,6 +53,16 @@ dependencies = [ | |||||||
|  "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", |  "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "atty" | ||||||
|  | version = "0.2.11" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "autocfg" | name = "autocfg" | ||||||
| version = "0.1.4" | version = "0.1.4" | ||||||
| @ -116,6 +139,17 @@ dependencies = [ | |||||||
|  "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |  "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "bstr" | ||||||
|  | version = "0.2.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "build_const" | name = "build_const" | ||||||
| version = "0.2.1" | version = "0.2.1" | ||||||
| @ -161,6 +195,20 @@ dependencies = [ | |||||||
|  "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", |  "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "clap" | ||||||
|  | version = "2.33.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "cloudabi" | name = "cloudabi" | ||||||
| version = "0.0.3" | version = "0.0.3" | ||||||
| @ -305,6 +353,26 @@ dependencies = [ | |||||||
|  "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", |  "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "csv" | ||||||
|  | version = "1.1.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "bstr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "csv-core" | ||||||
|  | version = "0.1.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "cursive" | name = "cursive" | ||||||
| version = "0.12.0" | version = "0.12.0" | ||||||
| @ -376,6 +444,16 @@ dependencies = [ | |||||||
|  "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", |  "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "dirs" | ||||||
|  | version = "1.0.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "docopt" | name = "docopt" | ||||||
| version = "1.1.0" | version = "1.1.0" | ||||||
| @ -397,6 +475,11 @@ name = "either" | |||||||
| version = "1.5.2" | version = "1.5.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "encode_unicode" | ||||||
|  | version = "0.3.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "encoding_rs" | name = "encoding_rs" | ||||||
| version = "0.8.17" | version = "0.8.17" | ||||||
| @ -601,6 +684,14 @@ name = "hashbrown" | |||||||
| version = "0.3.1" | version = "0.3.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "heck" | ||||||
|  | version = "0.3.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "hmac" | name = "hmac" | ||||||
| version = "0.7.0" | version = "0.7.0" | ||||||
| @ -842,6 +933,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||||
| name = "memchr" | name = "memchr" | ||||||
| version = "2.2.0" | version = "2.2.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "memmap" | name = "memmap" | ||||||
| @ -973,6 +1067,7 @@ dependencies = [ | |||||||
|  "odds 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", |  "odds 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", |  "openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", |  "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "prettydiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  "protobuf 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)", |  "protobuf 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)", | ||||||
|  "protobuf-codegen-pure 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)", |  "protobuf-codegen-pure 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)", | ||||||
|  "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |  "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| @ -1155,6 +1250,11 @@ dependencies = [ | |||||||
|  "libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)", |  "libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "numtoa" | ||||||
|  | version = "0.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "odds" | name = "odds" | ||||||
| version = "0.3.1" | version = "0.3.1" | ||||||
| @ -1308,6 +1408,29 @@ name = "pkg-config" | |||||||
| version = "0.3.14" | version = "0.3.14" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "prettydiff" | ||||||
|  | version = "0.3.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "prettytable-rs" | ||||||
|  | version = "0.8.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "proc-macro2" | name = "proc-macro2" | ||||||
| version = "0.4.30" | version = "0.4.30" | ||||||
| @ -1518,6 +1641,25 @@ name = "redox_syscall" | |||||||
| version = "0.1.54" | version = "0.1.54" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "redox_termios" | ||||||
|  | version = "0.1.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "redox_users" | ||||||
|  | version = "0.3.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "reffers" | name = "reffers" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
| @ -1538,6 +1680,14 @@ dependencies = [ | |||||||
|  "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", |  "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "regex-automata" | ||||||
|  | version = "0.1.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "regex-syntax" | name = "regex-syntax" | ||||||
| version = "0.6.6" | version = "0.6.6" | ||||||
| @ -1658,6 +1808,11 @@ name = "ryu" | |||||||
| version = "0.2.8" | version = "0.2.8" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ryu" | ||||||
|  | version = "1.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "safemem" | name = "safemem" | ||||||
| version = "0.3.0" | version = "0.3.0" | ||||||
| @ -1872,11 +2027,36 @@ name = "strsim" | |||||||
| version = "0.7.0" | version = "0.7.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "strsim" | ||||||
|  | version = "0.8.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "strsim" | name = "strsim" | ||||||
| version = "0.9.2" | version = "0.9.2" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "structopt" | ||||||
|  | version = "0.2.18" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "structopt-derive" | ||||||
|  | version = "0.2.18" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "subtle" | name = "subtle" | ||||||
| version = "1.0.0" | version = "1.0.0" | ||||||
| @ -1935,6 +2115,16 @@ dependencies = [ | |||||||
|  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", |  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "term" | ||||||
|  | version = "0.5.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "term_size" | name = "term_size" | ||||||
| version = "0.3.1" | version = "0.3.1" | ||||||
| @ -1945,6 +2135,25 @@ dependencies = [ | |||||||
|  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", |  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "termion" | ||||||
|  | version = "1.5.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc 0.2.57 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "textwrap" | ||||||
|  | version = "0.11.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "thread_local" | name = "thread_local" | ||||||
| version = "0.3.6" | version = "0.3.6" | ||||||
| @ -2292,6 +2501,11 @@ name = "vcpkg" | |||||||
| version = "0.2.6" | version = "0.2.6" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "vec_map" | ||||||
|  | version = "0.8.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "version_check" | name = "version_check" | ||||||
| version = "0.1.5" | version = "0.1.5" | ||||||
| @ -2361,10 +2575,13 @@ dependencies = [ | |||||||
| [metadata] | [metadata] | ||||||
| "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" | "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c" | ||||||
| "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" | "checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" | ||||||
|  | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" | ||||||
|  | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" | ||||||
| "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" | "checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" | ||||||
| "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" | "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" | ||||||
| "checksum array-macro 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4ff37a25fb442a1fecfd399be0dde685558bca30fb998420532889a36852d2" | "checksum array-macro 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4ff37a25fb442a1fecfd399be0dde685558bca30fb998420532889a36852d2" | ||||||
| "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" | "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" | ||||||
|  | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" | ||||||
| "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" | "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" | ||||||
| "checksum backtrace 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)" = "1a13fc43f04daf08ab4f71e3d27e1fc27fc437d3e95ac0063a796d92fb40f39b" | "checksum backtrace 0.3.26 (registry+https://github.com/rust-lang/crates.io-index)" = "1a13fc43f04daf08ab4f71e3d27e1fc27fc437d3e95ac0063a796d92fb40f39b" | ||||||
| "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" | "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" | ||||||
| @ -2374,6 +2591,7 @@ dependencies = [ | |||||||
| "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" | "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" | ||||||
| "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" | "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" | ||||||
| "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" | "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" | ||||||
|  | "checksum bstr 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc0572e02f76cb335f309b19e0a0d585b4f62788f7d26de2a13a836a637385f" | ||||||
| "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" | "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" | ||||||
| "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" | "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" | ||||||
| "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" | "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" | ||||||
| @ -2381,6 +2599,7 @@ dependencies = [ | |||||||
| "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" | "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" | ||||||
| "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" | "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" | ||||||
| "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" | "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" | ||||||
|  | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" | ||||||
| "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" | "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" | ||||||
| "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" | "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" | ||||||
| "checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" | "checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" | ||||||
| @ -2397,15 +2616,19 @@ dependencies = [ | |||||||
| "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" | "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" | ||||||
| "checksum cstr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "19f7a08ed4ecd7e077d4cee63937473e6f7cf57b702a9114ef41751b2cbc0f60" | "checksum cstr 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "19f7a08ed4ecd7e077d4cee63937473e6f7cf57b702a9114ef41751b2cbc0f60" | ||||||
| "checksum cstr-macros 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0f12dd847ec773fc98d75edba5394cb87d0f35e7ee548a4c81849ca6374b3d48" | "checksum cstr-macros 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0f12dd847ec773fc98d75edba5394cb87d0f35e7ee548a4c81849ca6374b3d48" | ||||||
|  | "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" | ||||||
|  | "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" | ||||||
| "checksum cursive 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b7ecc7282b5361471b607c26f44148205607e26d48a2fc65bd16e7619b1ebb78" | "checksum cursive 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b7ecc7282b5361471b607c26f44148205607e26d48a2fc65bd16e7619b1ebb78" | ||||||
| "checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6" | "checksum darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfbcb0c5961907597a7d1148e3af036268f2b773886b8bb3eeb1e1281d3d3d6" | ||||||
| "checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c" | "checksum darling_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6afc018370c3bff3eb51f89256a6bdb18b4fdcda72d577982a14954a7a0b402c" | ||||||
| "checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1" | "checksum darling_macro 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d8dac1c6f1d29a41c4712b4400f878cb4fcc4c7628f298dd75038e024998d1" | ||||||
| "checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" | "checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" | ||||||
| "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" | "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" | ||||||
|  | "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" | ||||||
| "checksum docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" | "checksum docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" | ||||||
| "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" | "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" | ||||||
| "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" | "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" | ||||||
|  | "checksum encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd" | ||||||
| "checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed" | "checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed" | ||||||
| "checksum enum-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ccd9b2d5e0eb5c2ff851791e2af90ab4531b1168cfc239d1c0bf467e60ba3c89" | "checksum enum-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ccd9b2d5e0eb5c2ff851791e2af90ab4531b1168cfc239d1c0bf467e60ba3c89" | ||||||
| "checksum enum-map-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "153f6e8a8b2868e2fedf921b165f30229edcccb74d6a9bb1ccf0480ef61cd07e" | "checksum enum-map-derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "153f6e8a8b2868e2fedf921b165f30229edcccb74d6a9bb1ccf0480ef61cd07e" | ||||||
| @ -2432,6 +2655,7 @@ dependencies = [ | |||||||
| "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" | "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" | ||||||
| "checksum h2 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "876d91114d78abbde2e1910e3b2d9d0fd1d89b769e20816dfb68d77992cf4158" | "checksum h2 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "876d91114d78abbde2e1910e3b2d9d0fd1d89b769e20816dfb68d77992cf4158" | ||||||
| "checksum hashbrown 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "29fba9abe4742d586dfd0c06ae4f7e73a1c2d86b856933509b269d82cdf06e18" | "checksum hashbrown 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "29fba9abe4742d586dfd0c06ae4f7e73a1c2d86b856933509b269d82cdf06e18" | ||||||
|  | "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" | ||||||
| "checksum hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f127a908633569f208325f86f71255d3363c79721d7f9fe31cd5569908819771" | "checksum hmac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f127a908633569f208325f86f71255d3363c79721d7f9fe31cd5569908819771" | ||||||
| "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" | "checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" | ||||||
| "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" | "checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" | ||||||
| @ -2481,6 +2705,7 @@ dependencies = [ | |||||||
| "checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" | "checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" | ||||||
| "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" | "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" | ||||||
| "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" | "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" | ||||||
|  | "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" | ||||||
| "checksum odds 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9a18d7081eb052145753e982d7b8de495f15f74636d0d963f09116581eab665" | "checksum odds 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a9a18d7081eb052145753e982d7b8de495f15f74636d0d963f09116581eab665" | ||||||
| "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" | "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" | ||||||
| "checksum openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)" = "97c140cbb82f3b3468193dd14c1b88def39f341f68257f8a7fe8ed9ed3f628a5" | "checksum openssl 0.10.23 (registry+https://github.com/rust-lang/crates.io-index)" = "97c140cbb82f3b3468193dd14c1b88def39f341f68257f8a7fe8ed9ed3f628a5" | ||||||
| @ -2498,6 +2723,8 @@ dependencies = [ | |||||||
| "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" | "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" | ||||||
| "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" | "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" | ||||||
| "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" | "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" | ||||||
|  | "checksum prettydiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5240be0c9ea1bc7887819a36264cb9475eb71c58749808e5b989c8c1fdc67acf" | ||||||
|  | "checksum prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" | ||||||
| "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" | "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" | ||||||
| "checksum procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9a1574a51c3fd37b26d2c0032b649d08a7d51d4cca9c41bbc5bf7118fa4509d0" | "checksum procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9a1574a51c3fd37b26d2c0032b649d08a7d51d4cca9c41bbc5bf7118fa4509d0" | ||||||
| "checksum protobuf 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)" = "<none>" | "checksum protobuf 3.0.0-pre (git+https://github.com/stepancheg/rust-protobuf)" = "<none>" | ||||||
| @ -2522,8 +2749,11 @@ dependencies = [ | |||||||
| "checksum rawslice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22b23b9f57ea250c6db4b21e2897b43ff08209217ca8260469fae6c0f9ad7e25" | "checksum rawslice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22b23b9f57ea250c6db4b21e2897b43ff08209217ca8260469fae6c0f9ad7e25" | ||||||
| "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" | ||||||
| "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" | "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" | ||||||
|  | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" | ||||||
|  | "checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" | ||||||
| "checksum reffers 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c3b765c544398b56cb85f1c77c6e1d963930b8b5c9678b2cc93195795a6fc32" | "checksum reffers 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c3b765c544398b56cb85f1c77c6e1d963930b8b5c9678b2cc93195795a6fc32" | ||||||
| "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" | "checksum regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8f0a0bcab2fd7d1d7c54fa9eae6f43eddeb9ce2e7352f8518a814a4f65d60c58" | ||||||
|  | "checksum regex-automata 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ed09217220c272b29ef237a974ad58515bde75f194e3ffa7e6d0bf0f3b01f86" | ||||||
| "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" | "checksum regex-syntax 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dcfd8681eebe297b81d98498869d4aae052137651ad7b96822f09ceb690d0a96" | ||||||
| "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" | "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" | ||||||
| "checksum reqwest 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e57803405f8ea0eb041c1567dac36127e0c8caa1251c843cb03d43fd767b3d50" | "checksum reqwest 0.9.17 (registry+https://github.com/rust-lang/crates.io-index)" = "e57803405f8ea0eb041c1567dac36127e0c8caa1251c843cb03d43fd767b3d50" | ||||||
| @ -2535,6 +2765,7 @@ dependencies = [ | |||||||
| "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" | ||||||
| "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" | "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" | ||||||
| "checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" | "checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" | ||||||
|  | "checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" | ||||||
| "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" | "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" | ||||||
| "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" | "checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" | ||||||
| "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" | "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" | ||||||
| @ -2562,14 +2793,20 @@ dependencies = [ | |||||||
| "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" | ||||||
| "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" | "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" | ||||||
| "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" | ||||||
|  | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" | ||||||
| "checksum strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6" | "checksum strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6" | ||||||
|  | "checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7" | ||||||
|  | "checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107" | ||||||
| "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" | "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" | ||||||
| "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" | ||||||
| "checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" | "checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" | ||||||
| "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" | "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" | ||||||
| "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" | "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" | ||||||
| "checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef" | "checksum tempfile 3.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7dc4738f2e68ed2855de5ac9cdbe05c9216773ecde4739b2f095002ab03a13ef" | ||||||
|  | "checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" | ||||||
| "checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327" | "checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327" | ||||||
|  | "checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330" | ||||||
|  | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" | ||||||
| "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" | "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" | ||||||
| "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" | ||||||
| "checksum tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2ffcf4bcfc641413fa0f1427bf8f91dfc78f56a6559cbf50e04837ae442a87" | "checksum tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "ec2ffcf4bcfc641413fa0f1427bf8f91dfc78f56a6559cbf50e04837ae442a87" | ||||||
| @ -2607,6 +2844,7 @@ dependencies = [ | |||||||
| "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" | "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" | ||||||
| "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" | "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" | ||||||
| "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" | "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" | ||||||
|  | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" | ||||||
| "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" | ||||||
| "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" | "checksum want 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "797464475f30ddb8830cc529aaaae648d581f99e2036a928877dfde027ddf6b3" | ||||||
| "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" | ||||||
|  | |||||||
| @ -27,6 +27,7 @@ mylog = { git = "https://github.com/scottlamb/mylog" } | |||||||
| odds = { version = "0.3.1", features = ["std-vec"] } | odds = { version = "0.3.1", features = ["std-vec"] } | ||||||
| openssl = "0.10" | openssl = "0.10" | ||||||
| parking_lot = { version = "0.8", features = [] } | parking_lot = { version = "0.8", features = [] } | ||||||
|  | prettydiff = "0.3.1" | ||||||
| protobuf = { git = "https://github.com/stepancheg/rust-protobuf" } | protobuf = { git = "https://github.com/stepancheg/rust-protobuf" } | ||||||
| regex = "1.0" | regex = "1.0" | ||||||
| rusqlite = "0.18" | rusqlite = "0.18" | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								db/check.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								db/check.rs
									
									
									
									
									
								
							| @ -30,6 +30,7 @@ | |||||||
| 
 | 
 | ||||||
| //! Subcommand to check the database and sample file dir for errors.
 | //! Subcommand to check the database and sample file dir for errors.
 | ||||||
| 
 | 
 | ||||||
|  | use crate::compare; | ||||||
| use crate::db::{self, CompositeId, FromSqlUuid}; | use crate::db::{self, CompositeId, FromSqlUuid}; | ||||||
| use crate::dir; | use crate::dir; | ||||||
| use crate::raw; | use crate::raw; | ||||||
| @ -48,6 +49,15 @@ pub struct Options { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> { | pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> { | ||||||
|  |     // Compare schemas.
 | ||||||
|  |     { | ||||||
|  |         let mut expected = rusqlite::Connection::open_in_memory()?; | ||||||
|  |         db::init(&mut expected)?; | ||||||
|  |         if let Some(diffs) = compare::get_diffs("actual", conn, "expected", &expected)? { | ||||||
|  |             println!("{}", &diffs); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     let db_uuid = raw::get_db_uuid(&conn)?; |     let db_uuid = raw::get_db_uuid(&conn)?; | ||||||
| 
 | 
 | ||||||
|     // Scan directories.
 |     // Scan directories.
 | ||||||
|  | |||||||
							
								
								
									
										168
									
								
								db/compare.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								db/compare.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,168 @@ | |||||||
|  | // This file is part of Moonfire NVR, a security camera network video recorder.
 | ||||||
|  | // Copyright (C) 2019 Scott Lamb <slamb@slamb.org>
 | ||||||
|  | //
 | ||||||
|  | // This program is free software: you can redistribute it and/or modify
 | ||||||
|  | // it under the terms of the GNU General Public License as published by
 | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or
 | ||||||
|  | // (at your option) any later version.
 | ||||||
|  | //
 | ||||||
|  | // In addition, as a special exception, the copyright holders give
 | ||||||
|  | // permission to link the code of portions of this program with the
 | ||||||
|  | // OpenSSL library under certain conditions as described in each
 | ||||||
|  | // individual source file, and distribute linked combinations including
 | ||||||
|  | // the two.
 | ||||||
|  | //
 | ||||||
|  | // You must obey the GNU General Public License in all respects for all
 | ||||||
|  | // of the code used other than OpenSSL. If you modify file(s) with this
 | ||||||
|  | // exception, you may extend this exception to your version of the
 | ||||||
|  | // file(s), but you are not obligated to do so. If you do not wish to do
 | ||||||
|  | // so, delete this exception statement from your version. If you delete
 | ||||||
|  | // this exception statement from all source files in the program, then
 | ||||||
|  | // also delete it here.
 | ||||||
|  | //
 | ||||||
|  | // This program is distributed in the hope that it will be useful,
 | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||||
|  | // GNU General Public License for more details.
 | ||||||
|  | //
 | ||||||
|  | // You should have received a copy of the GNU General Public License
 | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||||
|  | 
 | ||||||
|  | use failure::Error; | ||||||
|  | use prettydiff::diff_slice; | ||||||
|  | use rusqlite::params; | ||||||
|  | use std::fmt::Write; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, PartialEq)] | ||||||
|  | struct Column { | ||||||
|  |     cid: u32, | ||||||
|  |     name: String, | ||||||
|  |     type_: String, | ||||||
|  |     notnull: bool, | ||||||
|  |     dflt_value: rusqlite::types::Value, | ||||||
|  |     pk: u32, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for Column { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         write!(f, "{:?}", self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Eq, PartialEq)] | ||||||
|  | struct Index { | ||||||
|  |     seq: u32, | ||||||
|  |     name: String, | ||||||
|  |     unique: bool, | ||||||
|  |     origin: String, | ||||||
|  |     partial: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for Index { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         write!(f, "{:?}", self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Eq, PartialEq)] | ||||||
|  | struct IndexColumn { | ||||||
|  |     seqno: u32, | ||||||
|  |     cid: u32, | ||||||
|  |     name: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for IndexColumn { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         write!(f, "{:?}", self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Returns a sorted vec of table names in the given connection.
 | ||||||
|  | fn get_tables(c: &rusqlite::Connection) -> Result<Vec<String>, rusqlite::Error> { | ||||||
|  |     c.prepare("select name from sqlite_master where type = 'table' order by name")? | ||||||
|  |      .query_map(params![], |r| r.get(0))? | ||||||
|  |      .collect() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Returns a vec of columns in the given table.
 | ||||||
|  | fn get_table_columns(c: &rusqlite::Connection, table: &str) | ||||||
|  |                      -> Result<Vec<Column>, rusqlite::Error> { | ||||||
|  |     c.prepare_cached("select * from pragma_table_info(?)")? | ||||||
|  |      .query_map(params![table], |r| Ok(Column { | ||||||
|  |          cid: r.get(0)?, | ||||||
|  |          name: r.get(1)?, | ||||||
|  |          type_: r.get(2)?, | ||||||
|  |          notnull: r.get(3)?, | ||||||
|  |          dflt_value: r.get(4)?, | ||||||
|  |          pk: r.get(5)?, | ||||||
|  |      }))? | ||||||
|  |      .collect() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Returns a vec of indices associated with the given table.
 | ||||||
|  | fn get_indices(c: &rusqlite::Connection, table: &str) -> Result<Vec<Index>, rusqlite::Error> { | ||||||
|  |     c.prepare_cached("select * from pragma_index_list(?)")? | ||||||
|  |      .query_map(params![table], |r| Ok(Index { | ||||||
|  |          seq: r.get(0)?, | ||||||
|  |          name: r.get(1)?, | ||||||
|  |          unique: r.get(2)?, | ||||||
|  |          origin: r.get(3)?, | ||||||
|  |          partial: r.get(4)?, | ||||||
|  |      }))? | ||||||
|  |      .collect() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Returns a vec of all the columns in the given index.
 | ||||||
|  | fn get_index_columns(c: &rusqlite::Connection, index: &str) | ||||||
|  |                      -> Result<Vec<IndexColumn>, rusqlite::Error> { | ||||||
|  |     c.prepare_cached("select * from pragma_index_info(?)")? | ||||||
|  |      .query_map(params![index], |r| Ok(IndexColumn { | ||||||
|  |          seqno: r.get(0)?, | ||||||
|  |          cid: r.get(1)?, | ||||||
|  |          name: r.get(2)?, | ||||||
|  |      }))? | ||||||
|  |      .collect() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn get_diffs(n1: &str, c1: &rusqlite::Connection, n2: &str, c2: &rusqlite::Connection) | ||||||
|  |            -> Result<Option<String>, Error> { | ||||||
|  |     let mut diffs = String::new(); | ||||||
|  | 
 | ||||||
|  |     // Compare table list.
 | ||||||
|  |     let tables1 = get_tables(c1)?; | ||||||
|  |     let tables2 = get_tables(c2)?; | ||||||
|  |     if tables1 != tables2 { | ||||||
|  |         write!(&mut diffs, "table list mismatch, {} vs {}:\n{}", | ||||||
|  |                n1, n2, diff_slice(&tables1, &tables2))?; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Compare columns and indices for each table.
 | ||||||
|  |     for t in &tables1 { | ||||||
|  |         let columns1 = get_table_columns(c1, &t)?; | ||||||
|  |         let columns2 = get_table_columns(c2, &t)?; | ||||||
|  |         if columns1 != columns2 { | ||||||
|  |             write!(&mut diffs, "table {:?} column, {} vs {}:\n{}", | ||||||
|  |                    t, n1, n2, diff_slice(&columns1, &columns2))?; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mut indices1 = get_indices(c1, &t)?; | ||||||
|  |         let mut indices2 = get_indices(c2, &t)?; | ||||||
|  |         indices1.sort_by(|a, b| a.name.cmp(&b.name)); | ||||||
|  |         indices2.sort_by(|a, b| a.name.cmp(&b.name)); | ||||||
|  |         if indices1 != indices2 { | ||||||
|  |             write!(&mut diffs, "table {:?} indices, {} vs {}:\n{}", | ||||||
|  |                    t, n1, n2, diff_slice(&indices1, &indices2))?; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for i in &indices1 { | ||||||
|  |             let ic1 = get_index_columns(c1, &i.name)?; | ||||||
|  |             let ic2 = get_index_columns(c2, &i.name)?; | ||||||
|  |             if ic1 != ic2 { | ||||||
|  |                 write!(&mut diffs, "table {:?} index {:?} columns {} vs {}:\n{}", | ||||||
|  |                        t, i, n1, n2, diff_slice(&ic1, &ic2))?; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Ok(if diffs.is_empty() { None } else { Some(diffs) }) | ||||||
|  | } | ||||||
| @ -33,6 +33,7 @@ | |||||||
| pub mod auth; | pub mod auth; | ||||||
| pub mod check; | pub mod check; | ||||||
| mod coding; | mod coding; | ||||||
|  | mod compare; | ||||||
| pub mod db; | pub mod db; | ||||||
| pub mod dir; | pub mod dir; | ||||||
| mod raw; | mod raw; | ||||||
|  | |||||||
| @ -35,7 +35,7 @@ | |||||||
| use crate::db; | use crate::db; | ||||||
| use failure::{Error, bail}; | use failure::{Error, bail}; | ||||||
| use log::info; | use log::info; | ||||||
| use rusqlite::types::ToSql; | use rusqlite::params; | ||||||
| 
 | 
 | ||||||
| mod v0_to_v1; | mod v0_to_v1; | ||||||
| mod v1_to_v2; | mod v1_to_v2; | ||||||
| @ -55,13 +55,13 @@ pub struct Args<'a> { | |||||||
| 
 | 
 | ||||||
| fn set_journal_mode(conn: &rusqlite::Connection, requested: &str) -> Result<(), Error> { | fn set_journal_mode(conn: &rusqlite::Connection, requested: &str) -> Result<(), Error> { | ||||||
|     assert!(!requested.contains(';'));  // quick check for accidental sql injection.
 |     assert!(!requested.contains(';'));  // quick check for accidental sql injection.
 | ||||||
|     let actual = conn.query_row(&format!("pragma journal_mode = {}", requested), &[] as &[&dyn ToSql], |     let actual = conn.query_row(&format!("pragma journal_mode = {}", requested), params![], | ||||||
|                                 |row| row.get::<_, String>(0))?; |                                 |row| row.get::<_, String>(0))?; | ||||||
|     info!("...database now in journal_mode {} (requested {}).", actual, requested); |     info!("...database now in journal_mode {} (requested {}).", actual, requested); | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> { | fn upgrade(args: &Args, target_ver: i32, conn: &mut rusqlite::Connection) -> Result<(), Error> { | ||||||
|     let upgraders = [ |     let upgraders = [ | ||||||
|         v0_to_v1::run, |         v0_to_v1::run, | ||||||
|         v1_to_v2::run, |         v1_to_v2::run, | ||||||
| @ -73,7 +73,7 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> { | |||||||
|     { |     { | ||||||
|         assert_eq!(upgraders.len(), db::EXPECTED_VERSION as usize); |         assert_eq!(upgraders.len(), db::EXPECTED_VERSION as usize); | ||||||
|         let old_ver = |         let old_ver = | ||||||
|             conn.query_row("select max(id) from version", &[] as &[&dyn ToSql], |             conn.query_row("select max(id) from version", params![], | ||||||
|                            |row| row.get(0))?; |                            |row| row.get(0))?; | ||||||
|         if old_ver > db::EXPECTED_VERSION { |         if old_ver > db::EXPECTED_VERSION { | ||||||
|             bail!("Database is at version {}, later than expected {}", |             bail!("Database is at version {}, later than expected {}", | ||||||
| @ -81,29 +81,35 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> { | |||||||
|         } else if old_ver < 0 { |         } else if old_ver < 0 { | ||||||
|             bail!("Database is at negative version {}!", old_ver); |             bail!("Database is at negative version {}!", old_ver); | ||||||
|         } |         } | ||||||
|         info!("Upgrading database from version {} to version {}...", old_ver, db::EXPECTED_VERSION); |         info!("Upgrading database from version {} to version {}...", old_ver, target_ver); | ||||||
|         set_journal_mode(&conn, args.flag_preset_journal).unwrap(); |         set_journal_mode(&conn, args.flag_preset_journal).unwrap(); | ||||||
|         for ver in old_ver .. db::EXPECTED_VERSION { |         for ver in old_ver .. target_ver { | ||||||
|             info!("...from version {} to version {}", ver, ver + 1); |             info!("...from version {} to version {}", ver, ver + 1); | ||||||
|             let tx = conn.transaction()?; |             let tx = conn.transaction()?; | ||||||
|             upgraders[ver as usize](&args, &tx)?; |             upgraders[ver as usize](&args, &tx)?; | ||||||
|             tx.execute(r#" |             tx.execute(r#" | ||||||
|                 insert into version (id, unix_time, notes) |                 insert into version (id, unix_time, notes) | ||||||
|                              values (?, cast(strftime('%s', 'now') as int32), ?) |                              values (?, cast(strftime('%s', 'now') as int32), ?) | ||||||
|             "#, &[&(ver + 1) as &dyn ToSql, &UPGRADE_NOTES])?;
 |             "#, params![&(ver + 1), &UPGRADE_NOTES])?;
 | ||||||
|             tx.commit()?; |             tx.commit()?; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> { | ||||||
|     // Enforce foreign keys. This is on by default with --features=bundled (as rusqlite
 |     // Enforce foreign keys. This is on by default with --features=bundled (as rusqlite
 | ||||||
|     // compiles the SQLite3 amalgamation with -DSQLITE_DEFAULT_FOREIGN_KEYS=1). Ensure it's
 |     // compiles the SQLite3 amalgamation with -DSQLITE_DEFAULT_FOREIGN_KEYS=1). Ensure it's
 | ||||||
|     // always on. Note that our foreign keys are immediate rather than deferred, so we have to
 |     // always on. Note that our foreign keys are immediate rather than deferred, so we have to
 | ||||||
|     // be careful about the order of operations during the upgrade.
 |     // be careful about the order of operations during the upgrade.
 | ||||||
|     conn.execute("pragma foreign_keys = on", &[] as &[&dyn ToSql])?; |     conn.execute("pragma foreign_keys = on", params![])?; | ||||||
| 
 | 
 | ||||||
|     // Make the database actually durable.
 |     // Make the database actually durable.
 | ||||||
|     conn.execute("pragma fullfsync = on", &[] as &[&dyn ToSql])?; |     conn.execute("pragma fullfsync = on", params![])?; | ||||||
|     conn.execute("pragma synchronous = 2", &[] as &[&dyn ToSql])?; |     conn.execute("pragma synchronous = 2", params![])?; | ||||||
|  | 
 | ||||||
|  |     upgrade(args, db::EXPECTED_VERSION, conn)?; | ||||||
| 
 | 
 | ||||||
|     // WAL is the preferred journal mode for normal operation; it reduces the number of syncs
 |     // WAL is the preferred journal mode for normal operation; it reduces the number of syncs
 | ||||||
|     // without compromising safety.
 |     // without compromising safety.
 | ||||||
| @ -116,5 +122,55 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> { | |||||||
|         "#).unwrap();
 |         "#).unwrap();
 | ||||||
|     } |     } | ||||||
|     info!("...done."); |     info!("...done."); | ||||||
|  | 
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use crate::compare; | ||||||
|  |     use super::*; | ||||||
|  | 
 | ||||||
|  |     fn new_conn() -> Result<rusqlite::Connection, Error> { | ||||||
|  |         let conn = rusqlite::Connection::open_in_memory()?; | ||||||
|  |         conn.execute("pragma foreign_keys = on", params![])?; | ||||||
|  |         conn.execute("pragma fullfsync = on", params![])?; | ||||||
|  |         conn.execute("pragma synchronous = 2", params![])?; | ||||||
|  |         Ok(conn) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn compare(c: &rusqlite::Connection, ver: i32, fresh_sql: &str) -> Result<(), Error> { | ||||||
|  |         let fresh = new_conn()?; | ||||||
|  |         fresh.execute_batch(fresh_sql)?; | ||||||
|  |         if let Some(diffs) = compare::get_diffs("upgraded", &c, "fresh", &fresh)? { | ||||||
|  |             panic!("Version {}: differences found:\n{}", ver, diffs); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Upgrades and compares schemas.
 | ||||||
|  |     /// Doesn't (yet) compare any actual data.
 | ||||||
|  |     #[test] | ||||||
|  |     fn upgrade_and_compare() -> Result<(), Error> { | ||||||
|  |         let tmpdir = tempdir::TempDir::new("moonfire-nvr-test").unwrap(); | ||||||
|  |         let path = tmpdir.path().to_str().unwrap().to_owned(); | ||||||
|  |         let mut upgraded = new_conn()?; | ||||||
|  |         upgraded.execute_batch(include_str!("v0.sql"))?; | ||||||
|  | 
 | ||||||
|  |         for (ver, fresh_sql) in &[(1, Some(include_str!("v1.sql"))), | ||||||
|  |                                   (2, None),  // transitional; don't compare schemas.
 | ||||||
|  |                                   (3, Some(include_str!("v3.sql"))), | ||||||
|  |                                   (4, None),  // transitional; don't compare schemas.
 | ||||||
|  |                                   (4, Some(include_str!("../schema.sql")))] { | ||||||
|  |             upgrade(&Args { | ||||||
|  |                 flag_sample_file_dir: Some(&path), | ||||||
|  |                 flag_preset_journal: "delete", | ||||||
|  |                 flag_no_vacuum: false, | ||||||
|  |             }, *ver, &mut upgraded)?; | ||||||
|  |             if let Some(f) = fresh_sql { | ||||||
|  |                 compare(&upgraded, *ver, f)?; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										159
									
								
								db/upgrade/v0.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								db/upgrade/v0.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | |||||||
|  | -- This file is part of Moonfire NVR, a security camera digital video recorder. | ||||||
|  | -- Copyright (C) 2016 Scott Lamb <slamb@slamb.org> | ||||||
|  | -- | ||||||
|  | -- This program is free software: you can redistribute it and/or modify | ||||||
|  | -- it under the terms of the GNU General Public License as published by | ||||||
|  | -- the Free Software Foundation, either version 3 of the License, or | ||||||
|  | -- (at your option) any later version. | ||||||
|  | -- | ||||||
|  | -- In addition, as a special exception, the copyright holders give | ||||||
|  | -- permission to link the code of portions of this program with the | ||||||
|  | -- OpenSSL library under certain conditions as described in each | ||||||
|  | -- individual source file, and distribute linked combinations including | ||||||
|  | -- the two. | ||||||
|  | -- | ||||||
|  | -- You must obey the GNU General Public License in all respects for all | ||||||
|  | -- of the code used other than OpenSSL. If you modify file(s) with this | ||||||
|  | -- exception, you may extend this exception to your version of the | ||||||
|  | -- file(s), but you are not obligated to do so. If you do not wish to do | ||||||
|  | -- so, delete this exception statement from your version. If you delete | ||||||
|  | -- this exception statement from all source files in the program, then | ||||||
|  | -- also delete it here. | ||||||
|  | -- | ||||||
|  | -- This program is distributed in the hope that it will be useful, | ||||||
|  | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | -- GNU General Public License for more details. | ||||||
|  | -- | ||||||
|  | -- You should have received a copy of the GNU General Public License | ||||||
|  | -- along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | -- | ||||||
|  | -- schema.sql: SQLite3 database schema for Moonfire NVR. | ||||||
|  | -- See also design/schema.md. | ||||||
|  | 
 | ||||||
|  | --pragma journal_mode = wal; | ||||||
|  | 
 | ||||||
|  | -- This table tracks the schema version. | ||||||
|  | -- There is one row for the initial database creation (inserted below, after the | ||||||
|  | -- create statements) and one for each upgrade procedure (if any). | ||||||
|  | create table version ( | ||||||
|  |   id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- The unix time as of the creation/upgrade, as determined by | ||||||
|  |   -- cast(strftime('%s', 'now') as int). | ||||||
|  |   unix_time integer not null, | ||||||
|  | 
 | ||||||
|  |   -- Optional notes on the creation/upgrade; could include the binary version. | ||||||
|  |   notes text | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table camera ( | ||||||
|  |   id integer primary key, | ||||||
|  |   uuid blob unique,-- not null check (length(uuid) = 16), | ||||||
|  | 
 | ||||||
|  |   -- A short name of the camera, used in log messages. | ||||||
|  |   short_name text,-- not null, | ||||||
|  | 
 | ||||||
|  |   -- A short description of the camera. | ||||||
|  |   description text, | ||||||
|  | 
 | ||||||
|  |   -- The host (or IP address) to use in rtsp:// URLs when accessing the camera. | ||||||
|  |   host text, | ||||||
|  | 
 | ||||||
|  |   -- The username to use when accessing the camera. | ||||||
|  |   -- If empty, no username or password will be supplied. | ||||||
|  |   username text, | ||||||
|  | 
 | ||||||
|  |   -- The password to use when accessing the camera. | ||||||
|  |   password text, | ||||||
|  | 
 | ||||||
|  |   -- The path (starting with "/") to use in rtsp:// URLs to reference this | ||||||
|  |   -- camera's "main" (full-quality) video stream. | ||||||
|  |   main_rtsp_path text, | ||||||
|  | 
 | ||||||
|  |   -- The path (starting with "/") to use in rtsp:// URLs to reference this | ||||||
|  |   -- camera's "sub" (low-bandwidth) video stream. | ||||||
|  |   sub_rtsp_path text, | ||||||
|  | 
 | ||||||
|  |   -- The number of bytes of video to retain, excluding the currently-recording | ||||||
|  |   -- file. Older files will be deleted as necessary to stay within this limit. | ||||||
|  |   retain_bytes integer not null check (retain_bytes >= 0) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Each row represents a single completed recorded segment of video. | ||||||
|  | -- Recordings are typically ~60 seconds; never more than 5 minutes. | ||||||
|  | create table recording ( | ||||||
|  |   id integer primary key, | ||||||
|  |   camera_id integer references camera (id) not null, | ||||||
|  | 
 | ||||||
|  |   sample_file_bytes integer not null check (sample_file_bytes > 0), | ||||||
|  | 
 | ||||||
|  |   -- The starting time of the recording, in 90 kHz units since | ||||||
|  |   -- 1970-01-01 00:00:00 UTC. Currently on initial connection, this is taken | ||||||
|  |   -- from the local system time; on subsequent recordings, it exactly | ||||||
|  |   -- matches the previous recording's end time. | ||||||
|  |   start_time_90k integer not null check (start_time_90k > 0), | ||||||
|  | 
 | ||||||
|  |   -- The duration of the recording, in 90 kHz units. | ||||||
|  |   duration_90k integer not null | ||||||
|  |       check (duration_90k >= 0 and duration_90k < 5*60*90000), | ||||||
|  | 
 | ||||||
|  |   -- The number of 90 kHz units the local system time is ahead of the | ||||||
|  |   -- recording; negative numbers indicate the local system time is behind | ||||||
|  |   -- the recording. Large values would indicate that the local time has jumped | ||||||
|  |   -- during recording or that the local time and camera time frequencies do | ||||||
|  |   -- not match. | ||||||
|  |   local_time_delta_90k integer not null, | ||||||
|  | 
 | ||||||
|  |   video_samples integer not null check (video_samples > 0), | ||||||
|  |   video_sync_samples integer not null check (video_samples > 0), | ||||||
|  |   video_sample_entry_id integer references video_sample_entry (id), | ||||||
|  | 
 | ||||||
|  |   sample_file_uuid blob not null check (length(sample_file_uuid) = 16), | ||||||
|  |   sample_file_sha1 blob not null check (length(sample_file_sha1) = 20), | ||||||
|  |   video_index blob not null check (length(video_index) > 0) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create index recording_cover on recording ( | ||||||
|  |   -- Typical queries use "where camera_id = ? order by start_time_90k (desc)?". | ||||||
|  |   camera_id, | ||||||
|  |   start_time_90k, | ||||||
|  | 
 | ||||||
|  |   -- These fields are not used for ordering; they cover most queries so | ||||||
|  |   -- that only database verification and actual viewing of recordings need | ||||||
|  |   -- to consult the underlying row. | ||||||
|  |   duration_90k, | ||||||
|  |   video_samples, | ||||||
|  |   video_sync_samples, | ||||||
|  |   video_sample_entry_id, | ||||||
|  |   sample_file_bytes | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Files in the sample file directory which may be present but should simply be | ||||||
|  | -- discarded on startup. (Recordings which were never completed or have been | ||||||
|  | -- marked for completion.) | ||||||
|  | create table reserved_sample_files ( | ||||||
|  |   uuid blob primary key check (length(uuid) = 16), | ||||||
|  |   state integer not null  -- 0 (writing) or 1 (deleted) | ||||||
|  | ) without rowid; | ||||||
|  | 
 | ||||||
|  | -- A concrete box derived from a ISO/IEC 14496-12 section 8.5.2 | ||||||
|  | -- VisualSampleEntry box. Describes the codec, width, height, etc. | ||||||
|  | create table video_sample_entry ( | ||||||
|  |   id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- A SHA-1 hash of |bytes|. | ||||||
|  |   sha1 blob unique not null check (length(sha1) = 20), | ||||||
|  | 
 | ||||||
|  |   -- The width and height in pixels; must match values within | ||||||
|  |   -- |sample_entry_bytes|. | ||||||
|  |   width integer not null check (width > 0), | ||||||
|  |   height integer not null check (height > 0), | ||||||
|  | 
 | ||||||
|  |   -- The serialized box, including the leading length and box type (avcC in | ||||||
|  |   -- the case of H.264). | ||||||
|  |   data blob not null check (length(data) > 86) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | insert into version (id, unix_time,                           notes) | ||||||
|  |              values (0,  cast(strftime('%s', 'now') as int), 'db creation'); | ||||||
| @ -43,7 +43,7 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> | |||||||
|         alter table camera rename to old_camera; |         alter table camera rename to old_camera; | ||||||
|         create table camera ( |         create table camera ( | ||||||
|           id integer primary key, |           id integer primary key, | ||||||
|           uuid blob unique, |           uuid blob unique not null check (length(uuid) = 16), | ||||||
|           short_name text not null, |           short_name text not null, | ||||||
|           description text, |           description text, | ||||||
|           host text, |           host text, | ||||||
|  | |||||||
							
								
								
									
										205
									
								
								db/upgrade/v1.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								db/upgrade/v1.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,205 @@ | |||||||
|  | -- This file is part of Moonfire NVR, a security camera digital video recorder. | ||||||
|  | -- Copyright (C) 2016 Scott Lamb <slamb@slamb.org> | ||||||
|  | -- | ||||||
|  | -- This program is free software: you can redistribute it and/or modify | ||||||
|  | -- it under the terms of the GNU General Public License as published by | ||||||
|  | -- the Free Software Foundation, either version 3 of the License, or | ||||||
|  | -- (at your option) any later version. | ||||||
|  | -- | ||||||
|  | -- In addition, as a special exception, the copyright holders give | ||||||
|  | -- permission to link the code of portions of this program with the | ||||||
|  | -- OpenSSL library under certain conditions as described in each | ||||||
|  | -- individual source file, and distribute linked combinations including | ||||||
|  | -- the two. | ||||||
|  | -- | ||||||
|  | -- You must obey the GNU General Public License in all respects for all | ||||||
|  | -- of the code used other than OpenSSL. If you modify file(s) with this | ||||||
|  | -- exception, you may extend this exception to your version of the | ||||||
|  | -- file(s), but you are not obligated to do so. If you do not wish to do | ||||||
|  | -- so, delete this exception statement from your version. If you delete | ||||||
|  | -- this exception statement from all source files in the program, then | ||||||
|  | -- also delete it here. | ||||||
|  | -- | ||||||
|  | -- This program is distributed in the hope that it will be useful, | ||||||
|  | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | -- GNU General Public License for more details. | ||||||
|  | -- | ||||||
|  | -- You should have received a copy of the GNU General Public License | ||||||
|  | -- along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | -- | ||||||
|  | -- schema.sql: SQLite3 database schema for Moonfire NVR. | ||||||
|  | -- See also design/schema.md. | ||||||
|  | 
 | ||||||
|  | -- This table tracks the schema version. | ||||||
|  | -- There is one row for the initial database creation (inserted below, after the | ||||||
|  | -- create statements) and one for each upgrade procedure (if any). | ||||||
|  | create table version ( | ||||||
|  |   id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- The unix time as of the creation/upgrade, as determined by | ||||||
|  |   -- cast(strftime('%s', 'now') as int). | ||||||
|  |   unix_time integer not null, | ||||||
|  | 
 | ||||||
|  |   -- Optional notes on the creation/upgrade; could include the binary version. | ||||||
|  |   notes text | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table camera ( | ||||||
|  |   id integer primary key, | ||||||
|  |   uuid blob unique not null check (length(uuid) = 16), | ||||||
|  | 
 | ||||||
|  |   -- A short name of the camera, used in log messages. | ||||||
|  |   short_name text not null, | ||||||
|  | 
 | ||||||
|  |   -- A short description of the camera. | ||||||
|  |   description text, | ||||||
|  | 
 | ||||||
|  |   -- The host (or IP address) to use in rtsp:// URLs when accessing the camera. | ||||||
|  |   host text, | ||||||
|  | 
 | ||||||
|  |   -- The username to use when accessing the camera. | ||||||
|  |   -- If empty, no username or password will be supplied. | ||||||
|  |   username text, | ||||||
|  | 
 | ||||||
|  |   -- The password to use when accessing the camera. | ||||||
|  |   password text, | ||||||
|  | 
 | ||||||
|  |   -- The path (starting with "/") to use in rtsp:// URLs to reference this | ||||||
|  |   -- camera's "main" (full-quality) video stream. | ||||||
|  |   main_rtsp_path text, | ||||||
|  | 
 | ||||||
|  |   -- The path (starting with "/") to use in rtsp:// URLs to reference this | ||||||
|  |   -- camera's "sub" (low-bandwidth) video stream. | ||||||
|  |   sub_rtsp_path text, | ||||||
|  | 
 | ||||||
|  |   -- The number of bytes of video to retain, excluding the currently-recording | ||||||
|  |   -- file. Older files will be deleted as necessary to stay within this limit. | ||||||
|  |   retain_bytes integer not null check (retain_bytes >= 0), | ||||||
|  | 
 | ||||||
|  |   -- The low 32 bits of the next recording id to assign for this camera. | ||||||
|  |   -- Typically this is the maximum current recording + 1, but it does | ||||||
|  |   -- not decrease if that recording is deleted. | ||||||
|  |   next_recording_id integer not null check (next_recording_id >= 0) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Each row represents a single completed recorded segment of video. | ||||||
|  | -- Recordings are typically ~60 seconds; never more than 5 minutes. | ||||||
|  | create table recording ( | ||||||
|  |   -- The high 32 bits of composite_id are taken from the camera's id, which | ||||||
|  |   -- improves locality. The low 32 bits are taken from the camera's | ||||||
|  |   -- next_recording_id (which should be post-incremented in the same | ||||||
|  |   -- transaction). It'd be simpler to use a "without rowid" table and separate | ||||||
|  |   -- fields to make up the primary key, but | ||||||
|  |   -- <https://www.sqlite.org/withoutrowid.html> points out that "without rowid" | ||||||
|  |   -- is not appropriate when the average row size is in excess of 50 bytes. | ||||||
|  |   -- recording_cover rows (which match this id format) are typically 1--5 KiB. | ||||||
|  |   composite_id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- This field is redundant with id above, but used to enforce the reference | ||||||
|  |   -- constraint and to structure the recording_start_time index. | ||||||
|  |   camera_id integer not null references camera (id), | ||||||
|  | 
 | ||||||
|  |   -- The offset of this recording within a run. 0 means this was the first | ||||||
|  |   -- recording made from a RTSP session. The start of the run has id | ||||||
|  |   -- (id-run_offset). | ||||||
|  |   run_offset integer not null, | ||||||
|  | 
 | ||||||
|  |   -- flags is a bitmask: | ||||||
|  |   -- | ||||||
|  |   -- * 1, or "trailing zero", indicates that this recording is the last in a | ||||||
|  |   --   stream. As the duration of a sample is not known until the next sample | ||||||
|  |   --   is received, the final sample in this recording will have duration 0. | ||||||
|  |   flags integer not null, | ||||||
|  | 
 | ||||||
|  |   sample_file_bytes integer not null check (sample_file_bytes > 0), | ||||||
|  | 
 | ||||||
|  |   -- The starting time of the recording, in 90 kHz units since | ||||||
|  |   -- 1970-01-01 00:00:00 UTC. Currently on initial connection, this is taken | ||||||
|  |   -- from the local system time; on subsequent recordings, it exactly | ||||||
|  |   -- matches the previous recording's end time. | ||||||
|  |   start_time_90k integer not null check (start_time_90k > 0), | ||||||
|  | 
 | ||||||
|  |   -- The duration of the recording, in 90 kHz units. | ||||||
|  |   duration_90k integer not null | ||||||
|  |       check (duration_90k >= 0 and duration_90k < 5*60*90000), | ||||||
|  | 
 | ||||||
|  |   -- The number of 90 kHz units the local system time is ahead of the | ||||||
|  |   -- recording; negative numbers indicate the local system time is behind | ||||||
|  |   -- the recording. Large absolute values would indicate that the local time | ||||||
|  |   -- has jumped during recording or that the local time and camera time | ||||||
|  |   -- frequencies do not match. | ||||||
|  |   local_time_delta_90k integer not null, | ||||||
|  | 
 | ||||||
|  |   video_samples integer not null check (video_samples > 0), | ||||||
|  |   video_sync_samples integer not null check (video_sync_samples > 0), | ||||||
|  |   video_sample_entry_id integer references video_sample_entry (id), | ||||||
|  | 
 | ||||||
|  |   check (composite_id >> 32 = camera_id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create index recording_cover on recording ( | ||||||
|  |   -- Typical queries use "where camera_id = ? order by start_time_90k". | ||||||
|  |   camera_id, | ||||||
|  |   start_time_90k, | ||||||
|  | 
 | ||||||
|  |   -- These fields are not used for ordering; they cover most queries so | ||||||
|  |   -- that only database verification and actual viewing of recordings need | ||||||
|  |   -- to consult the underlying row. | ||||||
|  |   duration_90k, | ||||||
|  |   video_samples, | ||||||
|  |   video_sync_samples, | ||||||
|  |   video_sample_entry_id, | ||||||
|  |   sample_file_bytes, | ||||||
|  |   run_offset, | ||||||
|  |   flags | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Large fields for a recording which are not needed when simply listing all | ||||||
|  | -- of the recordings in a given range. In particular, when serving a byte | ||||||
|  | -- range within a .mp4 file, the recording_playback row is needed for the | ||||||
|  | -- recording(s) corresponding to that particular byte range, needed, but the | ||||||
|  | -- recording rows suffice for all other recordings in the .mp4. | ||||||
|  | create table recording_playback ( | ||||||
|  |   -- See description on recording table. | ||||||
|  |   composite_id integer primary key references recording (composite_id), | ||||||
|  | 
 | ||||||
|  |   -- The binary representation of the sample file's uuid. The canonical text | ||||||
|  |   -- representation of this uuid is the filename within the sample file dir. | ||||||
|  |   sample_file_uuid blob not null check (length(sample_file_uuid) = 16), | ||||||
|  | 
 | ||||||
|  |   -- The sha1 hash of the contents of the sample file. | ||||||
|  |   sample_file_sha1 blob not null check (length(sample_file_sha1) = 20), | ||||||
|  | 
 | ||||||
|  |   -- See design/schema.md#video_index for a description of this field. | ||||||
|  |   video_index blob not null check (length(video_index) > 0) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Files in the sample file directory which may be present but should simply be | ||||||
|  | -- discarded on startup. (Recordings which were never completed or have been | ||||||
|  | -- marked for completion.) | ||||||
|  | create table reserved_sample_files ( | ||||||
|  |   uuid blob primary key check (length(uuid) = 16), | ||||||
|  |   state integer not null  -- 0 (writing) or 1 (deleted) | ||||||
|  | ) without rowid; | ||||||
|  | 
 | ||||||
|  | -- A concrete box derived from a ISO/IEC 14496-12 section 8.5.2 | ||||||
|  | -- VisualSampleEntry box. Describes the codec, width, height, etc. | ||||||
|  | create table video_sample_entry ( | ||||||
|  |   id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- A SHA-1 hash of |bytes|. | ||||||
|  |   sha1 blob unique not null check (length(sha1) = 20), | ||||||
|  | 
 | ||||||
|  |   -- The width and height in pixels; must match values within | ||||||
|  |   -- |sample_entry_bytes|. | ||||||
|  |   width integer not null check (width > 0), | ||||||
|  |   height integer not null check (height > 0), | ||||||
|  | 
 | ||||||
|  |   -- The serialized box, including the leading length and box type (avcC in | ||||||
|  |   -- the case of H.264). | ||||||
|  |   data blob not null check (length(data) > 86) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | insert into version (id, unix_time,                           notes) | ||||||
|  |              values (1,  cast(strftime('%s', 'now') as int), 'db creation'); | ||||||
| @ -98,6 +98,7 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> | |||||||
|           last_use_peer_addr blob, |           last_use_peer_addr blob, | ||||||
|           use_count not null default 0 |           use_count not null default 0 | ||||||
|         ) without rowid; |         ) without rowid; | ||||||
|  |         create index user_session_uid on user_session (user_id); | ||||||
|     "#)?;
 |     "#)?;
 | ||||||
|     let db_uuid = ::uuid::Uuid::new_v4(); |     let db_uuid = ::uuid::Uuid::new_v4(); | ||||||
|     let db_uuid_bytes = &db_uuid.as_bytes()[..]; |     let db_uuid_bytes = &db_uuid.as_bytes()[..]; | ||||||
|  | |||||||
							
								
								
									
										400
									
								
								db/upgrade/v3.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										400
									
								
								db/upgrade/v3.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,400 @@ | |||||||
|  | -- This file is part of Moonfire NVR, a security camera digital video recorder. | ||||||
|  | -- Copyright (C) 2016 Scott Lamb <slamb@slamb.org> | ||||||
|  | -- | ||||||
|  | -- This program is free software: you can redistribute it and/or modify | ||||||
|  | -- it under the terms of the GNU General Public License as published by | ||||||
|  | -- the Free Software Foundation, either version 3 of the License, or | ||||||
|  | -- (at your option) any later version. | ||||||
|  | -- | ||||||
|  | -- In addition, as a special exception, the copyright holders give | ||||||
|  | -- permission to link the code of portions of this program with the | ||||||
|  | -- OpenSSL library under certain conditions as described in each | ||||||
|  | -- individual source file, and distribute linked combinations including | ||||||
|  | -- the two. | ||||||
|  | -- | ||||||
|  | -- You must obey the GNU General Public License in all respects for all | ||||||
|  | -- of the code used other than OpenSSL. If you modify file(s) with this | ||||||
|  | -- exception, you may extend this exception to your version of the | ||||||
|  | -- file(s), but you are not obligated to do so. If you do not wish to do | ||||||
|  | -- so, delete this exception statement from your version. If you delete | ||||||
|  | -- this exception statement from all source files in the program, then | ||||||
|  | -- also delete it here. | ||||||
|  | -- | ||||||
|  | -- This program is distributed in the hope that it will be useful, | ||||||
|  | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | -- GNU General Public License for more details. | ||||||
|  | -- | ||||||
|  | -- You should have received a copy of the GNU General Public License | ||||||
|  | -- along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | -- | ||||||
|  | -- schema.sql: SQLite3 database schema for Moonfire NVR. | ||||||
|  | -- See also design/schema.md. | ||||||
|  | 
 | ||||||
|  | -- Database metadata. There should be exactly one row in this table. | ||||||
|  | create table meta ( | ||||||
|  |   uuid blob not null check (length(uuid) = 16) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- This table tracks the schema version. | ||||||
|  | -- There is one row for the initial database creation (inserted below, after the | ||||||
|  | -- create statements) and one for each upgrade procedure (if any). | ||||||
|  | create table version ( | ||||||
|  |   id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- The unix time as of the creation/upgrade, as determined by | ||||||
|  |   -- cast(strftime('%s', 'now') as int). | ||||||
|  |   unix_time integer not null, | ||||||
|  | 
 | ||||||
|  |   -- Optional notes on the creation/upgrade; could include the binary version. | ||||||
|  |   notes text | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Tracks every time the database has been opened in read/write mode. | ||||||
|  | -- This is used to ensure directories are in sync with the database (see | ||||||
|  | -- schema.proto:DirMeta), to disambiguate uncommitted recordings, and | ||||||
|  | -- potentially to understand time problems. | ||||||
|  | create table open ( | ||||||
|  |   id integer primary key, | ||||||
|  |   uuid blob unique not null check (length(uuid) = 16), | ||||||
|  | 
 | ||||||
|  |   -- Information about when / how long the database was open. These may be all | ||||||
|  |   -- null, for example in the open that represents all information written | ||||||
|  |   -- prior to database version 3. | ||||||
|  | 
 | ||||||
|  |   -- System time when the database was opened, in 90 kHz units since | ||||||
|  |   -- 1970-01-01 00:00:00Z excluding leap seconds. | ||||||
|  |   start_time_90k integer, | ||||||
|  | 
 | ||||||
|  |   -- System time when the database was closed or (on crash) last flushed. | ||||||
|  |   end_time_90k integer, | ||||||
|  | 
 | ||||||
|  |   -- How long the database was open. This is end_time_90k - start_time_90k if | ||||||
|  |   -- there were no time steps or leap seconds during this time. | ||||||
|  |   duration_90k integer | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table sample_file_dir ( | ||||||
|  |   id integer primary key, | ||||||
|  |   path text unique not null, | ||||||
|  |   uuid blob unique not null check (length(uuid) = 16), | ||||||
|  | 
 | ||||||
|  |   -- The last (read/write) open of this directory which fully completed. | ||||||
|  |   -- See schema.proto:DirMeta for a more complete description. | ||||||
|  |   last_complete_open_id integer references open (id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table camera ( | ||||||
|  |   id integer primary key, | ||||||
|  |   uuid blob unique not null check (length(uuid) = 16), | ||||||
|  | 
 | ||||||
|  |   -- A short name of the camera, used in log messages. | ||||||
|  |   short_name text not null, | ||||||
|  | 
 | ||||||
|  |   -- A short description of the camera. | ||||||
|  |   description text, | ||||||
|  | 
 | ||||||
|  |   -- The host (or IP address) to use in rtsp:// URLs when accessing the camera. | ||||||
|  |   host text, | ||||||
|  | 
 | ||||||
|  |   -- The username to use when accessing the camera. | ||||||
|  |   -- If empty, no username or password will be supplied. | ||||||
|  |   username text, | ||||||
|  | 
 | ||||||
|  |   -- The password to use when accessing the camera. | ||||||
|  |   password text | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table stream ( | ||||||
|  |   id integer primary key, | ||||||
|  |   camera_id integer not null references camera (id), | ||||||
|  |   sample_file_dir_id integer references sample_file_dir (id), | ||||||
|  |   type text not null check (type in ('main', 'sub')), | ||||||
|  | 
 | ||||||
|  |   -- If record is true, the stream should start recording when moonfire | ||||||
|  |   -- starts. If false, no new recordings will be made, but old recordings | ||||||
|  |   -- will not be deleted. | ||||||
|  |   record integer not null check (record in (1, 0)), | ||||||
|  | 
 | ||||||
|  |   -- The path (starting with "/") to use in rtsp:// URLs to for this stream. | ||||||
|  |   rtsp_path text not null, | ||||||
|  | 
 | ||||||
|  |   -- The number of bytes of video to retain, excluding the currently-recording | ||||||
|  |   -- file. Older files will be deleted as necessary to stay within this limit. | ||||||
|  |   retain_bytes integer not null check (retain_bytes >= 0), | ||||||
|  | 
 | ||||||
|  |   -- Flush the database when the first instant of completed recording is this | ||||||
|  |   -- many seconds old. A value of 0 means that every completed recording will | ||||||
|  |   -- cause an immediate flush. Higher values may allow flushes to be combined, | ||||||
|  |   -- reducing SSD write cycles. For example, if all streams have a flush_if_sec | ||||||
|  |   -- >= x sec, there will be: | ||||||
|  |   -- | ||||||
|  |   -- * at most one flush per x sec in total | ||||||
|  |   -- * at most x sec of completed but unflushed recordings per stream. | ||||||
|  |   -- * at most x completed but unflushed recordings per stream, in the worst | ||||||
|  |   --   case where a recording instantly fails, waits the 1-second retry delay, | ||||||
|  |   --   then fails again, forever. | ||||||
|  |   flush_if_sec integer not null, | ||||||
|  | 
 | ||||||
|  |   -- The low 32 bits of the next recording id to assign for this stream. | ||||||
|  |   -- Typically this is the maximum current recording + 1, but it does | ||||||
|  |   -- not decrease if that recording is deleted. | ||||||
|  |   next_recording_id integer not null check (next_recording_id >= 0), | ||||||
|  | 
 | ||||||
|  |   unique (camera_id, type) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Each row represents a single completed recorded segment of video. | ||||||
|  | -- Recordings are typically ~60 seconds; never more than 5 minutes. | ||||||
|  | create table recording ( | ||||||
|  |   -- The high 32 bits of composite_id are taken from the stream's id, which | ||||||
|  |   -- improves locality. The low 32 bits are taken from the stream's | ||||||
|  |   -- next_recording_id (which should be post-incremented in the same | ||||||
|  |   -- transaction). It'd be simpler to use a "without rowid" table and separate | ||||||
|  |   -- fields to make up the primary key, but | ||||||
|  |   -- <https://www.sqlite.org/withoutrowid.html> points out that "without rowid" | ||||||
|  |   -- is not appropriate when the average row size is in excess of 50 bytes. | ||||||
|  |   -- recording_cover rows (which match this id format) are typically 1--5 KiB. | ||||||
|  |   composite_id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- The open in which this was committed to the database. For a given | ||||||
|  |   -- composite_id, only one recording will ever be committed to the database, | ||||||
|  |   -- but in-memory state may reflect a recording which never gets committed. | ||||||
|  |   -- This field allows disambiguation in etags and such. | ||||||
|  |   open_id integer not null references open (id), | ||||||
|  | 
 | ||||||
|  |   -- This field is redundant with id above, but used to enforce the reference | ||||||
|  |   -- constraint and to structure the recording_start_time index. | ||||||
|  |   stream_id integer not null references stream (id), | ||||||
|  | 
 | ||||||
|  |   -- The offset of this recording within a run. 0 means this was the first | ||||||
|  |   -- recording made from a RTSP session. The start of the run has id | ||||||
|  |   -- (id-run_offset). | ||||||
|  |   run_offset integer not null, | ||||||
|  | 
 | ||||||
|  |   -- flags is a bitmask: | ||||||
|  |   -- | ||||||
|  |   -- * 1, or "trailing zero", indicates that this recording is the last in a | ||||||
|  |   --   stream. As the duration of a sample is not known until the next sample | ||||||
|  |   --   is received, the final sample in this recording will have duration 0. | ||||||
|  |   flags integer not null, | ||||||
|  | 
 | ||||||
|  |   sample_file_bytes integer not null check (sample_file_bytes > 0), | ||||||
|  | 
 | ||||||
|  |   -- The starting time of the recording, in 90 kHz units since | ||||||
|  |   -- 1970-01-01 00:00:00 UTC excluding leap seconds. Currently on initial | ||||||
|  |   -- connection, this is taken from the local system time; on subsequent | ||||||
|  |   -- recordings, it exactly matches the previous recording's end time. | ||||||
|  |   start_time_90k integer not null check (start_time_90k > 0), | ||||||
|  | 
 | ||||||
|  |   -- The duration of the recording, in 90 kHz units. | ||||||
|  |   duration_90k integer not null | ||||||
|  |       check (duration_90k >= 0 and duration_90k < 5*60*90000), | ||||||
|  | 
 | ||||||
|  |   video_samples integer not null check (video_samples > 0), | ||||||
|  |   video_sync_samples integer not null check (video_sync_samples > 0), | ||||||
|  |   video_sample_entry_id integer references video_sample_entry (id), | ||||||
|  | 
 | ||||||
|  |   check (composite_id >> 32 = stream_id) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create index recording_cover on recording ( | ||||||
|  |   -- Typical queries use "where stream_id = ? order by start_time_90k". | ||||||
|  |   stream_id, | ||||||
|  |   start_time_90k, | ||||||
|  | 
 | ||||||
|  |   -- These fields are not used for ordering; they cover most queries so | ||||||
|  |   -- that only database verification and actual viewing of recordings need | ||||||
|  |   -- to consult the underlying row. | ||||||
|  |   open_id, | ||||||
|  |   duration_90k, | ||||||
|  |   video_samples, | ||||||
|  |   video_sync_samples, | ||||||
|  |   video_sample_entry_id, | ||||||
|  |   sample_file_bytes, | ||||||
|  |   run_offset, | ||||||
|  |   flags | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Fields which are only needed to check/correct database integrity problems | ||||||
|  | -- (such as incorrect timestamps). | ||||||
|  | create table recording_integrity ( | ||||||
|  |   -- See description on recording table. | ||||||
|  |   composite_id integer primary key references recording (composite_id), | ||||||
|  | 
 | ||||||
|  |   -- The number of 90 kHz units the local system's monotonic clock has | ||||||
|  |   -- advanced more than the stated duration of recordings in a run since the | ||||||
|  |   -- first recording ended. Negative numbers indicate the local system time is | ||||||
|  |   -- behind the recording. | ||||||
|  |   -- | ||||||
|  |   -- The first recording of a run (that is, one with run_offset=0) has null | ||||||
|  |   -- local_time_delta_90k because errors are assumed to | ||||||
|  |   -- be the result of initial buffering rather than frequency mismatch. | ||||||
|  |   -- | ||||||
|  |   -- This value should be near 0 even on long runs in which the camera's clock | ||||||
|  |   -- and local system's clock frequency differ because each recording's delta | ||||||
|  |   -- is used to correct the durations of the next (up to 500 ppm error). | ||||||
|  |   local_time_delta_90k integer, | ||||||
|  | 
 | ||||||
|  |   -- The number of 90 kHz units the local system's monotonic clock had | ||||||
|  |   -- advanced since the database was opened, as of the start of recording. | ||||||
|  |   -- TODO: fill this in! | ||||||
|  |   local_time_since_open_90k integer, | ||||||
|  | 
 | ||||||
|  |   -- The difference between start_time_90k+duration_90k and a wall clock | ||||||
|  |   -- timestamp captured at end of this recording. This is meaningful for all | ||||||
|  |   -- recordings in a run, even the initial one (run_offset=0), because | ||||||
|  |   -- start_time_90k is derived from the wall time as of when recording | ||||||
|  |   -- starts, not when it ends. | ||||||
|  |   -- TODO: fill this in! | ||||||
|  |   wall_time_delta_90k integer, | ||||||
|  | 
 | ||||||
|  |   -- The sha1 hash of the contents of the sample file. | ||||||
|  |   sample_file_sha1 blob check (length(sample_file_sha1) <= 20) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Large fields for a recording which are needed ony for playback. | ||||||
|  | -- In particular, when serving a byte range within a .mp4 file, the | ||||||
|  | -- recording_playback row is needed for the recording(s) corresponding to that | ||||||
|  | -- particular byte range, needed, but the recording rows suffice for all other | ||||||
|  | -- recordings in the .mp4. | ||||||
|  | create table recording_playback ( | ||||||
|  |   -- See description on recording table. | ||||||
|  |   composite_id integer primary key references recording (composite_id), | ||||||
|  | 
 | ||||||
|  |   -- See design/schema.md#video_index for a description of this field. | ||||||
|  |   video_index blob not null check (length(video_index) > 0) | ||||||
|  | 
 | ||||||
|  |   -- audio_index could be added here in the future. | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- Files which are to be deleted (may or may not still exist). | ||||||
|  | -- Note that besides these files, for each stream, any recordings >= its | ||||||
|  | -- next_recording_id should be discarded on startup. | ||||||
|  | create table garbage ( | ||||||
|  |   -- This is _mostly_ redundant with composite_id, which contains the stream | ||||||
|  |   -- id and thus a linkage to the sample file directory. Listing it here | ||||||
|  |   -- explicitly means that streams can be deleted without losing the | ||||||
|  |   -- association of garbage to directory. | ||||||
|  |   sample_file_dir_id integer not null references sample_file_dir (id), | ||||||
|  | 
 | ||||||
|  |   -- See description on recording table. | ||||||
|  |   composite_id integer not null, | ||||||
|  | 
 | ||||||
|  |   -- Organize the table first by directory, as that's how it will be queried. | ||||||
|  |   primary key (sample_file_dir_id, composite_id) | ||||||
|  | ) without rowid; | ||||||
|  | 
 | ||||||
|  | -- A concrete box derived from a ISO/IEC 14496-12 section 8.5.2 | ||||||
|  | -- VisualSampleEntry box. Describes the codec, width, height, etc. | ||||||
|  | create table video_sample_entry ( | ||||||
|  |   id integer primary key, | ||||||
|  | 
 | ||||||
|  |   -- A SHA-1 hash of |bytes|. | ||||||
|  |   sha1 blob unique not null check (length(sha1) = 20), | ||||||
|  | 
 | ||||||
|  |   -- The width and height in pixels; must match values within | ||||||
|  |   -- |sample_entry_bytes|. | ||||||
|  |   width integer not null check (width > 0), | ||||||
|  |   height integer not null check (height > 0), | ||||||
|  | 
 | ||||||
|  |   -- The codec in RFC-6381 format, such as "avc1.4d001f". | ||||||
|  |   rfc6381_codec text not null, | ||||||
|  | 
 | ||||||
|  |   -- The serialized box, including the leading length and box type (avcC in | ||||||
|  |   -- the case of H.264). | ||||||
|  |   data blob not null check (length(data) > 86) | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | create table user ( | ||||||
|  |   id integer primary key, | ||||||
|  |   username unique not null, | ||||||
|  | 
 | ||||||
|  |   -- Bitwise mask of flags: | ||||||
|  |   -- 1: disabled. If set, no method of authentication for this user will succeed. | ||||||
|  |   flags integer not null, | ||||||
|  | 
 | ||||||
|  |   -- If set, a hash for password authentication, as generated by `libpasta::hash_password`. | ||||||
|  |   password_hash text, | ||||||
|  | 
 | ||||||
|  |   -- A counter which increments with every password reset or clear. | ||||||
|  |   password_id integer not null default 0, | ||||||
|  | 
 | ||||||
|  |   -- Updated lazily on database flush; reset when password_id is incremented. | ||||||
|  |   -- This could be used to automatically disable the password on hitting a threshold. | ||||||
|  |   password_failure_count integer not null default 0, | ||||||
|  | 
 | ||||||
|  |   -- If set, a Unix UID that is accepted for authentication when using HTTP over | ||||||
|  |   -- a Unix domain socket. (Additionally, the UID running Moonfire NVR can authenticate | ||||||
|  |   -- as anyone; there's no point in trying to do otherwise.) This might be an easy | ||||||
|  |   -- bootstrap method once configuration happens through a web UI rather than text UI. | ||||||
|  |   unix_uid integer | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | -- A single session, whether for browser or robot use. | ||||||
|  | -- These map at the HTTP layer to an "s" cookie (exact format described | ||||||
|  | -- elsewhere), which holds the session id and an encrypted sequence number for | ||||||
|  | -- replay protection. | ||||||
|  | create table user_session ( | ||||||
|  |   -- The session id is a 48-byte blob. This is the unencoded, unsalted Blake2b-192 | ||||||
|  |   -- (24 bytes) of the unencoded session id. Much like `password_hash`, a | ||||||
|  |   -- hash is used here so that a leaked database backup can't be trivially used | ||||||
|  |   -- to steal credentials. | ||||||
|  |   session_id_hash blob primary key not null, | ||||||
|  | 
 | ||||||
|  |   user_id integer references user (id) not null, | ||||||
|  | 
 | ||||||
|  |   -- A 32-byte random number. Used to derive keys for the replay protection | ||||||
|  |   -- and CSRF tokens. | ||||||
|  |   seed blob not null, | ||||||
|  | 
 | ||||||
|  |   -- A bitwise mask of flags, currently all properties of the HTTP cookie | ||||||
|  |   -- used to hold the session: | ||||||
|  |   -- 1: HttpOnly | ||||||
|  |   -- 2: Secure | ||||||
|  |   -- 4: SameSite=Lax | ||||||
|  |   -- 8: SameSite=Strict - 4 must also be set. | ||||||
|  |   flags integer not null, | ||||||
|  | 
 | ||||||
|  |   -- The domain of the HTTP cookie used to store this session. The outbound | ||||||
|  |   -- `Set-Cookie` header never specifies a scope, so this matches the `Host:` of | ||||||
|  |   -- the inbound HTTP request (minus the :port, if any was specified). | ||||||
|  |   domain text, | ||||||
|  | 
 | ||||||
|  |   -- An editable description which might describe the device/program which uses | ||||||
|  |   -- this session, such as "Chromebook", "iPhone", or "motion detection worker". | ||||||
|  |   description text, | ||||||
|  | 
 | ||||||
|  |   creation_password_id integer,        -- the id it was created from, if created via password | ||||||
|  |   creation_time_sec integer not null,  -- sec since epoch | ||||||
|  |   creation_user_agent text,            -- User-Agent header from inbound HTTP request. | ||||||
|  |   creation_peer_addr blob,             -- IPv4 or IPv6 address, or null for Unix socket. | ||||||
|  | 
 | ||||||
|  |   revocation_time_sec integer,         -- sec since epoch | ||||||
|  |   revocation_user_agent text,          -- User-Agent header from inbound HTTP request. | ||||||
|  |   revocation_peer_addr blob,           -- IPv4 or IPv6 address, or null for Unix socket/no peer. | ||||||
|  | 
 | ||||||
|  |   -- A value indicating the reason for revocation, with optional additional | ||||||
|  |   -- text detail. Enumeration values: | ||||||
|  |   -- 0: logout link clicked (i.e. from within the session itself) | ||||||
|  |   -- | ||||||
|  |   -- This might be extended for a variety of other reasons: | ||||||
|  |   -- x: user revoked (while authenticated in another way) | ||||||
|  |   -- x: password change invalidated all sessions created with that password | ||||||
|  |   -- x: expired (due to fixed total time or time inactive) | ||||||
|  |   -- x: evicted (due to too many sessions) | ||||||
|  |   -- x: suspicious activity | ||||||
|  |   revocation_reason integer, | ||||||
|  |   revocation_reason_detail text, | ||||||
|  | 
 | ||||||
|  |   -- Information about requests which used this session, updated lazily on database flush. | ||||||
|  |   last_use_time_sec integer,           -- sec since epoch | ||||||
|  |   last_use_user_agent text,            -- User-Agent header from inbound HTTP request. | ||||||
|  |   last_use_peer_addr blob,             -- IPv4 or IPv6 address, or null for Unix socket. | ||||||
|  |   use_count not null default 0 | ||||||
|  | ) without rowid; | ||||||
|  | 
 | ||||||
|  | create index user_session_uid on user_session (user_id); | ||||||
|  | 
 | ||||||
|  | insert into version (id, unix_time,                           notes) | ||||||
|  |              values (3,  cast(strftime('%s', 'now') as int), 'db creation'); | ||||||
| @ -185,6 +185,10 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> | |||||||
|         drop table old_recording; |         drop table old_recording; | ||||||
|         drop table old_stream; |         drop table old_stream; | ||||||
|         drop table old_camera; |         drop table old_camera; | ||||||
|  | 
 | ||||||
|  |         -- This was supposed to be present in version 2, but the upgrade procedure used to miss it. | ||||||
|  |         -- Catch up so we know a version 4 database is right. | ||||||
|  |         create index if not exists user_session_uid on user_session (user_id); | ||||||
|     "#)?;
 |     "#)?;
 | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user