diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7d02d4ba2..80dac6605 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,10 +1,9 @@ { "ImportPath": "github.com/calmh/syncthing", - "GoVersion": "go1.2.1", + "GoVersion": "go1.2.2", "Packages": [ "./cmd/syncthing", "./cmd/assets", - "./cmd/stcli", "./discover/cmd/discosrv" ], "Deps": [ @@ -49,6 +48,18 @@ { "ImportPath": "github.com/juju/ratelimit", "Rev": "cbaa435c80a9716e086f25d409344b26c4039358" + }, + { + "ImportPath": "github.com/vitrun/qart/coding", + "Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0" + }, + { + "ImportPath": "github.com/vitrun/qart/gf256", + "Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0" + }, + { + "ImportPath": "github.com/vitrun/qart/qr", + "Rev": "ccb109cf25f0cd24474da73b9fee4e7a3e8a8ce0" } ] } diff --git a/Godeps/_workspace/src/github.com/vitrun/qart/coding/coding.go b/Godeps/_workspace/src/github.com/vitrun/qart/coding/coding.go new file mode 100644 index 000000000..df7540944 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vitrun/qart/coding/coding.go @@ -0,0 +1,815 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package coding implements low-level QR coding details. +package coding + +import ( + "fmt" + "strconv" + "strings" + "github.com/vitrun/qart/gf256" +) + +// Field is the field for QR error correction. +var Field = gf256.NewField(0x11d, 2) + +// A Version represents a QR version. +// The version specifies the size of the QR code: +// a QR code with version v has 4v+17 pixels on a side. +// Versions number from 1 to 40: the larger the version, +// the more information the code can store. +type Version int + +const MinVersion = 1 +const MaxVersion = 40 + +func (v Version) String() string { + return strconv.Itoa(int(v)) +} + +func (v Version) sizeClass() int { + if v <= 9 { + return 0 + } + if v <= 26 { + return 1 + } + return 2 +} + +// DataBytes returns the number of data bytes that can be +// stored in a QR code with the given version and level. +func (v Version) DataBytes(l Level) int { + vt := &vtab[v] + lev := &vt.level[l] + return vt.bytes - lev.nblock*lev.check +} + +// Encoding implements a QR data encoding scheme. +// The implementations--Numeric, Alphanumeric, and String--specify +// the character set and the mapping from UTF-8 to code bits. +// The more restrictive the mode, the fewer code bits are needed. +type Encoding interface { + Check() error + Bits(v Version) int + Encode(b *Bits, v Version) +} + +type Bits struct { + b []byte + nbit int +} + +func (b *Bits) Reset() { + b.b = b.b[:0] + b.nbit = 0 +} + +func (b *Bits) Bits() int { + return b.nbit +} + +func (b *Bits) Bytes() []byte { + if b.nbit%8 != 0 { + panic("fractional byte") + } + return b.b +} + +func (b *Bits) Append(p []byte) { + if b.nbit%8 != 0 { + panic("fractional byte") + } + b.b = append(b.b, p...) + b.nbit += 8 * len(p) +} + +func (b *Bits) Write(v uint, nbit int) { + for nbit > 0 { + n := nbit + if n > 8 { + n = 8 + } + if b.nbit%8 == 0 { + b.b = append(b.b, 0) + } else { + m := -b.nbit & 7 + if n > m { + n = m + } + } + b.nbit += n + sh := uint(nbit - n) + b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7)) + v -= v >> sh << sh + nbit -= n + } +} + +// Num is the encoding for numeric data. +// The only valid characters are the decimal digits 0 through 9. +type Num string + +func (s Num) String() string { + return fmt.Sprintf("Num(%#q)", string(s)) +} + +func (s Num) Check() error { + for _, c := range s { + if c < '0' || '9' < c { + return fmt.Errorf("non-numeric string %#q", string(s)) + } + } + return nil +} + +var numLen = [3]int{10, 12, 14} + +func (s Num) Bits(v Version) int { + return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3 +} + +func (s Num) Encode(b *Bits, v Version) { + b.Write((uint)(1), 4) + b.Write(uint(len(s)), numLen[v.sizeClass()]) + var i int + for i = 0; i+3 <= len(s); i += 3 { + w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0') + b.Write(w, 10) + } + switch len(s) - i { + case 1: + w := uint(s[i] - '0') + b.Write(w, 4) + case 2: + w := uint(s[i]-'0')*10 + uint(s[i+1]-'0') + b.Write(w, 7) + } +} + +// Alpha is the encoding for alphanumeric data. +// The valid characters are 0-9A-Z$%*+-./: and space. +type Alpha string + +const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" + +func (s Alpha) String() string { + return fmt.Sprintf("Alpha(%#q)", string(s)) +} + +func (s Alpha) Check() error { + for _, c := range s { + if strings.IndexRune(alphabet, c) < 0 { + return fmt.Errorf("non-alphanumeric string %#q", string(s)) + } + } + return nil +} + +var alphaLen = [3]int{9, 11, 13} + +func (s Alpha) Bits(v Version) int { + return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2 +} + +func (s Alpha) Encode(b *Bits, v Version) { + b.Write((uint)(2), 4) + b.Write(uint(len(s)), alphaLen[v.sizeClass()]) + var i int + for i = 0; i+2 <= len(s); i += 2 { + w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 + + uint(strings.IndexRune(alphabet, rune(s[i+1]))) + b.Write(w, 11) + } + + if i < len(s) { + w := uint(strings.IndexRune(alphabet, rune(s[i]))) + b.Write(w, 6) + } +} + +// String is the encoding for 8-bit data. All bytes are valid. +type String string + +func (s String) String() string { + return fmt.Sprintf("String(%#q)", string(s)) +} + +func (s String) Check() error { + return nil +} + +var stringLen = [3]int{8, 16, 16} + +func (s String) Bits(v Version) int { + return 4 + stringLen[v.sizeClass()] + 8*len(s) +} + +func (s String) Encode(b *Bits, v Version) { + b.Write((uint)(4), 4) + b.Write(uint(len(s)), stringLen[v.sizeClass()]) + for i := 0; i < len(s); i++ { + b.Write(uint(s[i]), 8) + } +} + +// A Pixel describes a single pixel in a QR code. +type Pixel uint32 + +const ( + Black Pixel = 1 << iota + Invert +) + +func (p Pixel) Offset() uint { + return uint(p >> 6) +} + +func OffsetPixel(o uint) Pixel { + return Pixel(o << 6) +} + +func (r PixelRole) Pixel() Pixel { + return Pixel(r << 2) +} + +func (p Pixel) Role() PixelRole { + return PixelRole(p>>2) & 15 +} + +func (p Pixel) String() string { + s := p.Role().String() + if p&Black != 0 { + s += "+black" + } + if p&Invert != 0 { + s += "+invert" + } + s += "+" + strconv.FormatUint(uint64(p.Offset()), 10) + return s +} + +// A PixelRole describes the role of a QR pixel. +type PixelRole uint32 + +const ( + _ PixelRole = iota + Position // position squares (large) + Alignment // alignment squares (small) + Timing // timing strip between position squares + Format // format metadata + PVersion // version pattern + Unused // unused pixel + Data // data bit + Check // error correction check bit + Extra +) + +var roles = []string{ + "", + "position", + "alignment", + "timing", + "format", + "pversion", + "unused", + "data", + "check", + "extra", +} + +func (r PixelRole) String() string { + if Position <= r && r <= Check { + return roles[r] + } + return strconv.Itoa(int(r)) +} + +// A Level represents a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota + M + Q + H +) + +func (l Level) String() string { + if L <= l && l <= H { + return "LMQH"[l : l+1] + } + return strconv.Itoa(int(l)) +} + +// A Code is a square pixel grid. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row +} + +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1<= pad { + break + } + b.Write((uint)(0x11), 8) + } + } +} + +func (b *Bits) AddCheckBytes(v Version, l Level) { + nd := v.DataBytes(l) + if b.nbit < nd*8 { + b.Pad(nd*8 - b.nbit) + } + if b.nbit != nd*8 { + panic("qr: too much data") + } + + dat := b.Bytes() + vt := &vtab[v] + lev := &vt.level[l] + db := nd / lev.nblock + extra := nd % lev.nblock + chk := make([]byte, lev.check) + rs := gf256.NewRSEncoder(Field, lev.check) + for i := 0; i < lev.nblock; i++ { + if i == lev.nblock-extra { + db++ + } + rs.ECC(dat[:db], chk) + b.Append(chk) + dat = dat[db:] + } + + if len(b.Bytes()) != vt.bytes { + panic("qr: internal error") + } +} + +func (p *Plan) Encode(text ...Encoding) (*Code, error) { + var b Bits + for _, t := range text { + if err := t.Check(); err != nil { + return nil, err + } + t.Encode(&b, p.Version) + } + if b.Bits() > p.DataBytes*8 { + return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8) + } + b.AddCheckBytes(p.Version, p.Level) + bytes := b.Bytes() + + // Now we have the checksum bytes and the data bytes. + // Construct the actual code. + c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7} + c.Bitmap = make([]byte, c.Stride*c.Size) + crow := c.Bitmap + for _, row := range p.Pixel { + for x, pix := range row { + switch pix.Role() { + case Data, Check: + o := pix.Offset() + if bytes[o/8]&(1< 40 { + return nil, fmt.Errorf("invalid QR version %d", int(v)) + } + siz := 17 + int(v)*4 + m := grid(siz) + p.Pixel = m + + // Timing markers (overwritten by boxes). + const ti = 6 // timing is in row/column 6 (counting from 0) + for i := range m { + p := Timing.Pixel() + if i&1 == 0 { + p |= Black + } + m[i][ti] = p + m[ti][i] = p + } + + // Position boxes. + posBox(m, 0, 0) + posBox(m, siz-7, 0) + posBox(m, 0, siz-7) + + // Alignment boxes. + info := &vtab[v] + for x := 4; x+5 < siz; { + for y := 4; y+5 < siz; { + // don't overwrite timing markers + if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) { + } else { + alignBox(m, x, y) + } + if y == 4 { + y = info.apos + } else { + y += info.astride + } + } + if x == 4 { + x = info.apos + } else { + x += info.astride + } + } + + // Version pattern. + pat := vtab[v].pattern + if pat != 0 { + v := pat + for x := 0; x < 6; x++ { + for y := 0; y < 3; y++ { + p := PVersion.Pixel() + if v&1 != 0 { + p |= Black + } + m[siz-11+y][x] = p + m[x][siz-11+y] = p + v >>= 1 + } + } + } + + // One lonely black pixel + m[siz-8][8] = Unused.Pixel() | Black + + return p, nil +} + +// fplan adds the format pixels +func fplan(l Level, m Mask, p *Plan) error { + // Format pixels. + fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10 + fb |= uint32(m) << 10 // mask + const formatPoly = 0x537 + rem := fb + for i := 14; i >= 10; i-- { + if rem&(1<>i)&1 == 1 { + pix |= Black + } + if (invert>>i)&1 == 1 { + pix ^= Invert | Black + } + // top left + switch { + case i < 6: + p.Pixel[i][8] = pix + case i < 8: + p.Pixel[i+1][8] = pix + case i < 9: + p.Pixel[8][7] = pix + default: + p.Pixel[8][14-i] = pix + } + // bottom right + switch { + case i < 8: + p.Pixel[8][siz-1-int(i)] = pix + default: + p.Pixel[siz-1-int(14-i)][8] = pix + } + } + return nil +} + +// lplan edits a version-only Plan to add information +// about the error correction levels. +func lplan(v Version, l Level, p *Plan) error { + p.Level = l + + nblock := vtab[v].level[l].nblock + ne := vtab[v].level[l].check + nde := (vtab[v].bytes - ne*nblock) / nblock + extra := (vtab[v].bytes - ne*nblock) % nblock + dataBits := (nde*nblock + extra) * 8 + checkBits := ne * nblock * 8 + + p.DataBytes = vtab[v].bytes - ne*nblock + p.CheckBytes = ne * nblock + p.Blocks = nblock + + // Make data + checksum pixels. + data := make([]Pixel, dataBits) + for i := range data { + data[i] = Data.Pixel() | OffsetPixel(uint(i)) + } + check := make([]Pixel, checkBits) + for i := range check { + check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits)) + } + + // Split into blocks. + dataList := make([][]Pixel, nblock) + checkList := make([][]Pixel, nblock) + for i := 0; i < nblock; i++ { + // The last few blocks have an extra data byte (8 pixels). + nd := nde + if i >= nblock-extra { + nd++ + } + dataList[i], data = data[0:nd*8], data[nd*8:] + checkList[i], check = check[0:ne*8], check[ne*8:] + } + if len(data) != 0 || len(check) != 0 { + panic("data/check math") + } + + // Build up bit sequence, taking first byte of each block, + // then second byte, and so on. Then checksums. + bits := make([]Pixel, dataBits+checkBits) + dst := bits + for i := 0; i < nde+1; i++ { + for _, b := range dataList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + for i := 0; i < ne; i++ { + for _, b := range checkList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + if len(dst) != 0 { + panic("dst math") + } + + // Sweep up pair of columns, + // then down, assigning to right then left pixel. + // Repeat. + // See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm + siz := len(p.Pixel) + rem := make([]Pixel, 7) + for i := range rem { + rem[i] = Extra.Pixel() + } + src := append(bits, rem...) + for x := siz; x > 0; { + for y := siz - 1; y >= 0; y-- { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + if x == 7 { // vertical timing strip + x-- + } + for y := 0; y < siz; y++ { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + } + return nil +} + +// mplan edits a version+level-only Plan to add the mask. +func mplan(m Mask, p *Plan) error { + p.Mask = m + for y, row := range p.Pixel { + for x, pix := range row { + if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) { + row[x] ^= Black | Invert + } + } + } + return nil +} + +// posBox draws a position (large) box at upper left x, y. +func posBox(m [][]Pixel, x, y int) { + pos := Position.Pixel() + // box + for dy := 0; dy < 7; dy++ { + for dx := 0; dx < 7; dx++ { + p := pos + if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 { + p |= Black + } + m[y+dy][x+dx] = p + } + } + // white border + for dy := -1; dy < 8; dy++ { + if 0 <= y+dy && y+dy < len(m) { + if x > 0 { + m[y+dy][x-1] = pos + } + if x+7 < len(m) { + m[y+dy][x+7] = pos + } + } + } + for dx := -1; dx < 8; dx++ { + if 0 <= x+dx && x+dx < len(m) { + if y > 0 { + m[y-1][x+dx] = pos + } + if y+7 < len(m) { + m[y+7][x+dx] = pos + } + } + } +} + +// alignBox draw an alignment (small) box at upper left x, y. +func alignBox(m [][]Pixel, x, y int) { + // box + align := Alignment.Pixel() + for dy := 0; dy < 5; dy++ { + for dx := 0; dx < 5; dx++ { + p := align + if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 { + p |= Black + } + m[y+dy][x+dx] = p + } + } +} + diff --git a/Godeps/_workspace/src/github.com/vitrun/qart/gf256/gf256.go b/Godeps/_workspace/src/github.com/vitrun/qart/gf256/gf256.go new file mode 100644 index 000000000..ada96619e --- /dev/null +++ b/Godeps/_workspace/src/github.com/vitrun/qart/gf256/gf256.go @@ -0,0 +1,241 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gf256 implements arithmetic over the Galois Field GF(256). +package gf256 + +import "strconv" + +// A Field represents an instance of GF(256) defined by a specific polynomial. +type Field struct { + log [256]byte // log[0] is unused + exp [510]byte +} + +// NewField returns a new field corresponding to the polynomial poly +// and generator α. The Reed-Solomon encoding in QR codes uses +// polynomial 0x11d with generator 2. +// +// The choice of generator α only affects the Exp and Log operations. +func NewField(poly, α int) *Field { + if poly < 0x100 || poly >= 0x200 || reducible(poly) { + panic("gf256: invalid polynomial: " + strconv.Itoa(poly)) + } + + var f Field + x := 1 + for i := 0; i < 255; i++ { + if x == 1 && i != 0 { + panic("gf256: invalid generator " + strconv.Itoa(α) + + " for polynomial " + strconv.Itoa(poly)) + } + f.exp[i] = byte(x) + f.exp[i+255] = byte(x) + f.log[x] = byte(i) + x = mul(x, α, poly) + } + f.log[0] = 255 + for i := 0; i < 255; i++ { + if f.log[f.exp[i]] != byte(i) { + panic("bad log") + } + if f.log[f.exp[i+255]] != byte(i) { + panic("bad log") + } + } + for i := 1; i < 256; i++ { + if f.exp[f.log[i]] != byte(i) { + panic("bad log") + } + } + + return &f +} + +// nbit returns the number of significant in p. +func nbit(p int) uint { + n := uint(0) + for ; p > 0; p >>= 1 { + n++ + } + return n +} + +// polyDiv divides the polynomial p by q and returns the remainder. +func polyDiv(p, q int) int { + np := nbit(p) + nq := nbit(q) + for ; np >= nq; np-- { + if p&(1<<(np-1)) != 0 { + p ^= q << (np - nq) + } + } + return p +} + +// mul returns the product x*y mod poly, a GF(256) multiplication. +func mul(x, y, poly int) int { + z := 0 + for x > 0 { + if x&1 != 0 { + z ^= y + } + x >>= 1 + y <<= 1 + if y&0x100 != 0 { + y ^= poly + } + } + return z +} + +// reducible reports whether p is reducible. +func reducible(p int) bool { + // Multiplying n-bit * n-bit produces (2n-1)-bit, + // so if p is reducible, one of its factors must be + // of np/2+1 bits or fewer. + np := nbit(p) + for q := 2; q < int(1<<(np/2+1)); q++ { + if polyDiv(p, q) == 0 { + return true + } + } + return false +} + +// Add returns the sum of x and y in the field. +func (f *Field) Add(x, y byte) byte { + return x ^ y +} + +// Exp returns the the base-α exponential of e in the field. +// If e < 0, Exp returns 0. +func (f *Field) Exp(e int) byte { + if e < 0 { + return 0 + } + return f.exp[e%255] +} + +// Log returns the base-α logarithm of x in the field. +// If x == 0, Log returns -1. +func (f *Field) Log(x byte) int { + if x == 0 { + return -1 + } + return int(f.log[x]) +} + +// Inv returns the multiplicative inverse of x in the field. +// If x == 0, Inv returns 0. +func (f *Field) Inv(x byte) byte { + if x == 0 { + return 0 + } + return f.exp[255-f.log[x]] +} + +// Mul returns the product of x and y in the field. +func (f *Field) Mul(x, y byte) byte { + if x == 0 || y == 0 { + return 0 + } + return f.exp[int(f.log[x])+int(f.log[y])] +} + +// An RSEncoder implements Reed-Solomon encoding +// over a given field using a given number of error correction bytes. +type RSEncoder struct { + f *Field + c int + gen []byte + lgen []byte + p []byte +} + +func (f *Field) gen(e int) (gen, lgen []byte) { + // p = 1 + p := make([]byte, e+1) + p[e] = 1 + + for i := 0; i < e; i++ { + // p *= (x + Exp(i)) + // p[j] = p[j]*Exp(i) + p[j+1]. + c := f.Exp(i) + for j := 0; j < e; j++ { + p[j] = f.Mul(p[j], c) ^ p[j+1] + } + p[e] = f.Mul(p[e], c) + } + + // lp = log p. + lp := make([]byte, e+1) + for i, c := range p { + if c == 0 { + lp[i] = 255 + } else { + lp[i] = byte(f.Log(c)) + } + } + + return p, lp +} + +// NewRSEncoder returns a new Reed-Solomon encoder +// over the given field and number of error correction bytes. +func NewRSEncoder(f *Field, c int) *RSEncoder { + gen, lgen := f.gen(c) + return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen} +} + +// ECC writes to check the error correcting code bytes +// for data using the given Reed-Solomon parameters. +func (rs *RSEncoder) ECC(data []byte, check []byte) { + if len(check) < rs.c { + panic("gf256: invalid check byte length") + } + if rs.c == 0 { + return + } + + // The check bytes are the remainder after dividing + // data padded with c zeros by the generator polynomial. + + // p = data padded with c zeros. + var p []byte + n := len(data) + rs.c + if len(rs.p) >= n { + p = rs.p + } else { + p = make([]byte, n) + } + copy(p, data) + for i := len(data); i < len(p); i++ { + p[i] = 0 + } + + // Divide p by gen, leaving the remainder in p[len(data):]. + // p[0] is the most significant term in p, and + // gen[0] is the most significant term in the generator, + // which is always 1. + // To avoid repeated work, we store various values as + // lv, not v, where lv = log[v]. + f := rs.f + lgen := rs.lgen[1:] + for i := 0; i < len(data); i++ { + c := p[i] + if c == 0 { + continue + } + q := p[i+1:] + exp := f.exp[f.log[c]:] + for j, lg := range lgen { + if lg != 255 { // lgen uses 255 for log 0 + q[j] ^= exp[lg] + } + } + } + copy(check, p[len(data):]) + rs.p = p +} diff --git a/Godeps/_workspace/src/github.com/vitrun/qart/qr/png.go b/Godeps/_workspace/src/github.com/vitrun/qart/qr/png.go new file mode 100644 index 000000000..9a45f0771 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vitrun/qart/qr/png.go @@ -0,0 +1,401 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package qr + +// PNG writer for QR codes. + +import ( + "bytes" + "encoding/binary" + "hash" + "hash/crc32" +) + +// PNG returns a PNG image displaying the code. +// +// PNG uses a custom encoder tailored to QR codes. +// Its compressed size is about 2x away from optimal, +// but it runs about 20x faster than calling png.Encode +// on c.Image(). +func (c *Code) PNG() []byte { + var p pngWriter + return p.encode(c) +} + +type pngWriter struct { + tmp [16]byte + wctmp [4]byte + buf bytes.Buffer + zlib bitWriter + crc hash.Hash32 +} + +var pngHeader = []byte("\x89PNG\r\n\x1a\n") + +func (w *pngWriter) encode(c *Code) []byte { + scale := c.Scale + siz := c.Size + + w.buf.Reset() + + // Header + w.buf.Write(pngHeader) + + // Header block + binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale)) + binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale)) + w.tmp[8] = 1 // 1-bit + w.tmp[9] = 0 // gray + w.tmp[10] = 0 + w.tmp[11] = 0 + w.tmp[12] = 0 + w.writeChunk("IHDR", w.tmp[:13]) + + // Comment + w.writeChunk("tEXt", comment) + + // Data + w.zlib.writeCode(c) + w.writeChunk("IDAT", w.zlib.bytes.Bytes()) + + // End + w.writeChunk("IEND", nil) + + return w.buf.Bytes() +} + +var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/") + +func (w *pngWriter) writeChunk(name string, data []byte) { + if w.crc == nil { + w.crc = crc32.NewIEEE() + } + binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data))) + w.buf.Write(w.wctmp[0:4]) + w.crc.Reset() + copy(w.wctmp[0:4], name) + w.buf.Write(w.wctmp[0:4]) + w.crc.Write(w.wctmp[0:4]) + w.buf.Write(data) + w.crc.Write(data) + crc := w.crc.Sum32() + binary.BigEndian.PutUint32(w.wctmp[0:4], crc) + w.buf.Write(w.wctmp[0:4]) +} + +func (b *bitWriter) writeCode(c *Code) { + const ftNone = 0 + + b.adler32.Reset() + b.bytes.Reset() + b.nbit = 0 + + scale := c.Scale + siz := c.Size + + // zlib header + b.tmp[0] = 0x78 + b.tmp[1] = 0 + b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31) + b.bytes.Write(b.tmp[0:2]) + + // Start flate block. + b.writeBits(1, 1, false) // final block + b.writeBits(1, 2, false) // compressed, fixed Huffman tables + + // White border. + // First row. + b.byte(ftNone) + n := (scale*(siz+8) + 7) / 8 + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + row := make([]byte, 1+n) + for y := 0; y < siz; y++ { + row[0] = ftNone + j := 1 + var z uint8 + nz := 0 + for x := -4; x < siz+4; x++ { + // Raw data. + for i := 0; i < scale; i++ { + z <<= 1 + if !c.Black(x, y) { + z |= 1 + } + if nz++; nz == 8 { + row[j] = z + j++ + nz = 0 + } + } + } + if j < len(row) { + row[j] = z + } + for _, z := range row { + b.byte(z) + } + + // Scale-1 copies. + b.repeat((scale-1)*(1+n), 1+n) + + b.adler32.WriteN(row, scale) + } + + // White border. + // First row. + b.byte(ftNone) + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + // End of block. + b.hcode(256) + b.flushBits() + + // adler32 + binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32()) + b.bytes.Write(b.tmp[0:4]) +} + +// A bitWriter is a write buffer for bit-oriented data like deflate. +type bitWriter struct { + bytes bytes.Buffer + bit uint32 + nbit uint + + tmp [4]byte + adler32 adigest +} + +func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) { + // reverse, for huffman codes + if rev { + br := uint32(0) + for i := uint(0); i < nbit; i++ { + br |= ((bit >> i) & 1) << (nbit - 1 - i) + } + bit = br + } + b.bit |= bit << b.nbit + b.nbit += nbit + for b.nbit >= 8 { + b.bytes.WriteByte(byte(b.bit)) + b.bit >>= 8 + b.nbit -= 8 + } +} + +func (b *bitWriter) flushBits() { + if b.nbit > 0 { + b.bytes.WriteByte(byte(b.bit)) + b.nbit = 0 + b.bit = 0 + } +} + +func (b *bitWriter) hcode(v int) { + /* + Lit Value Bits Codes + --------- ---- ----- + 0 - 143 8 00110000 through + 10111111 + 144 - 255 9 110010000 through + 111111111 + 256 - 279 7 0000000 through + 0010111 + 280 - 287 8 11000000 through + 11000111 + */ + switch { + case v <= 143: + b.writeBits(uint32(v)+0x30, 8, true) + case v <= 255: + b.writeBits(uint32(v-144)+0x190, 9, true) + case v <= 279: + b.writeBits(uint32(v-256)+0, 7, true) + case v <= 287: + b.writeBits(uint32(v-280)+0xc0, 8, true) + default: + panic("invalid hcode") + } +} + +func (b *bitWriter) byte(x byte) { + b.hcode(int(x)) +} + +func (b *bitWriter) codex(c int, val int, nx uint) { + b.hcode(c + val>>nx) + b.writeBits(uint32(val)&(1<= 258+3; n -= 258 { + b.repeat1(258, d) + } + if n > 258 { + // 258 < n < 258+3 + b.repeat1(10, d) + b.repeat1(n-10, d) + return + } + if n < 3 { + panic("invalid flate repeat") + } + b.repeat1(n, d) +} + +func (b *bitWriter) repeat1(n, d int) { + /* + Extra Extra Extra + Code Bits Length(s) Code Bits Lengths Code Bits Length(s) + ---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 + */ + switch { + case n <= 10: + b.codex(257, n-3, 0) + case n <= 18: + b.codex(265, n-11, 1) + case n <= 34: + b.codex(269, n-19, 2) + case n <= 66: + b.codex(273, n-35, 3) + case n <= 130: + b.codex(277, n-67, 4) + case n <= 257: + b.codex(281, n-131, 5) + case n == 258: + b.hcode(285) + default: + panic("invalid repeat length") + } + + /* + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 + */ + if d <= 4 { + b.writeBits(uint32(d-1), 5, true) + } else if d <= 32768 { + nbit := uint(16) + for d <= 1<<(nbit-1) { + nbit-- + } + v := uint32(d - 1) + v &^= 1 << (nbit - 1) // top bit is implicit + code := uint32(2*nbit - 2) // second bit is low bit of code + code |= v >> (nbit - 2) + v &^= 1 << (nbit - 2) + b.writeBits(code, 5, true) + // rest of bits follow + b.writeBits(uint32(v), nbit-2, false) + } else { + panic("invalid repeat distance") + } +} + +func (b *bitWriter) run(v byte, n int) { + if n == 0 { + return + } + b.byte(v) + if n-1 < 3 { + for i := 0; i < n-1; i++ { + b.byte(v) + } + } else { + b.repeat(n-1, 1) + } +} + +type adigest struct { + a, b uint32 +} + +func (d *adigest) Reset() { d.a, d.b = 1, 0 } + +const amod = 65521 + +func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) { + // TODO(rsc): 6g doesn't do magic multiplies for b %= amod, + // only for b = b%amod. + + // invariant: a, b < amod + if pi == 0 { + b += uint32(n%amod) * a + b = b % amod + return a, b + } + + // n times: + // a += pi + // b += a + // is same as + // b += n*a + n*(n+1)/2*pi + // a += n*pi + m := uint32(n) + b += (m % amod) * a + b = b % amod + b += (m * (m + 1) / 2) % amod * uint32(pi) + b = b % amod + a += (m % amod) * uint32(pi) + a = a % amod + return a, b +} + +func afinish(a, b uint32) uint32 { + return b<<16 | a +} + +func (d *adigest) WriteN(p []byte, n int) { + for i := 0; i < n; i++ { + for _, pi := range p { + d.a, d.b = aupdate(d.a, d.b, pi, 1) + } + } +} + +func (d *adigest) WriteNByte(pi byte, n int) { + d.a, d.b = aupdate(d.a, d.b, pi, n) +} + +func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) } + diff --git a/Godeps/_workspace/src/github.com/vitrun/qart/qr/qr.go b/Godeps/_workspace/src/github.com/vitrun/qart/qr/qr.go new file mode 100644 index 000000000..5660b4c6d --- /dev/null +++ b/Godeps/_workspace/src/github.com/vitrun/qart/qr/qr.go @@ -0,0 +1,109 @@ +package qr + +import ( + "errors" + "image" + "image/color" + "github.com/vitrun/qart/coding" +) + +// A Level denotes a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota // 20% redundant + M // 38% redundant + Q // 55% redundant + H // 65% redundant +) + +// Encode returns an encoding of text at the given error correction level. +func Encode(text string, level Level) (*Code, error) { + // Pick data encoding, smallest first. + // We could split the string and use different encodings + // but that seems like overkill for now. + var enc coding.Encoding + switch { + case coding.Num(text).Check() == nil: + enc = coding.Num(text) + case coding.Alpha(text).Check() == nil: + enc = coding.Alpha(text) + default: + enc = coding.String(text) + } + + // Pick size. + l := coding.Level(level) + var v coding.Version + for v = coding.MinVersion; ; v++ { + if v > coding.MaxVersion { + return nil, errors.New("text too long to encode as QR") + } + if enc.Bits(v) <= v.DataBytes(l)*8 { + break + } + } + + // Build and execute plan. + p, err := coding.NewPlan(v, l, 0) + if err != nil { + return nil, err + } + cc, err := p.Encode(enc) + if err != nil { + return nil, err + } + + // TODO: Pick appropriate mask. + + return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil +} + +// A Code is a square pixel grid. +// It implements image.Image and direct PNG encoding. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row + Scale int // number of image pixels per QR pixel +} + +// Black returns true if the pixel at (x,y) is black. +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1< 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += a64 * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, (uint64)(n)) +} + + +// ResizeNRGBA returns a scaled copy of the RGBA image slice r of m. +// The returned image has width w and height h. +func ResizeNRGBA(m *image.NRGBA, r image.Rectangle, w, h int) *image.RGBA { + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + pix := m.Pix[(y-r.Min.Y)*m.Stride:] + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + p := pix[(x-r.Min.X)*4:] + r64 := uint64(p[0]) + g64 := uint64(p[1]) + b64 := uint64(p[2]) + a64 := uint64(p[3]) + r64 = (r64 * a64) / 255 + g64 = (g64 * a64) / 255 + b64 = (b64 * a64) / 255 + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += a64 * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, (uint64)(n)) +} + +// Resample returns a resampled copy of the image slice r of m. +// The returned image has width w and height h. +func Resample(m image.Image, r image.Rectangle, w, h int) *image.RGBA { + if w < 0 || h < 0 { + return nil + } + if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { + return image.NewRGBA(image.Rect(0, 0, w, h)) + } + curw, curh := r.Dx(), r.Dy() + img := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + // Get a source pixel. + subx := x * curw / w + suby := y * curh / h + r32, g32, b32, a32 := m.At(subx, suby).RGBA() + r := uint8(r32 >> 8) + g := uint8(g32 >> 8) + b := uint8(b32 >> 8) + a := uint8(a32 >> 8) + img.SetRGBA(x, y, color.RGBA{r, g, b, a}) + } +} +return img +} + diff --git a/auto/gui.files.go b/auto/gui.files.go index 73fec608b..abd51e490 100644 --- a/auto/gui.files.go +++ b/auto/gui.files.go @@ -18,7 +18,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["angular.min.js"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ffd43c6b53dc36bbdff32b946d5a7b93c54b72cee99c81904e4bd21e4e6e4c48f285f2c1bb16ac8357dec8726087f2dfdfe791645bb264af81a44d9979df822ecf5dcf4d72a60f3f1559ca0499f1fca2a07c87085ed20999e74ca4aca4d5dfabac2cf07fea6ff2707a6ffaf02ccb6771461eec90d3382b6051ccceca2ce6fa6f5c742f28e19742f0742e82dd7bf7bec49c146b36178b949d91bd6a47b4cc9332a36150cf0513727c32de951b4a9ecd6200b347024e0b09a75e1721a13ccf32cac3e0a81add173c0308a725fc9de68c840f8a79be020a1f2c84588dc9d53d023f087bc5e997e7b140e0dbbbf5e819156f5fc210f2da8c22f2980b45b964b199cbb3e4ed0a71153077750d24e28c428b34323a3767edc9d3f4cc1d5fae0f9e23cb8135caf2842290e3130f90038612b0e8d6f394f39c7bf61594b21738e7620295d0cc258bd3556ef1389d9223d02f3b2bc88c9ee69c92599e6705c9f2fc1c4684a0dcc628845c0cb4c8f1ab34d921c1abb410940120501b7038072392ea24873c17f93ccf885a417e4d12d043410b5828d62b30d040d04b017f69f5280bbd9e18c05fc7974794252f67abc200ffb61467392af31deaff55ba4c05095fa6bf4d8b71039b95cb19e5bdd0df01bc981d3060f44b9c1d1918d40ca9a6487863c8da72fcc0f5e4ede11fc63c869393bda39f4b58640a0744464040b093255246cd92e1f001c8fe028e386d491e81ff9e6694a859a580e1a23730fc215dd0af8cbd60f12ca38981444d1198cbc10dd0062e9a672fd9aff279074c39731b904738fe9b72b1a685e33069c64d70d6fe0f87ecd025478d109cf46f3eb1fdd059991ef98f9f3e55a6f83e1cdce5c87db0194568bf96624121aacc63e9933f582c6f04781817c545ce937ea0c62a0d78d58cf452fbfe9579b86080fcdffbf78747043c1a014483745d89bb8e3b10478ecaf99cd28426611576f0273d25e17d1965cc514355294b4538deb5a7c2e007460570732e7d763046271d6761b048131ab456bb310c7fae2d129a887643329a8dfd447485cc86946b5764bfc7e01b5c797513cba92839eb62d22fe62e595ecde2f979c2f315a81af0816581b2cfe97a96c73cd199cd7587a8fb18acc3e729f0b1c0c5757262d226139408008655d6f38804d3620d0771093416684d4511369b9358c46de66cb3dbf5295641043270bf41f258a50aa19f3a0dbc5290b9aff9fdedec1344a50864568466d2308ee02cbd88e70b03789a388af10a402623bf2098bd0006289bc3c0877707fbf972953370000869a8780c3148b8c76972e248a2cd95f9bb9f4623cb1bac294c1d597e01d8310985ecee221c4fac152281c910d76cd5c9ea984cc9e3eded6d7b659a68f753fd18b92decf79a41ca203237b96f6b362f453d6dcda35304899394912e014b0f8793d1222ede5e30c8e356948bb55494673dfe5445c7ae337bed8c08beee80824851a53573af63b18896f165b83d21ff4b1e2a4dc81507ecb7b5a0c5fb5c4058dff2a4ebce2a14bd48c62e8516e65a70dda821b91a82db5ad689fc9a40fc9b2f4848bb44eb08657b280b9e85ae362c737ab4d742e742b00dccdca0865ae7d0679c7651d576639b0eab2a88069fd37619e547d72afbd05d418627cac272f738dc8e6d985be4a7b657c275e097f6a0322b59424f5306c99f3ff441c2c2cee18cb3c08c3e16061734e8068a0638c5f7114527e42391af5680d90b59d6f342f998003db3078d9c6f7657cbe57f8fb74f22917f00047c1f5403c1e6919a00bdcc0ac1c3c7e35d9b0fbd7d8f8cb03c84646044fefa8b34a3074946476d5ed43498d98884a3864aec221c523e87f8119f51ad984764f4e378e4e5568bc460a81ddc57f97e0609e7b7d777ca4ef36fa0ec04eb31fed5746d292c4841359d98f5390cbab2b8064ca1f4de0969c5d365ccd75e481ef1d92ab44de29beb11a27817c31ee9aa869b8c0712fef630a896065773015cc10a88465e33c163a5504c7d0b0c1a76db6295d1ee34cb217f04347ea7880d2c8f53c4e1fd532bb3476ad1c9c33a4f70d41ba237f0df83e727b6cde1c2b66caaf10873c68c4ab4284410852f68569672c0084a2468452442b1c3d9bd4d3b2712e24169a38500f4e3b80db1c7e104cfd342f35e3b628f580fe6c8d2bf41a8f9f94de5a9eba69b086d09a964d12d2dd7517fb7e2727de32099b98e70a3cc4cbfe88a0c1b417fb3c434615268ba0fd5e7d77fe9d1b721f27f8c859613e88d51ddac7ca41bd580c4d8944a4333ee163ac8d42bbe505e006c1f7ddf523c590a81f76337ee0d7a86589b2062473a36bbc8c1120b26799562deac00844c50b3fd81f4566859234aa2a0ee5aa506b2a8e1461965676201e91679dcc1719d1cf430aaa1419adc690c6fe2a5cb6f9f35c0fa0e8acc253d44d92aae3275a8707ff6877cb1488b373e4adb3a614a75ad6d614bc23ae7528b076559a3b054e591dc6427f76d116d92cf10e10c960c4d526174e4fd92b1ae16ab0b53d8be0eadcbbf482f324bdf30f8a1ba6f1bdae094edecb137358ebfd08dc44ea790059e5352941cfe2f5f5275d33b97173d8921edf07ecd0bfd5cc65911367c4e889fb3b1b7286fdd7db69ab0cec20a5aa46e377450b19c99bbea48f0a85865a90883094a325e192ee2d27011979180a80b264bda4d62d58758e585d3350474a082ff3f7afb262ae44d6a7adad2ed7842ae16344ec031ee90ab603f674093d87a0f27210035c62b204ddd804c3f15390baeadce65fd5bbf618cb0813fea6a65c8de7ba7daad3ebf7de3d0755d502cf28bc0e9d338f2d15bad951beebc5bb4178b522478f8fb7bee0ee66a9fb749e47418a8789f2e695e8ad0eef487a0bb27ffb3ddd549be192be82fbcf1ade5ef2bb025e760277ac783885e82d924e1d5f5a4f2e12e298802f4f4e212ecdeab4e6bd911cd4e557fba956a589986cb6f435854df2ec219237b7570a987a34f79cae0d8118f09e0e2174049cea307a081432e89b622069a5f25b65bf9405b0371e226181b257f65f2082893358b9780f37a80f45beecc2b7eff9abf5f380985dc96f6cbc787d1b93a9497077e916cbefbf3c961589ee78df9954ddf6f8cda345f353be074cb956d3a8ce6a6bc53c12c48ddaba82b337f1b5acec96e790b666b62009777e0b4cdad2bf9fec8fc8f04c4766c809ca6d75eabb4145cd2842439a31392eede94d14136afb1109fec9d55b5a36d27e36001593ca7e1944c81e000d0d5235bd588ce501c98b597f2b9e15b66403512949d2b1a7591286f99484a9eda6754955030f1e891af9162ae3d4e4fcc32d7164acffd6fb5b9e178d759aa29b7c360f533e3343e1fd06051d7a100a9e3482b8e5725240c6e68eef0695191731156bd8d98d3dbba9eefe124e6624179456757926976283afdf860378eb989ebb83b8ad75e0f914242d576e5b7b06ad6521118e6ae63f66c603fc764bedb403d1cc759d6af87ca271a16d568afed58b4513b35bdb1cca89c0b7fda8bd7ae58880d340d754debda46575d4f23ccddc933e755ec46eb986734e62faa5be1de94d07c6a6bd1796c53ad1b475be4f189246b63c094fba69292c04fe5294f21efcfd6ae5ae1509ba43639c8cd8d57b605cdf8e537e14266f9f0ff75609ad78e7ad2ee6be1dcd85bcad657b1bca35e5de5430ca65a673f54f2753f3080bf83d99b955e7a47537a159022e32d8e540538ccebbe5acc00522574ce0b2a27a3f3ecb5901e570ee4c47d1c78eb7210e536b0d6408abe4e21e6a863a31e3ce2bf7509f6f7b38cc96a2fcfea238555fe3592558b662759d5589c64157739abeaf4e3f8c49db36dd38867ae79d6de49f5deab2ac981e24b16fde8587510f6142e7f0c359850c1ec4a1da01d4d48bbd3d78eaff8a34a633fbdae8a541d572d56b2d07f75a57828f902ad3195a2ee726cdf55cea784d27f8c0799e3d7e917681d593af07853eb56eb8e4af8b7d4c9f80edcd0d18027cbfa02f1a62fecf436ef13bb0d186ff848ba7ef0e37d086dacd01f62c9b7ac6ec9b08128ada51b12557f13d64954d72dc5867b0c5dce5b3dd5ce72d481f5a6c9d07deb7df56867fb4a1f8fd7f12aec3e439d00746bfd16aa98e20bafdbe9a33ea0d214ccb18d7582f91d05240bd5175badbb82897cd58d7705929d9a3443a4613c21b38a4ae3363496f792ea755f731f4a7efa89e80533ef82f625b106f3542f1ffbaaa5adc74ee9a6b73dd3dbaafcd980aa22b684ebf4645a70d5be06b2dafaacde0ad2316423c362876ce2e879ca21cee67c2d51d77f0dc7de0078660270694043cecc2f4b97c6b793992771b70aafe531afc2bc26431b4bf50ec0452803cbd2c498192956ab9bbc3491652a8b5962a3786c309da9036c48d42622b38948e83c5de2c52d18321431a5454b929ea5a2c00faae6557e831ac1af149d978b1afcb655762900d5137af5a850fe9ae567ea977826518ff1557c3df378bbaa171173eb053ed00875b5826cb3a6c8bc363f2cd6dd8380c5b022b63f27aea8d7bb8def59d8aa1413229fda7a5894d391c87f4f2f6912d602b476350e443a0197a059caf055d7707ada875c0e4a35d48ea0eb2ded36e97c90aba03c0397f5e4bfc943f33f4efe2d574ef73c4b777d58fba5f4648c6fb403f2477a13d286d07407625e0f23a6978a3ba07fe9476f3ecbe53928bbb207dc136cb2b325c5cff6bf233b936f968dfff448b3bdf42e767613ca86907417331b444b2f1177c07efe0d8cac5840c8b9a58d59c47bdf5a75208db38b785dbca9be2ffff6f6ed7de46f92bf89e2f9a264e707cfbf32b13e5247c6eb38f9bd40cc6518565296ef11c36974f578f2f3f5f4ac5d82cbc51bc1ea21b95857225b75e7bad7500ec1c8fe7119a85bd0e9f19fd33fff3c997a45809f2429f67437ffe91ef9af0edb30f4efb58f51144553fc6849012ca060a7a1097cebc9584971341d75493191896bfa8586412e0b42d5b9ec93a579ced43fe0b24382fd60620ccbfebdfe1762ea61c16356ccb332716664adb3e33e9cc2360dc0ded3bf1a28ae0db07409e8047e81ff54e9110b9bbd11fec30123c2ceb6e4472a7b23bbec3dd63023487547cf9e4ee5ce671a837bdc0c31952cfd5ccade9421a43e197d2e6133feb31567af9112838b2c65e73b0d0c298609a1d9724262213864c973c1b3b679e058f400145d505e44252b16e9a9a8eb87f04b4a2f3ec699bf67293f981ad20dab7ea6537241099cb1324b5820542a0ad2f62e569481643fe2aa54ac5bd2929dd45d67abfe5ea1a14d75d86a464e7a88130bd53b25141929489c711a27eb5b9127db6bddf46da6212d8802f855a5e38c681babe5d36a323bd762da9cff030000ffff010000ffff6965c7dcd2490000") + bs, _ = hex.DecodeString("1f8b080000096e8800ffd41cdb72db36f63d5f81a869492532e574773a3b769c4eeba45d6f6e9e38c98beb074a8424c614a800606c8dab7fdf830b498000295a4eda54336d24e2e0dc712e00e8f1c38f2c4b0947139a5f314c0f10a7051ea1694e784a0a5cfe5e650513ffa9dfe8e1f8def8e13ccb2771861e1ca0599c31008ac9bcc862aa7f0ba07b41015f18a7e9940787f7ee7d8e29626b32e58b94ccd15139235ae64991e130a8c682113abf181eca0905cd2631a0394201c54ce2a9e022c128cdb30cd330382b9f1e739a01865901bfd39ca0f0019be62be0f0c182f3d510dddc43f011b857147f7e1673817cffb07a3ac7fccd0b782464ad9f0ae231e58a7329623d9667c99b95a0c560ec66032c8a114556f048f0d41cb50767e9dc7dbe5c9f3c132207d6539227582039bff0203921420316df7a1c539a53cf3c8631792ec65c4a60129cb96c51bcca2d19c7637406f6257386267896538c26799e3194e5f9253ce11c539b22e712187891cf6fd2e400052f53c6310144603690700a4e24cd894e69cef3699e2105817e4912b003c30c00f97a050e1a707ccde197368ff2d0cdc840fe2abe3ec3247931593103fd9b82cf7361ccb7c2fe2fd365ca51f822fd75cc86356e522c279876627f0bf862724240d0cf717666505023a81c42e1ad316bcff123d783bbe33f8d690c2b277b8b3f1500642a0754864041309324524735487ffc80e478014b1c37342f90ff966618a9516580feaa3728fc2e43d02f843c27f124c38941440d2118cb210ce01aaf70cf4eb65fe6d3169c7264179467e2f9af2ac49a1e2e1ea3fab989ce9afffe949cbaeca827480cfa275fd871685ea467fee5a75795a9bef727775972ef6d4105b65f0abec09055a6b18cc9ef2d91b7223c8d19bbca69d28dd480d28857f5934e6edfbd3417173c40ff7df7eef40c41444340a897ad4b75577907f2c859319d629ce0242cd38ef8a43314de9759c67c6a982a25290f8787f650187c473007692e65cc0e862248c759182cd204070d68378789cfc662a1ce68b764a39ed8cd445bcaac59d9b82afb2d86d8e0eaab9d598a7941499b907e35b7e9f266124f2f139aafc0d4400f3c0b8c7d89d7933ca689ae6c362daaee12b04a9f3390632180abe2c4e44d162811200ccbaae7110ac66c0d0b71093c32e14d8c85f5e424e6715338dbed0e7d865518810d31df6079a84a85d0cf9d465e1ac89c577f7f33f9085929029db1d02c1a8611aca5e7f17461204f13c7305e05c862e46781e6288007984ce1c1fbb727c7f9729513080002535ff5186a9078cfd3e4c2d144532af3bb9f47a3caeb6d29513a92fc0aa88b2214aabbab7038b220780283a180d9ab8ad5211aa3c7fbfbfb36649ae8f0537e8cda16e67bdd20259099ebdab7319a17bc1ab6c64550048da394a03605cb082706a345ccde5c11a8e35698f2b53494075e7ccaa6e3d019dd384f385db760114485492be15ec57c112de3eb707f84fe831e2a4b488813f2eb9a63f62ee790d6f73ce5ba032554cf93a1cba145b9525c3b6928aefad0b6c05a896f10e4bfe90285b84db58e52f6fb8ae00174ad61b9d3a3a306391783ed60e604f5a8b10e7dce693755cd30b66db1aa86a8f73a6db6517e728db64f842ba8f078c1ac702f1e37739ba82df2991d95041cc4a523e8cc0a92e0594aa0f8f3a73e2858c825ac711298d9c7a2e0a206db40d300abf8be20d18af98ce7ab1550f66296fd3c5731261091d943468ed7b34b70f9eff9fe45c4f3f740801e836920d93c5203609709e3347c3c3cb4e5d0d38fd040b487500c0cd09f7fa2fae94992e1415316350c6e3640e1a0e652ec229c623a85fc11cfb136cc2334f87e38f04aab556208d44ceeabfc388382f3ebdb3b25b3fc2b183b11fd18fd62b6b60c16a4609a56ca7a1d066d555c8d8629bbb7625ad17419d3b51793477db6096d97f8ea76842cde26b047bb6ac34de603897fbf1f56cb82ab2907a90002b291d74dc4b25224c63e008387c3a65a65b69b6539d48f40c61f14c5069627288ac7c733abb217dc8a200f709ee4a82744afe1df936717b6cf09c0a66ecae791a819332cc90a25822a7c49b3f494138284468246464258ec70b64fd3c1098562a134c94202fa7ed8c4d81170826729d3b25781d8a3d693a910e99fa0d4fcf2b6fad47dd36d94b6845292b56bcb0dd4dfacbadcd8d84b676e20dcaa33332eba2a131b417fb1c6346352697a1faa2baeffdc616f43e57f9b088d20d099a3da45f980b79a413063732a1dcd385b686153437cc694016e1f7f5f533d590a89f7433bed2d76865c9b08c28e766c7185044bd130c9a314f3640530641c9bdb1f82df922ca9558914d643abd510226abc5186c99c2fa0dc428f5b24ae8a830e41353628935b9de175bc74e5edf206806fe1c804e960ca367159a94387fb933fe5f345ca5efb386dda8428d335a6850d0deb9a4b01f7aab20661a1da2339c92eee9b2adaa69f3ecae9ad199ca4dcd891f76bc63a5a2c0f4c61fa3ab40eff220d64b6be61f05d79ded67783536e670fbda571fc196f65763c862af012235650f85fbec4eaa4772a0f7a1243dbe1fd4a16fca9883316d6728e905fb2a1b7296f9c7d3636611dc0125ba44e377452b182990b75c669c45659cac360243419af8c10716d8488eb8843d6059745cd4d62b50fb1ca99b36b08e4c004ff3b7bf33a62f224359d356c3b1ca19b058e13088c07e82638ce09f0c4f7dec14a08c08cf10a58532720e38f2c27c1c6dab9acbe753bc6406ce00fdab632e4de7babd9ad7d7efbc4a1e5b8e0b6fbec2dead398cd73876d47e20dd1d8a2e089880dbbc8e667aa44e9dd5ef29d44181376d3ce6677f9450cf2e6cc460e29d1169482efe9190f227c0dae9884379b5199175c56040950dff36b584b5e355a6067389ba93def46f962552faebc356351756209eb161d5509ab7a1c7dcc53024b1979fc46003f074e721a3d8035724a25d3561612262bd5b6535cb52d90ba358ba578a096269f8ce33eb6c8af023fae38d982cc67c51b535fc07eb226f112f8dff4b06423dc7a4de987f9eb159d60a8bdf156653b149da34d79b8e157c9f6b3499f1efad5a1de9aa45c1ff7eb05622e0535da235248c8261fc6e6ab3cf311559a3af751477afe6d72392677f31b381b033da4bc83a44d695dcd77570e7f4bc26e2627a8b93afdb52c9b21bc8d5092133c42e9e16d05ede5f39a0af2e9de81aa8276b359000fc8e2290ec7680c0c0740ae7ab2573ed1159483b38a52be90be6385561111ba7355a30e3ae529184ad1137b8daa160f061e3df26df498b0e7e985d986db4ae9389f2e27d7121f3aa09a733ba5969f09c5f1658f0d20755c0b985a96b4927855b045e8a6f9969816b19cf2b0dc7b8929de35f47c0b2b31e70b4c4b3edb0a457307a5358ef70ee3a2ce7103774b73dd19215228ce9aa17c07af260d1381631e3a6e4f7aee3799c2b73ba847e238cbbaed50c644c3a36aeb35038b766a67cfc100333a7be62fa1c5b1b068147bba863a46767da36ddf0147efd225464f9d5bbb5bbd639ae1983e2f4fad3b4b42f32ab0c5e7b9cdb5ded8da438f2f245b5b13a69c37969cb414ad339a420f91ad5db3c2a23659ad6b90db3bafdcb634f397df8599ec18e0ff55629a56817ad4dc771363436fab5d1d15d3967e7a95f7719812cebe48e5db9d1109fc2d8cdeae8dd333ea368e41892c4e99a42920606ebafa3a034959d03937bc9c8ace33d7227a5e06900bf7f2e2ceada5d05bcf5e4370f4057a0d68c41c736cb58347fd3bb7607fbdc8a258ed9459bd44b1cabf44b16af1ec14ab9a8a53ac8a590e54557e9c5fb863b66f1af9cc75cf2a3aa9b381b24b72b0f88a453f39522e842345cb9f430d215432bb510be84033d2dc896ce657f151adb19f5fd744aa8f2b81952ef4afb6124f689e096f4ca5aadb02db3755f329a5742fe35eeef865f60bb48d2c1b78a2a975ea764723fc53fa64714fddb0518f2bd5fa80f3b63700f534ef15c02d146f7989bbba90e4bda86d40e817c5e45d5bb765d8c294b6d22d99aade596b65aaed1465cb398b6ee7adfdd9d676d4c1f5baaed07df0be7eb475fb4a2f8f57f12a6c5f43ad08e46595e605f95ea6188b1b68bbd9a35aa0d215cc675bfb04f33d0f2816ca37ca425ba091bc75be0f70529c8a3543a5613c4293924be3b43696e7a6eaf6617d5e8b7ef80169808917a07988add13cd1e0435fb7b4f7d869ddf4b4a77a5a593f1b5855c696789d3d99065e35afc6aca63eada682760cddc8b4d8a29b387a9652c8b3395d4bd2d5affed46b044f4d042e0fc29133f3cdd7a5f16e67e629dcadc66b794ecb34afd9d0ce52de537009cac4b23429664689d5d84d5e9ac43255c52cc546f1d0103a530bd8d0a8cd44663391e069ba1407cbe0c8d0c414162f493a4f39132f7c4dcbfa465844bc45e9dcacd4e8f7adb64b2128aff8ab4b8ff26b96cfd5977822490fc5adfd6ae4f17ed92f0aca8d37048047e8ab15665b34c5e6c67cf159ef1e04240688d87eddb9e45ecf36deb721ab828f90bc0aec11510e473cff2dbdc6495829d09a55071019045c86262911b7cefaf3d35ce4f2a134431508daeefaeea3d60bc30acb5308593ffe1b3d34ff71ea6f09393ef2801efaa8766be9c7a1b8431ea0dfd3dbb0d687a73b30f3aa1f339d5cdc81fc0b3f79f3da30cdc1d8a53f8839c1363f5b62f16705be213f9377aa8d7f3ab4d904bd8b9fdd86b33e2cddc5cd7af1d2c9c41da85f7e0527630b48393bfa98c5bcf72e580bd138bb8ad7ec75f9fefbd7f76fef4b0826fbdb389e2e0a7279f2ec0b33eb637560dcde93ef33c454a661a565795f321c47378f473f6dc6f3660b2e81b7a2d58f24b0ee44f6aa9deb4e47390527fbdb75a04e41c7e77f8cfff8e362ec558178654a89a777f39f1ca17fb5f886617faf7f0ca2281a8b97aa1442060d3b0e4de47b3f0e951607e3419b161359b8a69f7118e4b221543b975dba34d799fa03330728380e46c663b97fafff824df598d398b0695624ce88ec750e9aaf8ea96b7280fb487f35486c0cb47809e4b8f80b014f941d45637334107fd86080c87c4fbe447334b0dbde738d33825277f0f4c958ce7caa29b8cbcd505341d24f85dc9b3294d4a5a34f054c167f5663fe4a70624891a5e4f2a0c621d53042385b8e50cc39852a79ca69d6740ff12c7a00866698b2a8206c91ce78d53f849f537cf521cefc7b96f285ae3ebb61e5673c465718c11a2bb284045c95a2a06d2fb0e20c34fb4140a57cddd096dc493d74a6eaf7296aded40e5b25c84507737ca1f64e11168230146714c7c97a27f6e4f65a3b7fdb7948195208bfa8769c27dac72afd3436999d6331edceff070000ffff010000ffff95cb65f1724a0000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["app.js"] = bs @@ -63,7 +63,7 @@ func init() { bs, _ = ioutil.ReadAll(gr) Assets["favicon.png"] = bs - bs, _ = hex.DecodeString("1f8b080000096e8800ffec5ceb731b3792ffeebf029eddf3a34e43ca8fec6dc914ab1cc9d9b8367e94155f6e2b950fe00cc84134034c008c249eacfddbaf1b98f7831cd192ec5cf2c11639001ae8463f7e0d366676fff8ddd18fff7aff8a442689e7f766f887c454ac0e3d263c22563e4dd3434faf4560222e56f651208551328e993af44e8a9623a3628f0431d5fad0c34eb1a4a71e9264349cdf2364963043491051a59939f432b3f4ffee550d9131a9cf7ecbf8d9a1f73ffec797fe914c526af82266401666640246bd7e75c8c215ab8d13346187de1967e7a954a6d6f59c87263a0cd9190f986fbfec112eb8e134f675406376f864b2df2114321d289e1a2e458d56a71bcd4c2455a747ccc529512c068941b309324378809422c59687de929ee1d7490a829cdfc301869b98cd4b21924fe4f2123ee9b732646f61a6478fafae6653d7ab9cc0115b4869b451349d065a4fcb6f93848b093cf1f2759875cc74c498712bb4df8959a7c08461170607db164216325c934bfb91909486212cc85f4863647240fe6b3fbd7891b72d81697f49131eaf0f88f73d8bcf98e101256f59c6bc3d523ed8232f15487b8f682ab4af99e24b47e2ea9efd93c5ff197d53ce9850b5e2c237323d204f26dfb0a4d177828bf51329a44e69c0ca518db5bc6122967be48d143480bf475268090ab947bc239929ce142cf11c565892694d4141d950bb4326340be7c6aaae5173549d6e2bca6bb875091b32dc5a520e3752ee6d2d2987a5141652854c39d909295a7cc572255b623e20fb2f9a3b5d7b62c9f8df541b9e4acdd1220e50a7c026cfda13706d7c21fd45064ec19453d9c756e17c5438b7b4d640602cce12518e09b94e630a7bc905e83af317b10c4e8b75806a3b4b067d2cf4a3541cc5579101d5a91a1634385d2999891067910a96bf5a3c7afaec6f7be4e9f37dfcefc9e3174d092a1af24c1f906735e60bf93c492fc8f3ea7921c8a7f0f869f1f8aacd17a8999884141c474bcf63b684d5ee578ade60efc97ef5d86a3e8df90ae58f4cbed82eab42c08523eeca17f782dce7097a4d2a4c8f25101335cdec9c39113fdfdfefed1e76bc8763f229f88e9ea9c01b4dad76409098ba28716f6655dfbac6fbbe4f7e9429eca222beef9e097a5686187a862dee0f6a7ef131644b9ac51009204431db8fafa8f5e7cecdcd425e1241f74d41742a6f4307091bd69cc35f282a426f3ee3c9aa684193f28856017a581fbff94f9efedd3a76e2028ff7ec29b87d2b31f7793a27a59f9fe984c671412ce221d8b67f018eb839bff379996130fb27101634cd7b03842537cf7b149c64718d8f423ab58f56972ac6635e740f954c43792eca36db4ef3b8f317afdd0fa4bf5a6198463dcfbfd4a9bc0ab97920163a7d315b9492a70a43d26cba807fb43151b5ee72828489acb11abbde39acc92a380f4e212487e10796ca478f816a5d88ab789d46187649f9c98f42dc4f27ae072c8195bd0c4382c3c1d149b5c625cda630c3d629711f464d09ec9e33c7726b5a24313861290a7ec643d4d311cb6220ef13660c289a1eb5b640ae5aeb2a86dfe8ba749419dcce516b92cb657b4df9f0919ba398365499919bb384ee516bc20f8e42df7cb3691657df9bad55db6c0ae2b1b06b0a36e75cd880f771fdd1e77dc79536e0bccef78814f19a00923c17842f896001d39aaaf50b92af8c9c53251036e6fe31270f32e060a8f781fe92af5e0b743ba5d102e1cae61b8b89fd24042f56b3b37a3b8885c5c4feefe7d336fd43bbaf8f2eddf69a45cf9a2d16cf7af3828bb78c852c8418f06c5e4a6c982c4688b62f48e73f468c387e3365bd3d89a8260bc62004d3331692056071210da101e0170a2e7552b963926428f27c3946969d20043290fb7993f464364d9bcab075d188d96a5126ef066b32b05287c6dd97729f164610f8e7ebc4fec9a31a490162e59efb7674dd2da3b5d0ba9ec48caa25bff07af6aaf9a0f1b5f625ffd8d178b6ca6288e8a0a02d7d6e686e410f07560edb624df2086100628ec715855e2dff5baf09b8ad42c4987a848728d69c3e677a84595c5ee280236c78849f26af8f213adb7d82af8c1a4711e01ac1bf3fc09271c7c61951bdd7803d35a375231803df314d7511a35388be98b5fea5c16111e0f161ce0cb0001c34285f27b6929cca31572cb0fbf489d8ccf83d35d1d5d510d99a92cf1d8513434d56976a13eb38b56ac20874275b8db4d8e506bb4de116a223a50cafe99fea5d5c3207f697426e0a7954db2338245def4c5af95ffe1d727d9e222e6c8d77f89990e653d57e840fa3edbec2d03630a899dcebe3d91488f4500e1b00b6b19156c2302eec2c72da5de5ee0b5fca18d3399932d162e03bdbb2cbca4b25be0b06029900e86dc3458c58919282ffaf0d443b70d16348b7cdca2a960bd662e41ff08cc60dc47d2d5612c0ccf1cf3917bf4c5696dc773c661a3c0c8dcfe95abfcd920553575764898ff7c8c0a06fd7c60e5a7041716bbfbd7d81443269cbe30719dca03862a4764d69d8315f4018412cb3d0475c1f4bdacecade0168934b0bd43e4f220220e635058243be803cf00ca7258537540378dc553bbafdec12f224c1b2fb0140c63bc835bcf9bf98ee46d69e61f75be3decac161b72e311d019af169dc7196f83c243f7113dd90c00a14270a0837c1dc5df70cb9bc14c501cd920b774a206c6f70bae8751bc2fc2b2c0226de23c342fc1ce1c2932e2c80870824da0fdb2069089601c0ec4f5452c513b095024c7aad73097b4483a21b93aa40f40e78dcda563c4d7229791701ee96903453923cb7c0bdaa651596ed1b4a2b444b6986f289f224b3ae7a30f668b94205fcb938077cf4f8971db2885179c4f84cc23155a410f8cd774600abcd151f3389eb05c7ba1de5b42c64a16db51d8bf587d6b505f283b8bf62d43f84fb079cea58ecdf392278f9867c343cde083e378464bd8630964ce0cfb688da1f213e931daa4f758ba3a3f71f6f82a320cdde3315801db4d0057c1500b7158d0f9e5c5dfdc75db1ba11521de78fc9076ad8b539e66291e2f6250c3431b8ba826f77cb5596f6f0f431dd9d2399990e4bbd7175034f4520cff501e8bf1442662260effe49ee1f920cac1860408fe18ee61e7f5a8da46a67b3c53ce484a9b3a1b4763cd269057cfbbba51da0b3000f9fbd4d9c7af377f677c821b032483ca462c55449fbfe00f1e5720bf53bf324ed4391ff664aefe23fcedcb8fe247c0026ef8ee56e01cd395c9b07e62f06e8461cccda35da83d90a460ce12a6922a62cb0dfe974f6cbe0aae17d1ef3636463742fe61aa7476e687ec654076cdd59ee1ec4fde1101c168b757f6f0626f4b53d15ca1ac73636f58ee2feee27a15b18ead1d3df3d4003f282052812fd73d3407e99fcff036f9bb8bd0960f7156004640bc6eea4a57f82852da73ff7aacfeed467dafcf5191fbd524a2add5f62c1b0cdfd8e3b8999589988ccc9fed7586bf1561a1eb0cfaab1a8c325601ca1528d7fdc505b857679094f273ff28481ed01966107def707497200d9c3d5d54151ab062863a9381361bc76500b0759515b0dbffdfa8a4aa3497fa9455d7d6dd183538471c54bedb8fbee9f775b59d1d0e9b2cea8aca3c4c7dfdacaee71e59545f549fe1516c9c2bc349cd4aa17ef7515bd535f39549558370557cad52cc2b46026f72c7857e0600a2cea40664ab349793f612298997af3932cc562533225df499525dd0aae5153689863c54d942d260042a6018d93685a4e35550c364ce349ee0fa0e5da900feec18eb36d602800fa2ba9d6d350061982a1bc9ef5b8fef57698e45a67c8e2b7595f31e00dcc009b052c078c1cf594416e2ea5b3c7f490c94875ea3c11496448e3529dcb7cc1f5b1f65bdabfebbaa421ebd15bdbe8879cc672d5ebc15d87fcf6c78017777dd03383e5d198813edaff8bf38e868147cf9bc37a53c66d6e875d406362b5c1d77c25faf3bba31234b9d8d6f033d1f30137d3c75b37654a9b535515769a811fc4d2ba0523087cf7086c1726da8c704d28499504509290735014b2068d20af41b60ab68e54206fd220fe0170dd1a683f88581cf3f445838f74b4b76cb84d5755664be16c5d60af3ea9b2c3d7a04d5c2ce52de852bb4cb025fa420277a23ca02195cc27e4bd75b324927178d37b6f7ff543b03ae84c0a247bfdad7704fdf86674a029ba4d480772aff25809224cc24b521ea18a53df4570483c54063c3c3000ddf48b3ebc829a05a0082b920fbdfb2809d88f571780ff3ab650285d55e05e578e16a9519410f9f790da41cf965225f9ed10fce8e577fa30357965b7be6d2b359a38a0f82dd7e243fbf4f26144b56f63d043bc5a53109a089b8c4efecac5198d79481e3ce86b0db932ebf6211e4657ba803c00a6748b7b7d8c381e14142bef6c5b67041729d639e707e91db9568c02adf22c0d3fd7d9cbef7792e6953baf766bd0f26eeb6540bd32854795b8b03cf7c6ab87bf655cb1109c875d51679df51c6a60f3cfc1b209fe87b8bcb5144cfd3bd342ba114499387515869d7cd6a630396df01aa9bb2eb5ad3ea667b7dc4e7efad4b79390022317b0422c48176eb330ec318c66c4c6bb00a843145ce2c5344ca2b0c6dc2bec04ecd4390be91aec61b425342127c83b044b1816521de1472027ed655570418f20e24b10fae3c9a8129e9ed55b059e149bb749599bfc0147585a0f4c2d622a4e07e66f6575fd870efda6b6d13228160ce071f566a34863905e64ab3f0fbdef6552fc6696db0152e9b382516a4f5dcd42bfaef76add89bbd521b4016f8e356eb961170a11c4992dfad2f68cb27de3e0a66447dd713002fc97c5c76b48315c83d87860c50291058f95c23e87400e0f49b22e5c4e35e9aef22ed77a62d435e5fecada219e2953c0a429557815a4efd65de56b787a80c9645192522e1f11ecc681b9788a81e00552a66cf0a11964ce80d30362533ed0c235ea006e7c4e7ec48ecfa648ab732e321ca09bf47e8a9820ee9e26e06fbce482e6bc474e194b510b130e7ec644d490194be6586b643bcca6f0cd5d9859d8d582f81abe4a73506423e584a08b28d4da729793003c7730a8b95bdc3eaad14647bfe9a8a40f1ef49c1c8dba97531e76d6eeb7d13396df076c5a44cd8b1648609743a413a0df0bcbc6acb73cd5eac381636e0d25f2ac5db57c84c872d38afa433c069642394f58bcf4b6acdca6cbeee6135eeea98b1c5c03336cf4254cd0e9ac5d04746c4974b9b87eda98caeda903f6fa3375189d3ad46b9e6f248118227873690416b7de481a51119ae0c7561ad1d33a228d705df1ea65e31acfc664a2e40a037833fee7e78fbae5ef3adb515cb31ac834b6c4fc0fae24bf4a2b4826f86f19fe1e9fcaeb85ff76a776297c4ba225d4ef69aba0fe09de68031e61a97c896f1959bab32d52deae5b4fc89b3c62e2730d22c5a0e9de01e0809eadf31842ce5b96e970bb138a7530169dabfa1e9711dbf5faac89ea09c2901af62de1d6d384b1a684b70f3719936b1f694ed8b96150f860ac49d9c14da3faf7f4b832abc2705cbf1d4da7bcb8362231ff5cab71a21bb21bd75a590e7e475cdc3496024fda2b4988d561a14c4dc84f1cec05b42750cca276be24e0d143c9b4bdd9cdd0dd38d46938c8d2be6b8a06685cb300a432fff76c6aff161978866539144f9fcbb735a1e5bace6575afa58540d33edec1701cd35b4da7a6741de349514e779f65d77fc98b5870ba90177de725bdaa5e5377a7a12585012dad2e13d5ef75bbbb4fdd3907ec6bfcb18fbb0d860728a9820c2040855a2a99a0d600ded42401a8849a589dc0e83dfb16817687223d7299914d70ce734dd558135eeab7364592973bfddb4aebf30b26f687f52149f5ed6dbb20726335e48d6ebe06108e5b6027b2a53c451d8fd7b904d2ae46bc197d38b12b702f7cc045e0b6d99b652e6fad1923fe44f525d2f36a097d493a2ebc81eaf05713ebe150ff78cd9d70cbdc8a59bd5ed84a5091c30f749e6b6b1401fec202ea8b2ffe3a63f6ccd12288849a2002570b7e355e97a311c8380a5f4f269ebf0ca8894c6b3ef7cf4c1c91f96766dea3dfb8749b9977f1b2a281b45be7cd7fd4b4bb2f1726d50b9e6e380b1e0e5ef56ab8bef6ce25cafe6e2dbc5fc4ab7c97d129961bde1b966a3f3de51d27b8097846fd1051f443c4ad9d16612f743dec25d90cbe001cf3c13cc4cafceabb7db72822c9c19859464dd4db16a14de8bfea8adff3ab0d4598b5af2e9abc4b5d996c45f397c1a3f31e5518d8b27a58ef95e842ca78b3d4fadbf01a424b6e9b64b30165ecc6fe20a4d8209b7eb4d1fbf036d47e95f193dbd3fcdeb61496762e55f83bb68b7f7c7cfd47b589ebb07e9bf6b0153adf3574bcd6cb137fb7e07034ccc28feeadd8ee8da7800af14d6df64dd3bf3a50675be7ed8ebffe9631b5f69f4ef627cfb6f7ae5e68fd6bfb7dd61bc7d1346d75005eedb5070037f6b5e6ff070000ffff010000ffff4521ee30e75c0000") + bs, _ = hex.DecodeString("1f8b080000096e8800ffec5de973dc36b2ffeebf02e6eef3514fe4c847f66dc9a3a9722467e3daf8282b7e795ba97cc09098212292a00150f23c59fbb76f37c0fb98e18c478ab29b0fb186381ae84677e3d7608399de3f7d77f2e33fdebf22a18ea3d9bd29fe21114d96c70e4b1c922c5d9aa6c78e5a25be0e79b23445be48b41451c4e4b17356d49c681939c48fa852c70e368a043d779024a3c1ec1e21d398694afc904ac5f4b193e985fb57a7aa08b54e5df629e317c7ceffb91f5fba27224ea9e6f3880159189125d0ebf5ab63162c59ad5f426376ec5c7076990aa96b4d2f79a0c3e3805d709fb9e6e180f0846b4e2357f93462c74fbcc30ea180295ff2547391d468759ad14c8742765a443c392792452031a8d67ea609f7915228d9e2d859d00b7cf45210e4ec1e76d05c476c560a917c215757f04bbd15017b0b233d7a7c7d3d9dd856e50096d85c08adb4a4e9c4576a523e79314f3c2871f279e855c454c898b63334cf44af526042b3cf1a3b9b1a42e62258912bf393909406014cc89d0bad457c44fee730fdfc22af5b00d3ee82c63c5a1d11e77b165d30cd7d4adeb28c3907a42c38202f2548fb80289a285731c91796c4f53df3278bfe3bfca61c31a672c913578bf4883cf1be6171a3ad87937563910895529f95bd1a7379c392481c903722a13efc3d118912a09007c4391199e44cc2142f61862599d61014940db53b608962c14c1bd5d57286aad3ad45790dd72e6041866b4bcac15acabdb525e5a094c25cc880492bbb44242dbe22b1142d311f91c317cd95ae951832ee37d582a74271b48823d429b0c98bf6005c693711ee3c03a7a0cba14cb151381715ce4eadd511188bb23829fb045ca51185b5e409e83a73e791f0cf8b79806a5b4b067d2cf4a3541cc997a106d5a92ae6d43f5f4a9125018e22244c7f397ff4f4d95f0ec8d3e787f8cf93c72f9a129434e0993a22cf6acc17f279927e26cfabf242904fa1f869517cdde60bd42cf1020a8ea3a5e7115bc06c0f2b456fb0f7e4b02a369a4f23be44f923932f36cbaa1070e188bbf2c5b520f7798c5e9326bac712880e9b6676c9ac889f1f1ef6360f3adec332f9147c47cf50e08d26463b609398d85de2ded4a8be718df75d97fc28525845495cd79625f4a2dc62e805d6d83fa8f9c5cf802d6816c14e005b1433edf8921a7f6edddc34e0251174df144427f33a7490b060cd31dcb9a449e0cca63c5e163568520e51d2470febe293fbe4e95f8d632776e3719e3d05b76f24667f4f66a4f4f35315d3282a88853c00db763f83236e8e6f7d5ea6198cfe05840555b3de0dc2909be52d0a4eb2a8c647219dda4fa34b15e3112f9a0752a481b84cca3a534ff37de74f4ebb1d487fb9c46d1af53c7fa853791570fd2099abf4c5745e4a9e4adc92a69339fc471b0355f32e07885992356663e63b2be7646149c4fd73d89a83e0034bc5a3c740bd2ecc65b44a43dc7e49f9cb0d035c572bb6072c8619be0c0282ddc1e109b9c2a94d2730d2e8a1715d460d0dec5f322b82d6f0486270e05234fc8207a8b75b4c8fc13a9c31ad4101d5a839fa62d99a5fd17d4bc1f0f172f9247d68da1e361497e4f5a91db5aee47b918b0a338d6a366a7e62b1e84cce76df522692294da51ea92c0b681eb606fe6029f48d3b9d6451f5dcacadeaa6131097818513f009d6c50e7847db1e7df2775c2a0dcef5f28088245a1140ba9709e10b92309f2945e5ea05c967462ea94c10d6e6fe3b270f32e0208cfb407fc197af13748ba55301c2954f6a4c2672e300bc6ccd0fd4eb412c2c22e65f371fb6e9bfda6d5ddc724cab69f8ac5963f0b6332bb878cb58c002d8a39ecd4a890d93c51dacedabd2d98f212396df4c9add8884549139630011e8050bc81c62854468427dc057145cbe576d1724ce50e4f974b4281bc116cd40ee974dd2de7492369561e3a41153d676c1bc19cc49c34c6db4601fca759aeb84c07fae8acd9f7cd7252940c07c67b9195db7d3684db4ae2711a372c13f3b3d6bd52c683cd61ef29f1d8d67cb2c02c4010adad2e786e616f4b063b591182c4c1e214c414cf4b8a2d0abe57fe93501bb5488685387f000c59ad3e74c8d308bab2bec7082158ff097f7fa14d08359277864545b8a002709fefd01a68c2b36ce88eaad06eca989261a6001f88e68aa0a0c91023ac0a8fa4f0d0e0b0082853933c00270d0a0bccd9e4f722aa75c32dfacd3176222f7f75487d7d743646b4a3eb314ce34d5595daadd6daa0d73d09d6c34d262951bec36855b888e9432dcd23fd59bd86013ec2f85d819e2bcb647b048bfde98b4e2d3fc5969c953c4adadfe16df13d22c95ed222c0c37fb0a4ddb00a56672881780480fe5a001b01b0b69240cfd82ce2427dd59ee3ef1858830dc14294b5a0c7c676a769979a9c4b7c1802f6200e56df88a3b562845c2ffdf6c443b70d1634837cdca3212f336e0fc1b94d1a811096cc54a0c1836fa39e7e2176f69c87dc723a6c0c3d0e892aed4db2c9e33797d4d16587c40063a7dbbd2a6d39c271497f6db9b174828e2b63c7e10fe1ec51121b52da561fafc06c2f02391052ee2fb48d076b4f80e409b5818a0f675124900626e2910ecf21bc803cf985a5278431580c75db5a3dbce4c210f120cbb1f0064bc8358c399fd83a9be00b0d3ed7eabdf5b31d8edc625a64240332e8d3ace12cb03f213d7e19e0456a0b8a480701e86ddaaa7cbd555521c202d7862a3f3c4b406a78b5eb721cc3fc32460e003322cc4af112e947461011422906817b641d2102c0380d91fa8a492c7602bcec0f988393a42d18d095560f7f679d45a563cedea3fa8d83520698624796c816b558b2a0cdb7b0a2b9296d20cc513e5496b5df5a0efc962890af873714ef9e8f12f3b4411a3e288f1918465aa0821f0c9b54600b3cd151f2389ed36c7ba1de5b40c64a16db51d8bf587e6b501f283b8ef30ea1fc2fd034e752cf6ef1c11bc7c433e6a1ead059f6bb664b5826d2cf6e0cfa61db57f87f84a76a83a572d8e4ede7fdc07477e9abd67d2073b68a10b784c006e4b1a1d3db9befeafdb62752da43acd8bc907aad9d61cf3649ee2f2c50c34d1bfbe86a7dbe52a4b7b78fa98eece91c87487a5de7d750d4fc5469eeb03d07f9924224b7cf6eeefe4fe31c9c08a0106f418ee68eef1d56f28643b9a2dc621674c5e0c85b5e3914e6bc337ef554d0795f978f8ecace3d499bd33ef4987c0ca20f180264b264bdaf707882f161ba8df9a27691f8afc2f936a17ff7161fbf507e1033079772c770368cee2da7c63fecd00dd888359334773305bc188215c2574c8a401f63b9dcefe36b86a789dc7bc1c6df4eec55ce3f4c876cdcf98ea80ade79de2ad83b8ff380487c96cddf7dfc084dada53a1acb16f63516f69dfdffd247403433d7afabb0768403e613e8a44fddc34905fbc7f3ff0b68edb7d00bb3b8011902de8bb9396fe0116369cfedcab7edb539f49f3ed3316bd925248d59f62c1b0cebec7f522962c754866e4f02ee65abc159afbecab722cea70091847a854e31f17d464c95d5d41a9f7238f19d81e601976e47c7f14c747103d5c5f1f15b97480321692b3248856166a6127236aa3e1379f5f516934e94fb5a8abaf497ab08a302e89a9bdefbefbfbed66563474bacc332af33cb1f85b93793e2efdb3c83ec91f61922cc853d7492dbbf25e57d13bf99f4359937553b0295dcd2451036672cf8277198e26c0a2f2452615f3cafb135ec2f4c4999d652926c39209f94ec82cee66708d1a42c1184baec36cee010899f8348ac34939d4443258308527b93f80962b4d3ed8821d475bc3900ff49742ae2681f033044379beed69fdf16698e44a65c8e2b7595f52e21e4680c502967d464e7ad232d7a7d299637a8864843cb79e88c422a051a9ce65bc60db18fb2deddf365dd080f5e8ada974034e23b1ecf5e0b6417e3b65c08bdb36e899c1f268c4401fcdbfc57947c3c0c3e7cd6ebd21e326b7c33e43656cb4c1557c99f4c777272568b27b5bc3cf84cf07dc4c1f6fdd90296d0e5565d829067e1053ebe68c20f03d20b05c186833c215a124950240494c2e4151c80a3482bc06d94a583a52813caf41fc03e0ba15d07e10b228e2e98b061fe9686fd9709b36abcca4c299bcc05e7d926583bba04d3c59881bd0a5769a604bf485046e457940432a997be4bd71b3241451b0efb52f728d0756bec864be0beb5e1cc9ee7fe9eb29d8cd1529a483770723a6d9adac7e95c80bc31be7e1ed6bb95f9f0e2c340f3eedb05558626eb49fd5be81956d25ff373a9b17deaf01ca69bee0becda17e100754852fea4782d54be7e6a9e0d7ad7fbdc1259834c17f1090b7ae259a477cd3d612cfd555bc82c5fc42fc304bcef13470702ab55b469690bdd745a0dc059012cf130a51a7bd7bf4494eba949dc9167cf60426a3d2becb20c49c0603308c7949745492772c2eda496627914074da8943b6b51aa32918d10f22ae22dcbf732604211ddea73876eee314c1bbbcfa0cd16b67272facabba2e5457ee16a95194f0dca287d40e7e7221649cdfbdc39f4e7e631a0df4955993b653a8d1c40e45268a896e4de9d54370b1ae41d00ff1e26241c84bcc519af7679e5cd08807e4c183beda804bbd6abf82c0d880ce19acb9907672af4ff114027dcc29407cacebf4e0498ab734f2d7801db9568c02adf24d00feaeb397df9e6f790ea77627dbf06eb2fdc00564125fb4e0c4f29343bcd8fd29e39205603d66469d79d64f8006167f8d133307979d61eb1ea6e734ce1cc0e4b401f3a4d6696dcaeeeb592dbb925fbef4ad642a0d173043bc4e93d8c542d06efc243168dd07ea80e11778ed178f80f0868c63b47b46f26b64e0b4ac2d0b5b6d5ea819721e39430900e087ceb8b5e04f202acc0701c0433c82a84580e81f7bc38c0d18f0eca7103082bd180b0105dedac1210fc8396329ce34e630a80ea9265316cf701f330da61378b23780802fe8cf82e6c41507396821bcadb3f3362c85b138afd0b675d6d55c105802bcc904b39d4734391f9857eb10adff8cb7df37ac35658af9590805d65b711ac1428726d9fed8f95ec4458a426eb848a5cf6c47d929b52962fdc6d96b2667f6125da234ec0b98529c7ba24283fd283339b6cabc126a5ff0da97eca87dfb86e7292f8b9f5b48315881d8b86fc40288004ff1833e0f468e8f0982162bea6ad05de55dcef54ccb2de5feca380e7c85478962299578f3aeef1276e51c797a846777450660397d3c3058db31174fd111dc56caa4d92d69a6051e8bf8c49cb08116ae500770e173f223567c3a415ab700fdcad728b51bb4f482e5377c9b8b5f7318c52ebdcbf1f419d0ef3ba0be7350b59c51fff68b3eb4d818ce58b47036ccdc1cc4d93b95786db02e72b0020870475fab86cd256ba7179e1a125d2eb63f904ac566bc8dadfe1df076fd9ac35e50f710c1fd616fcc67df0bf6ae0879f8b385bd7b6a47606fdb146f5b376eeead45e02557b88934f7a0fc95836a39a2ce7214372b07e0f9867de783bd855361719225fc53862938a9d86e0b6a376adf7e6949b4c4c73d75153e3ec34bacc0a33d2d011fb2b0c7d9a4bc50bbf2c89b1c5362b9029122acb49f25b160c3a4760da1b70dd3b4d8d10ac558be4188b2bec625a6b5adbe6aa03a481d52c3be29dc38541d6b4a78e1789d31d9fa91e6848d1b068505634dca746e1ad53f27a79559158663dbed683ae55dd511d1ecd75a8d15dd90ddd8daca72f019b159d3588a88cbdc4244bc081365d2233f71b017d01e5f32831cf982703c0366ca7ccc81a1bbf108ea9de6204bf3f93beaa3714df1a473f6cfe9c4fc2dc2d60c33f128be702a3f2087966b1b9709fd861686e1a67807c3b14c6f349d9ad2758c274539dd7ea4577f791f32ff7c2e3ef71d32f4aa7a4dddad86961406b4b4ba3f58ff9483bdeed81365f7dbd7f8b3127b0114cf1b5209d0dc47855a4811a3d600105424060c839a581d58a803f3e1907683e200c19e1d98b8fc32d75485d7404afd56ba083472a77f53a1657ea7cce4d20c49aa6f6ddb39d06b13a0f7baf80ad0312e8119c864ef15a97b4ee7de573b01793ffa70666660bff18293c06533974989391caa1923be95de2144ac8e0a87ceac1af4ba0758d514fa8eb170e20d54872f4a8d8743fde33577c20d734b66f47a6e92bf931c7ea0f35c19a3f0f1a52aa82f7e8bf08299233a83202072f64370b5e057a355d91b818ca5706f5022b71d22e7df256b22d39acffd23444664fe9521f1e88fbfdd64485c7c276de83d7d5e7de7e2e1be2095541f7ddb73783abcabd43353fbea3b179afb9bb58078b191e4e2476f55ae44ef7e517b919237f4503bf100f321c2db8708283b3589b95cf9b09764735704449777e601de92a99ecd778811e20d6e66e576860ad522b40e96574df139bf6654ec7fe63362debbd4a6ac57347f193c57ed51858125abefb7bd129d0b11ad975a7f1dbe5e6fc96d9d6cd66cffbbb13fb8d7af914d3f0ce82dbc09b55f66fcece634bfb72e85a95d0a19fc8eede26f1f5fffa7dac436acdfa43ddcb9d71e5b7d50f5778bda46e31ffc69bfa06f339400aee15713cd57e97fb568cbd4ceda0d7ffd9431b9729f7a87deb3cdadab8fdfffdafef6fdda7e344d5b0d8057730509c08df95f20fc0b0000ffff010000fffffa536c1f13610000") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) Assets["index.html"] = bs diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 5bd57528f..49e5b8ca0 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -19,6 +19,7 @@ import ( "github.com/calmh/syncthing/logger" "github.com/calmh/syncthing/model" "github.com/codegangsta/martini" + "github.com/vitrun/qart/qr" ) type guiError struct { @@ -80,6 +81,7 @@ func startGUI(cfg config.GUIConfiguration, m *model.Model) error { router.Get("/rest/system", restGetSystem) router.Get("/rest/errors", restGetErrors) router.Get("/rest/discovery", restGetDiscovery) + router.Get("/qr/:text", getQR) router.Post("/rest/config", restPostConfig) router.Post("/rest/restart", restPostRestart) @@ -289,6 +291,17 @@ func restGetDiscovery(w http.ResponseWriter) { json.NewEncoder(w).Encode(discoverer.All()) } +func getQR(w http.ResponseWriter, params martini.Params) { + code, err := qr.Encode(params["text"], qr.M) + if err != nil { + http.Error(w, "Invalid", 500) + return + } + + w.Header().Set("Content-Type", "image/png") + w.Write(code.PNG()) +} + func basic(username string, passhash string) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { error := func() { diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 5f20825bb..9f95f5795 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -77,7 +77,8 @@ const ( - "beacon" (the beacon package) - "discover" (the discover package) - "files" (the files package) - - "net" (the main packge; connections & network messages) + - "net" (the main package; connections & network messages) + - "model" (the model package) - "scanner" (the scanner package) - "upnp" (the upnp package) - "xdr" (the xdr package) diff --git a/gui/app.js b/gui/app.js index 5b8e503da..ef81dc179 100644 --- a/gui/app.js +++ b/gui/app.js @@ -269,14 +269,15 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) { $scope.restart = function () { restarting = true; - $('#restarting').modal('show'); + $('#restarting').modal({backdrop: 'static', keyboard: false}); $http.post(urlbase + '/restart'); $scope.configInSync = true; }; $scope.shutdown = function () { + restarting = true; $http.post(urlbase + '/shutdown').success(function () { - setTimeout($scope.refresh(), 250); + $('#shutdown').modal({backdrop: 'static', keyboard: false}); }); $scope.configInSync = true; }; @@ -290,6 +291,10 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) { $('#editNode').modal({backdrop: 'static', keyboard: true}); }; + $scope.idNode = function () { + $('#idqr').modal('show'); + }; + $scope.addNode = function () { $scope.currentNode = {AddressesStr: 'dynamic'}; $scope.editingExisting = false; diff --git a/gui/index.html b/gui/index.html index c6db1c264..d3652438f 100644 --- a/gui/index.html +++ b/gui/index.html @@ -80,13 +80,14 @@ @@ -353,13 +354,56 @@ + + + + + + + +