diff --git a/.gitignore b/.gitignore index 70433cf6..fbdeb9e7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ clients client/client servers server/server +server/common/version.go release releases debs diff --git a/.travis.yml b/.travis.yml index 1f001313..5e5ab5db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,4 +6,9 @@ go: - tip before_install: + - npm install -g bower + +before_script: + - make client + - make server - cd server diff --git a/Makefile b/Makefile index 6a8077c2..f312d61d 100644 --- a/Makefile +++ b/Makefile @@ -24,57 +24,77 @@ # THE SOFTWARE. ### -RELEASE_VERSION=`cat VERSION` +RELEASE_VERSION="1.1" RELEASE_DIR="release/plik-$(RELEASE_VERSION)" +RELEASE_TARGETS=darwin-386 darwin-amd64 freebsd-386 \ +freebsd-amd64 linux-386 linux-amd64 linux-arm openbsd-386 \ +openbsd-amd64 GOHOSTOS=`go env GOHOSTOS` GOHOSTARCH=`go env GOHOSTARCH` -all: clean deps frontend server client - -### -# Install npm build dependencies -# ( run this first once ) -### -deps: - @cd server/public && npm install +DEBROOT_SERVER=debs/server +DEBROOT_CLIENT=debs/client +all: clean frontend clients server ### # Build frontend ressources ### frontend: - @if [ ! -d server/public/bower_components ]; then cd server/public && bower install --allow-root ; fi ; - @if [ ! -d server/public/public ]; then cd server/public && grunt ; fi ; + @if [ ! -d server/public/node_modules ]; then cd server/public && npm install ; fi + @if [ ! -d server/public/bower_components ]; then cd server/public && node_modules/bower/bin/bower install --allow-root ; fi + @if [ ! -d server/public/public ]; then cd server/public && node_modules/grunt-cli/bin/grunt ; fi ; ### # Build plik server for the current architecture ### server: - @sed -i -e "s/##VERSION##/$(RELEASE_VERSION)/g" server/common/config.go + @server/gen_build_info.sh $(RELEASE_VERSION) @cd server && go build -o plikd ./ - @sed -i -e "s/$(RELEASE_VERSION)/##VERSION##/g" server/common/config.go ### # Build plik server for all architectures ### -servers: - @cd server/public && bower install --allow-root - @cd server/public && grunt - @server/build.sh servers +servers: frontend + @server/gen_build_info.sh $(RELEASE_VERSION) + @cd server && for target in $(RELEASE_TARGETS) ; do \ + SERVER_DIR=../servers/$$target; \ + SERVER_PATH=$$SERVER_DIR/plikd; \ + export GOOS=`echo $$target | cut -d "-" -f 1`; \ + export GOARCH=`echo $$target | cut -d "-" -f 2`; \ + mkdir -p ../servers/$$target; \ + if [ $$GOOS = "windows" ] ; then SERVER_PATH=$$SERVER_DIR/plikd.exe ; fi ; \ + echo "Compiling plik server for $$target to $$SERVER_PATH"; \ + go build -o $$SERVER_PATH ; \ + done + @sed -i -e "s/$(RELEASE_VERSION)/##VERSION##/g" server/common/config.go ### # Build plik client for the current architecture ### client: + @server/gen_build_info.sh $(RELEASE_VERSION) @cd client && go build -o plik ./ ### # Build plik client for all architectures ### clients: - @client/build.sh clients + @server/gen_build_info.sh $(RELEASE_VERSION) + @cd client && for target in $(RELEASE_TARGETS) ; do \ + CLIENT_DIR=../clients/$$target; \ + CLIENT_PATH=$$CLIENT_DIR/plik; \ + CLIENT_MD5=$$CLIENT_DIR/MD5SUM; \ + export GOOS=`echo $$target | cut -d "-" -f 1`; \ + export GOARCH=`echo $$target | cut -d "-" -f 2`; \ + mkdir -p $$CLIENT_DIR; \ + if [ $$GOOS = "windows" ] ; then CLIENT_PATH=$$CLIENT_DIR/plik.exe ; fi ; \ + echo "Compiling plik client for $$target to $$CLIENT_PATH"; \ + go build -o $$CLIENT_PATH ; \ + md5sum $$CLIENT_PATH | awk '{print $$1}' > $$CLIENT_MD5; \ + done @mkdir -p clients/bash && cp client/plik.sh clients/bash ## @@ -93,67 +113,129 @@ debs: debs-client debs-server # Make server Debian packages ### debs-server: servers clients - @server/build.sh debs + @mkdir -p $(DEBROOT_SERVER)/usr/local/plikd/server + @mkdir -p $(DEBROOT_SERVER)/etc/init.d + @cp -R server/build/deb/DEBIAN $(DEBROOT_SERVER) + @cp -R clients/ $(DEBROOT_SERVER)/usr/local/plikd/clients + @cp -R server/public/ $(DEBROOT_SERVER)/usr/local/plikd/server/public + @cp -R server/plikd.cfg $(DEBROOT_SERVER)/etc/plikd.cfg + @cp -R server/plikd.init $(DEBROOT_SERVER)/etc/init.d/plikd && chmod +x $(DEBROOT_SERVER)/etc/init.d/plikd + @for arch in amd64 i386 armhf ; do \ + cp -R server/build/deb/DEBIAN/control $(DEBROOT_SERVER)/DEBIAN/control ; \ + sed -i -e "s/##ARCH##/$$arch/g" $(DEBROOT_SERVER)/DEBIAN/control ; \ + sed -i -e "s/##VERSION##/$(RELEASE_VERSION)/g" $(DEBROOT_SERVER)/DEBIAN/control ; \ + if [ $$arch = 'i386' ]; then \ + cp servers/linux-386/plikd $(DEBROOT_SERVER)/usr/local/plikd/server/ ; \ + elif [ $$arch = 'armhf' ]; then \ + cp servers/linux-arm/plikd $(DEBROOT_SERVER)/usr/local/plikd/server/ ; \ + else \ + cp servers/linux-$$arch/plikd $(DEBROOT_SERVER)/usr/local/plikd/server/ ; \ + fi ; \ + dpkg-deb --build $(DEBROOT_SERVER) debs/plikd-$(RELEASE_VERSION)-$$arch.deb ; \ + done ### # Make client Debian packages ### debs-client: clients - @client/build.sh debs - -### -# Build release archive -### -release: clean frontend server clients + @mkdir -p $(DEBROOT_CLIENT)/usr/local/bin + @cp -R client/build/deb/DEBIAN $(DEBROOT_CLIENT) + @for arch in amd64 i386 armhf ; do \ + cp -R client/build/deb/DEBIAN/control $(DEBROOT_CLIENT)/DEBIAN/control ; \ + sed -i -e "s/##ARCH##/$$arch/g" $(DEBROOT_CLIENT)/DEBIAN/control ; \ + sed -i -e "s/##VERSION##/$(RELEASE_VERSION)/g" $(DEBROOT_CLIENT)/DEBIAN/control ; \ + if [ $$arch = 'i386' ]; then \ + cp clients/linux-386/plik $(DEBROOT_CLIENT)/usr/local/bin ; \ + elif [ $$arch = 'armhf' ]; then \ + cp clients/linux-arm/plik $(DEBROOT_CLIENT)/usr/local/bin ; \ + else \ + cp clients/linux-$$arch/plik $(DEBROOT_CLIENT)/usr/local/bin ; \ + fi ; \ + dpkg-deb --build $(DEBROOT_CLIENT) debs/plik-$(RELEASE_VERSION)-$$arch.deb ; \ + done + + +### +# Prepare the release base (css, js, ...) +### +release-template: clean frontend clients @mkdir -p $(RELEASE_DIR)/server/public @cp -R clients $(RELEASE_DIR) - - @cp -R server/plikd $(RELEASE_DIR)/server @cp -R server/plikd.cfg $(RELEASE_DIR)/server - @cp -R server/public/css $(RELEASE_DIR)/server/public + @cp -R server/public/fonts $(RELEASE_DIR)/server/public @cp -R server/public/img $(RELEASE_DIR)/server/public @cp -R server/public/js $(RELEASE_DIR)/server/public @cp -R server/public/partials $(RELEASE_DIR)/server/public @cp -R server/public/public $(RELEASE_DIR)/server/public @cp -R server/public/index.html $(RELEASE_DIR)/server/public - @cd release && tar czvf plik-`cat ../VERSION`-$(GOHOSTOS)-$(GOHOSTARCH).tar.gz * + +### +# Build release archive +### +release: release-template server + @cp -R server/plikd $(RELEASE_DIR)/server + @cd release && tar czvf plik-$(RELEASE_VERSION)-$(GOHOSTOS)-$(GOHOSTARCH).tar.gz plik-$(RELEASE_VERSION) ### # Build release archives for all architectures ### -releases: release servers +releases: release-template servers @mkdir -p releases - @cp -R servers/linux-amd64/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-linux-64bits.tar.gz plik-`cat ../VERSION` - @cp -R servers/linux-386/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-linux-32bits.tar.gz plik-`cat ../VERSION` - @cp -R servers/linux-arm/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-linux-arm.tar.gz plik-`cat ../VERSION` - - @cp -R servers/freebsd-amd64/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-freebsd-64bits.tar.gz plik-`cat ../VERSION` - @cp -R servers/freebsd-386/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-freebsd-32bits.tar.gz plik-`cat ../VERSION` - @cp -R servers/freebsd-arm/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-freebsd-arm.tar.gz plik-`cat ../VERSION` + @cd release && for target in $(RELEASE_TARGETS) ; do \ + SERVER_PATH=../servers/$$target/plikd; \ + OS=`echo $$target | cut -d "-" -f 1`; \ + ARCH=`echo $$target | cut -d "-" -f 2`; \ + if [ $$OS = "darwin" ] ; then OS="macos" ; fi ; \ + if [ $$OS = "windows" ] ; then SERVER_PATH=../servers/$$target/plikd.exe ; fi ; \ + if [ $$ARCH = "386" ] ; then ARCH="32bits" ; fi ; \ + if [ $$ARCH = "amd64" ] ; then ARCH="64bits" ; fi ; \ + TARBALL_NAME=plik-$(RELEASE_VERSION)-$$OS-$$ARCH.tar.gz; \ + echo "Packaging plik release for $$target to $$TARBALL_NAME"; \ + cp -R $$SERVER_PATH plik-$(RELEASE_VERSION)/server; \ + tar czvf ../releases/$$TARBALL_NAME plik-$(RELEASE_VERSION); \ + done - @cp -R servers/openbsd-amd64/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-openbsd-64bits.tar.gz plik-`cat ../VERSION` - @cp -R servers/openbsd-386/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-openbsd-32bits.tar.gz plik-`cat ../VERSION` - - @rm $(RELEASE_DIR)/server/plikd - @cp -R servers/windows-amd64/plikd.exe $(RELEASE_DIR)/server && cd release && zip -r ../releases/plik-`cat ../VERSION`-windows-64bits.zip plik-`cat ../VERSION` - @cp -R servers/windows-386/plikd.exe $(RELEASE_DIR)/server && cd release && zip -r ../releases/plik-`cat ../VERSION`-windows-32bits.zip plik-`cat ../VERSION` + @md5sum releases/* > releases/md5sum.txt - @cp -R servers/darwin-amd64/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-macos-64bits.tar.gz plik-`cat ../VERSION` - @cp -R servers/darwin-386/plikd $(RELEASE_DIR)/server && cd release && tar czvf ../releases/plik-`cat ../VERSION`-macos-32bits.tar.gz plik-`cat ../VERSION` - @md5sum releases/* > releases/md5sum.txt +### +# Run tests and sanity checks +### +test: + @server/gen_build_info.sh $(RELEASE_VERSION) + @ERR="" ; for directory in server client ; do \ + cd $$directory; \ + echo -n "go test $$directory : "; \ + TEST=`go test ./... 2>&1`; \ + if [ $$? = 0 ] ; then echo "OK" ; else echo "$$TEST" | grep -v "no test files" | grep -v "^\[" && ERR="1"; fi ; \ + echo "go fmt $$directory : "; \ + for file in $$(find -name "*.go" | grep -v Godeps ); do \ + echo -n " - file $$file : " ; \ + FMT=`gofmt -l $$file` ; \ + if [ "$$FMT" = "" ] ; then echo "OK" ; else echo "FAIL" && ERR="1" ; fi ; \ + done; \ + echo -n "go vet $$directory : "; \ + VET=`go vet ./... 2>&1`; \ + if [ $$? = 0 ] ; then echo "OK" ; else echo "FAIL" && echo $$VET && ERR="1" ; fi ; \ + echo -n "go lint $$directory : "; \ + LINT=`golint ./...`; \ + if [ "$$LINT" = "" ] ; then echo "OK" ; else echo "FAIL" && echo $$LINT && ERR="1" ; fi ; \ + cd - 2>&1 > /dev/null; \ + done ; if [ "$$ERR" = "1" ] ; then exit 1 ; fi + @echo "cli client integration tests :\n" && cd client && ./test.sh ### # Remove all build files ### clean: + @rm -rf server/common/version.go @rm -rf server/public/bower_components @rm -rf server/public/public @rm -rf server/plikd diff --git a/README.md b/README.md index e7028562..84ab1eae 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,16 @@ Plik is an simple and powerful file uploading system written in golang. - Comments : Add custom message (in Markdown format) ### Version -1.0 +1.1 ### Installation ##### From release To run plik, it's very simple : ```sh -$ wget https://github.com/root-gg/plik/releases/download/1.0/plik-1.0.tar.gz -$ tar xvf plik-1.0.tar.gz -$ cd plik-1.0/server +$ wget https://github.com/root-gg/plik/releases/download/1.1/plik-1.1.tar.gz +$ tar xvf plik-1.1.tar.gz +$ cd plik-1.1/server $ ./plikd ``` Et voilà ! You now have a fully functional instance of plik running on http://127.0.0.1:8080. @@ -58,18 +58,10 @@ $ go get github.com/root-gg/plik/server $ cd $GOPATH/github.com/root-gg/plik/ ``` -As root user you need to install grunt, bower, and setup the golang crosscompilation environnement : -```sh -$ sudo -c "npm install -g bower grunt-cli" -$ sudo -c "client/build.sh env" -``` - To build everything and run it : ```sh -$ make deps -$ make release -$ cd server -$ ./plikd +$ make +$ cd server && ./plikd ``` To make debian packages : @@ -174,6 +166,23 @@ Remove file : - **DELETE** /$mode/:uploadid:/:fileid:/:filename: - Delete file. Upload **MUST** have "removable" option enabled. +Show server details : + + - **GET** /version + - Show plik server version, and some build informations (build host, date, git revision,...) + + - **GET** /config + - Show plik server configuration (ttl values, max file size, ...) + +QRCode : + + - **GET** /qrcode + - Generate a QRCode image from an url + - Params : + - url : The url you want to store in the QRCode + - size : The size of the generated image in pixels (default: 250, max: 1000) + + $mode can be "file" or "stream" depending if stream mode is enabled. See FAQ for more details. Examples : @@ -215,20 +224,21 @@ Options: -n, --name NAME Set file name when piping from STDIN --server SERVER Overrides plik url --comments COMMENT Set comments of the upload ( MarkDown compatible ) - --archive-options OPTIONS [tar|zip] Additional command line options -p Protect the upload with login and password --password PASSWD Protect the upload with login:password ( if omitted default login is "plik" ) -y, --yubikey Protect the upload with a Yubikey OTP -a Archive upload using default archive params ( see ~/.plikrc ) --archive MODE Archive upload using specified archive backend : tar|zip --compress MODE [tar] Compression codec : gzip|bzip2|xz|lzip|lzma|lzop|compress|no + --archive-options OPTIONS [tar|zip] Additional command line options -s Encrypt upload usnig default encrypt params ( see ~/.plikrc ) --secure MODE Archive upload using specified archive backend : openssl|pgp --cipher CIPHER [openssl] Openssl cipher to use ( see openssl help ) --passphrase PASSPHRASE [openssl] Passphrase or '-' to be prompted for a passphrase - --secure-options OPTIONS [openssl|pgp] Additional command line options --recipient RECIPIENT [pgp] Set recipient for pgp backend ( example : --recipient Bob ) + --secure-options OPTIONS [openssl|pgp] Additional command line options --update Update client + -v --version Show client version ``` For example to create directory tar.gz archive and encrypt it with openssl : @@ -248,7 +258,7 @@ Client configuration and preferences are stored at ~/.plikrc ( overridable with ### FAQ -##### I have an error when uploading from client : "Unable upload file : HTTP error 411 Length Required" +##### I have an error when uploading from client : "Unable to upload file : HTTP error 411 Length Required" Under nginx < 1.3.9, you must enable HttpChunkin module to allow transfer-encoding "chunked". You might want to install the "nginx-extras" Debian package with built-in HttpChunkin module. diff --git a/VERSION b/VERSION deleted file mode 100644 index 7dea76ed..00000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.0.1 diff --git a/client/config/config.go b/client/config/config.go index af4d661b..7afb5646 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -35,6 +35,8 @@ import ( "encoding/json" "fmt" "io" + "net/http" + "net/url" "os" "path/filepath" "strconv" @@ -160,9 +162,32 @@ func Load() (err error) { fmt.Printf("Please enter your plik domain [default:http://127.0.0.1:8080] : ") _, err := fmt.Scanf("%s", &domain) if err == nil { - Config.URL = domain - if !strings.HasPrefix(domain, "http") { - Config.URL = "http://" + domain + domain = strings.TrimRight(domain, "/") + parsedDomain, err := url.Parse(domain) + if err == nil { + if parsedDomain.Scheme == "" { + parsedDomain.Scheme = "http" + } + Config.URL = parsedDomain.String() + } + } + + // Try to HEAD the site to see if we have a redirection + resp, err := http.Head(Config.URL) + if err != nil { + return err + } + + finalURL := resp.Request.URL.String() + if finalURL != "" && finalURL != Config.URL { + fmt.Printf("We have been redirected to : %s\n", finalURL) + fmt.Printf("Replace current url (%s) with the new one ? [Y/n] ", Config.URL) + + input := "y" + fmt.Scanln(&input) + + if strings.HasPrefix(strings.ToLower(input), "y") { + Config.URL = strings.TrimSuffix(finalURL, "/") } } @@ -204,6 +229,10 @@ func Load() (err error) { func UnmarshalArgs(arguments map[string]interface{}) (err error) { // Handle flags + if arguments["--version"].(bool) { + fmt.Printf("Plik client %s\n", common.GetBuildInfo()) + os.Exit(0) + } if arguments["--debug"].(bool) { Config.Debug = true } @@ -317,7 +346,7 @@ func UnmarshalArgs(arguments map[string]interface{}) (err error) { } Upload.Removable = Config.Removable if arguments["--removable"].(bool) { - Upload.OneShot = true + Upload.Removable = true } Upload.Stream = Config.Stream if arguments["--stream"].(bool) { diff --git a/client/crypto/openssl/openssl.go b/client/crypto/openssl/openssl.go index 015774fc..1b59b18b 100644 --- a/client/crypto/openssl/openssl.go +++ b/client/crypto/openssl/openssl.go @@ -34,6 +34,7 @@ import ( "io" "os" "os/exec" + "strings" "github.com/root-gg/plik/server/common" ) @@ -99,7 +100,13 @@ func (ob *Backend) Encrypt(reader io.Reader, writer io.Writer) (err error) { os.Exit(1) return } - cmd := exec.Command(ob.Config.Openssl, ob.Config.Cipher, "-pass", fmt.Sprintf("fd:3")) + + var args []string + args = append(args, ob.Config.Cipher) + args = append(args, "-pass", fmt.Sprintf("fd:3")) + args = append(args, strings.Fields(ob.Config.Options)...) + + cmd := exec.Command(ob.Config.Openssl, args...) cmd.Stdin = reader // fd:0 cmd.Stdout = writer // fd:1 cmd.Stderr = os.Stderr // fd:2 diff --git a/client/crypto/pgp/config.go b/client/crypto/pgp/config.go index b590c25e..ad063d65 100644 --- a/client/crypto/pgp/config.go +++ b/client/crypto/pgp/config.go @@ -47,10 +47,10 @@ type BackendConfig struct { // NewPgpBackendConfig instantiate a new Backend Configuration // from config map passed as argument -func NewPgpBackendConfig(config map[string]interface{}) (pb *BackendConfig) { - pb = new(BackendConfig) - pb.Gpg = "/usr/bin/gpg" - pb.Keyring = os.Getenv("HOME") + "/.gnupg/pubring.gpg" - utils.Assign(pb, config) +func NewPgpBackendConfig(config map[string]interface{}) (pbc *BackendConfig) { + pbc = new(BackendConfig) + pbc.Gpg = "/usr/bin/gpg" + pbc.Keyring = os.Getenv("HOME") + "/.gnupg/pubring.gpg" + utils.Assign(pbc, config) return } diff --git a/client/plik.go b/client/plik.go index e8c7877f..55f82787 100644 --- a/client/plik.go +++ b/client/plik.go @@ -91,20 +91,21 @@ Options: -n, --name NAME Set file name when piping from STDIN --server SERVER Overrides plik url --comments COMMENT Set comments of the upload ( MarkDown compatible ) - --archive-options OPTIONS [tar|zip] Additional command line options -p Protect the upload with login and password --password PASSWD Protect the upload with login:password ( if omitted default login is "plik" ) -y, --yubikey Protect the upload with a Yubikey OTP -a Archive upload using default archive params ( see ~/.plikrc ) --archive MODE Archive upload using specified archive backend : tar|zip --compress MODE [tar] Compression codec : gzip|bzip2|xz|lzip|lzma|lzop|compress|no + --archive-options OPTIONS [tar|zip] Additional command line options -s Encrypt upload usnig default encrypt params ( see ~/.plikrc ) --secure MODE Archive upload using specified archive backend : openssl|pgp --cipher CIPHER [openssl] Openssl cipher to use ( see openssl help ) --passphrase PASSPHRASE [openssl] Passphrase or '-' to be prompted for a passphrase - --secure-options OPTIONS [openssl|pgp] Additional command line options --recipient RECIPIENT [pgp] Set recipient for pgp backend ( example : --recipient Bob ) + --secure-options OPTIONS [openssl|pgp] Additional command line options --update Update client + -v --version Show client version ` // Parse command line arguments arguments, _ = docopt.Parse(usage, nil, true, "", false) @@ -117,11 +118,15 @@ Options: } // Check client version - forceUpdate := arguments["--update"].(bool) - err = updateClient(forceUpdate) - if err != nil { + updateFlag := arguments["--update"].(bool) + err = updateClient(updateFlag) + if err == nil { + if updateFlag { + os.Exit(0) + } + } else { printf("Unable to update Plik client : %s\n", err) - if forceUpdate { + if updateFlag { os.Exit(1) } } @@ -197,7 +202,7 @@ Options: file, err := upload(uploadInfo, fileToUpload, fileToUpload.FileHandle) if err != nil { - printf("Unable upload file : %s\n", err) + printf("Unable to upload file : %s\n", err) return } @@ -447,11 +452,14 @@ func getFileURL(upload *common.Upload, file *common.File) (fileURL string) { return u.String() } -func updateClient(forceUpdate bool) (err error) { - if !forceUpdate && !config.Config.AutoUpdate { +func updateClient(updateFlag bool) (err error) { + // Do not check for update if AutoUpdate is not enabled + if !updateFlag && !config.Config.AutoUpdate { return } - if !forceUpdate && config.Config.Quiet { + + // Do not update when quiet mode is enabled + if !updateFlag && config.Config.Quiet { return } @@ -460,59 +468,138 @@ func updateClient(forceUpdate bool) (err error) { if err != nil { return } - MD5Sum, err := utils.FileMd5sum(path) + currentMD5, err := utils.FileMd5sum(path) if err != nil { return } - // Get client architechture - arch := runtime.GOOS + "-" + runtime.GOARCH - binary := "plik" - if runtime.GOOS == "windows" { - binary += ".exe" - } + // Check server version + var version string + var downloadURL string + var newMD5 string - // Get last client MD5Sum - baseURL := config.Config.URL + "/clients/" + arch var URL *url.URL - URL, err = url.Parse(baseURL + "/MD5SUM") + URL, err = url.Parse(config.Config.URL + "/version") if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) return } var req *http.Request req, err = http.NewRequest("GET", URL.String(), nil) if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) return } var resp *http.Response resp, err = client.Do(req) if err != nil { - return - } - if resp.StatusCode != 200 { - err = errors.New("Unable to get last MD5Sum : " + resp.Status) + err = fmt.Errorf("Unable to get server version : %s", err) return } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return + + if resp.StatusCode == 404 { + // <1.1 fallback on MD5SUM file + + baseURL := config.Config.URL + "/clients/" + runtime.GOOS + "-" + runtime.GOARCH + var URL *url.URL + URL, err = url.Parse(baseURL + "/MD5SUM") + if err != nil { + return + } + var req *http.Request + req, err = http.NewRequest("GET", URL.String(), nil) + if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) + return + } + var resp *http.Response + resp, err = client.Do(req) + if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) + return + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + err = fmt.Errorf("Unable to get server version : %s", resp.Status) + return + } + + var body []byte + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) + return + } + newMD5 = utils.Chomp(string(body)) + + binary := "plik" + if runtime.GOOS == "windows" { + binary += ".exe" + } + downloadURL = baseURL + "/" + binary + } else { + // >=1.1 use BuildInfo from /version + + if resp.StatusCode != 200 { + err = fmt.Errorf("Unable to get server version : %s", resp.Status) + return + } + + var body []byte + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) + return + } + + // Parse json BuildInfo object + buildInfo := new(common.BuildInfo) + err = json.Unmarshal(body, buildInfo) + if err != nil { + err = fmt.Errorf("Unable to get server version : %s", err) + return + } + + version = buildInfo.Version + for _, client := range buildInfo.Clients { + if client.OS == runtime.GOOS && client.ARCH == runtime.GOARCH { + newMD5 = client.Md5 + downloadURL = config.Config.URL + "/" + client.Path + break + } + } + + if newMD5 == "" || downloadURL == "" { + err = fmt.Errorf("Server does not offer a %s-%s client", runtime.GOOS, runtime.GOARCH) + return + } } - lastMD5Sum := utils.Chomp(string(body)) // Check if the client is up to date - if MD5Sum == lastMD5Sum { - if forceUpdate { - println("Plik client is up to date") + if currentMD5 == newMD5 { + if updateFlag { + if version != "" { + printf("Plik client %s is up to date\n", version) + } else { + printf("Plik client is up to date\n") + } os.Exit(0) } return } - fmt.Printf("Plik client is not up to date, do you want to update ? [Y/n] ") + + // Ask for permission + if version != "" { + fmt.Printf("Update Plik client from %s to %s ? [Y/n] ", common.GetBuildInfo().Version, version) + } else { + fmt.Printf("Update Plik client to match server version ? [Y/n] ") + } input := "y" fmt.Scanln(&input) if !strings.HasPrefix(strings.ToLower(input), "y") { - if forceUpdate { + if updateFlag { os.Exit(0) } return @@ -525,46 +612,56 @@ func updateClient(forceUpdate bool) (err error) { return } defer tmpFile.Close() - URL, err = url.Parse(baseURL + "/" + binary) + URL, err = url.Parse(downloadURL) if err != nil { + err = fmt.Errorf("Unable to download client : %s", err) return } req, err = http.NewRequest("GET", URL.String(), nil) if err != nil { + err = fmt.Errorf("Unable to download client : %s", err) return } resp, err = client.Do(req) if err != nil { + err = fmt.Errorf("Unable to download client : %s", err) return } if resp.StatusCode != 200 { - err = errors.New("Unable to get last client : " + resp.Status) + err = fmt.Errorf("Unable to download client : %s", resp.Status) return } defer resp.Body.Close() _, err = io.Copy(tmpFile, resp.Body) if err != nil { + err = fmt.Errorf("Unable to download client : %s", err) return } - // Check new MD5sum - MD5Sum, err = utils.FileMd5sum(tmpPath) + // Check download integrity + downloadMD5, err := utils.FileMd5sum(tmpPath) if err != nil { + err = fmt.Errorf("Unable to download client : %s", err) return } - if MD5Sum != lastMD5Sum { - err = fmt.Errorf("Invalid client MD5Sum %s does not match %s", MD5Sum, lastMD5Sum) + if downloadMD5 != newMD5 { + err = fmt.Errorf("Unable to download client : md5sum %s does not match %s", downloadMD5, newMD5) return } // Replace old client err = os.Rename(tmpPath, path) if err != nil { + err = fmt.Errorf("Unable to replace client : %s", err) return } - println("Plik client sucessfully updated") - os.Exit(0) + if version != "" { + printf("Plik client sucessfully updated to %s\n", version) + } else { + printf("Plik client sucessfully updated\n") + } + return } diff --git a/client/test.sh b/client/test.sh new file mode 100755 index 00000000..00548ecb --- /dev/null +++ b/client/test.sh @@ -0,0 +1,669 @@ +#!/bin/bash + +### +# The MIT License (MIT) +# +# Copyright (c) <2015> +# - Mathieu Bodjikian +# - Charles-Antoine Mathieu +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +### + +set -e +export SHELLOPTS + +ORIGIN=$(dirname $(readlink -f $0)) +cd "$ORIGIN/.." + +### +# Check that no plikd is running +### + +URL="http://127.0.0.1:8080" +if curl $URL 2>/dev/null | grep plik >/dev/null 2>&1 ; then + echo "A plik instance is running @ $URL" + exit 1 +fi + +### +# Build server and clients +### + +echo "Build current server and clients" +make clean client server +CLIENT=$(readlink -f client/plik) + +### +# Create temporary environement +### + +TMPDIR=$(mktemp -d) +SERVER_LOG="$TMPDIR/server_log" +CLIENT_LOG="$TMPDIR/client_log" + +SPECIMEN="$TMPDIR/SPECIMEN" + cat > $SPECIMEN << EOF +Lorem ipsum dolor sit amet, eu munere invenire est, in vel liber salutatus, id eum atqui iisque. Ut eam semper possim ullamcorper. Quodsi complectitur an mea. Oratio pertinacia ius ea, duo quem dolorum omittam at. Vix eu idque admodum, quem animal eam et. +Cu eum ullum constituto theophrastus, te eam nihil ignota iudicabit. Pri cu minim voluptatum inciderint. Ne nec inani latine. Ei voluptua splendide sit. +At vix clita aliquam docendi. Ex eum utroque dignissim theophrastus, nullam facete vituperatoribus his ne, mei ad delectus constituto. Qui ne euripidis liberavisse, te per labores lucilius, eu ferri convenire mea. Ius dico ceteros feugait eu, cu nisl magna option pro, cu agam veritus aliquando has. At pro mandamus qualisque, eu vis nostro aeterno. +Erat vulputate intellegebat an nam, te reque atomorum molestiae eos. Illud corpora incorrupte est cu, nullam audiam id per, mel et dicta legimus suscipiantur. Ad simul perfecto per, his veri legimus te. Cum aeque dissentiet et, atomorum aliquando efficiendi ex vix, his ei soleat omnium impetus. +Sed electram dignissim reformidans ut. In vim graeco torquatos pertinacia, duis tamquam duo id. Et viderer debitis vocibus quo, ea vero movet atomorum pri. Atqui delicatissimi an vis, amet deseruisse ius et. Eos rationibus scriptorem ex, vim meis eirmod consequuntur in. +EOF + +### +# Run server +### + +echo -n "Start Plik server : " +(cd server && ./plikd > $SERVER_LOG 2>&1) >/dev/null 2>&1 & + +# Verify that server is running +sleep 1 +if curl $URL 2>/dev/null | grep plik >/dev/null 2>&1 ; then + echo "OK" +else + echo "FAIL" + exit 1 +fi + +# Defer server shutdown +function shutdown { + EXITCODE=$? + if [ $EXITCODE -ne 0 ]; then + echo -e "FAIL\n" + if [ -f $SERVER_LOG ]; then + echo "last server logs :" + cat $SERVER_LOG + echo -e "\n" + fi + if [ -f $CLIENT_LOG ]; then + echo "last client logs :" + echo $UPLOAD_CMD + cat $CLIENT_LOG + echo -e "\n" + fi + fi + echo "Shutting down plik server" + PID=$(ps a | grep plikd | grep -v grep | awk '{print $1}') + if [ "x$PID" != "x" ];then + kill $PID + sleep 1 + PID=$(ps a | grep plikd | grep -v grep | awk '{print $1}') + if [ "x$PID" != "x" ];then + kill -9 $PID + fi + fi +} +trap shutdown EXIT + +### +# Helpers +### + +function before +{ + cd $TMPDIR + + rm -rf $TMPDIR/upload + rm -rf $TMPDIR/download + mkdir $TMPDIR/upload + mkdir $TMPDIR/download + + # Reset .plikrc file + export PLIKRC="$TMPDIR/.plikrc" + echo "URL = \"$URL\"" > $PLIKRC + + unset UPLOAD_CMD + unset UPLOAD_ID + unset UPLOAD_OPTS + unset UPLOAD_USER + unset UPLOAD_PWD + + truncate -s 0 $SERVER_LOG + truncate -s 0 $CLIENT_LOG +} + +# Upload files in the upload directory +function upload { + cd $TMPDIR/upload + UPLOAD_CMD="$CLIENT $@ *" + eval "$UPLOAD_CMD" >$CLIENT_LOG 2>&1 +} + +# Get upload options from server api +function uploadOpts { + UPLOAD_ID=$( cat $CLIENT_LOG | sed -n 's/^.*http.*\/\?id=\(.*\)$/\1/p' ) + local CURL_CMD="curl -s" + if [ "$UPLOAD_USER" != "" ] && [ "$UPLOAD_PWD" != "" ]; then + CURL_CMD="$CURL_CMD -u $UPLOAD_USER:$UPLOAD_PWD" + fi + CURL_CMD="$CURL_CMD $URL/upload/$UPLOAD_ID" + UPLOAD_OPTS=$( eval "$CURL_CMD" 2>/dev/null | python -m json.tool ) + +} + +# Download files by running the output cmds +function download { + cd $TMPDIR/download + local COMMANDS=$(cat $CLIENT_LOG | grep curl) + local IFS='\n' + for COMMAND in "$COMMANDS" + do + eval "$COMMAND" >/dev/null 2>/dev/null + done +} + +# Compare upload and download directories +function check { + diff --brief -r $TMPDIR/upload $TMPDIR/download +} + +### +# Tests +### + +echo -n " - help : " + +before +$CLIENT --help >$CLIENT_LOG 2>&1 +grep 'Usage' $CLIENT_LOG >/dev/null 2>/dev/null + +before +$CLIENT -h >$CLIENT_LOG 2>&1 +grep 'Usage' $CLIENT_LOG >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - version : " + +before +$CLIENT --version >$CLIENT_LOG 2>&1 +grep "Plik client v" $CLIENT_LOG >/dev/null 2>/dev/null + +before +$CLIENT -v >$CLIENT_LOG 2>&1 +grep "Plik client v" $CLIENT_LOG >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - debug : " + +before +$CLIENT -d >$CLIENT_LOG 2>&1 +grep "Arguments" $CLIENT_LOG >/dev/null 2>/dev/null +grep "Configuration" $CLIENT_LOG >/dev/null 2>/dev/null + +before +$CLIENT --debug >$CLIENT_LOG 2>&1 +grep "Arguments" $CLIENT_LOG >/dev/null 2>/dev/null +grep "Configuration" $CLIENT_LOG >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - single file : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload && download && check +echo "OK" + +#--------------------------------------------- + +echo -n " - single file with custom name : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --name CUSTOM +mv $TMPDIR/upload/FILE1 $TMPDIR/upload/CUSTOM +download && check + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --n CUSTOM +mv $TMPDIR/upload/FILE1 $TMPDIR/upload/CUSTOM +download && check + +echo "OK" + +#--------------------------------------------- + +echo -n " - multiple files : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +cp $SPECIMEN $TMPDIR/upload/FILE2 +upload && download && check +echo "OK" + +### +# Upload options +### + +echo -n " - one shot : " + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -o && uploadOpts +echo "$UPLOAD_OPTS" | grep '"oneShot": true' >/dev/null 2>/dev/null + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --oneshot && uploadOpts +echo "$UPLOAD_OPTS" | grep '"oneShot": true' >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - removable : " + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -r && uploadOpts +echo "$UPLOAD_OPTS" | grep '"removable": true' >/dev/null 2>/dev/null + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --removable && uploadOpts +echo "$UPLOAD_OPTS" | grep '"removable": true' >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - streaming : " + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +# Start upload cmd in background to create upload then kill it +( + set -e + upload -S & + child=$! + sleep 1 + ( + kill -0 $child && kill $child + sleep 1 + kill -0 $child && kill -9 $child + ) >/dev/null 2>&1 & +) +uploadOpts +echo "$UPLOAD_OPTS" | grep '"stream": true' >/dev/null 2>/dev/null + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +# Start upload cmd in background to create upload then kill it +( + set -e + upload --stream & + child=$! + sleep 1 + ( + kill -0 $child && kill $child + sleep 1 + kill -0 $child && kill -9 $child + ) >/dev/null 2>&1 & +) +uploadOpts +echo "$UPLOAD_OPTS" | grep '"stream": true' >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - ttl : " + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -t 3600 && uploadOpts +echo "$UPLOAD_OPTS" | grep '"ttl": 3600' >/dev/null 2>/dev/null + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --ttl 3600 && uploadOpts +echo "$UPLOAD_OPTS" | grep '"ttl": 3600' >/dev/null 2>/dev/null + +echo "OK" + +#--------------------------------------------- + +echo -n " - password : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +UPLOAD_USER="foo" +UPLOAD_PWD="bar" +upload --password "$UPLOAD_USER:$UPLOAD_PWD" && uploadOpts +echo "$UPLOAD_OPTS" | grep '"protectedByPassword": true' >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - prompted password : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +UPLOAD_USER="foo" +UPLOAD_PWD="bar" +echo -e "$UPLOAD_USER\n$UPLOAD_PWD\n" | upload -p && uploadOpts +echo "$UPLOAD_OPTS" | grep '"protectedByPassword": true' >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - comments : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --comments "foobar" && uploadOpts +echo "$UPLOAD_OPTS" | grep '"comments": "foobar"' >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - quiet : " + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -q +test $(cat $CLIENT_LOG | wc -l) -eq 1 +grep "$URL/file/.*/.*/FILE1" $CLIENT_LOG >/dev/null 2>/dev/null + +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --quiet +test $(cat $CLIENT_LOG | wc -l) -eq 1 +grep "$URL/file/.*/.*/FILE1" $CLIENT_LOG >/dev/null 2>/dev/null + +echo "OK" + +### +# Tar archive +### + +echo -n " - tar single file : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -a && download && check +echo "OK" + +#--------------------------------------------- + +echo -n " - tar multiple file : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +cp $SPECIMEN $TMPDIR/upload/FILE2 +upload -a && download && check +echo "OK" + +#--------------------------------------------- + +echo -n " - tar directory : " +before +mkdir $TMPDIR/upload/DIR +cp $SPECIMEN $TMPDIR/upload/DIR/FILE1 +cp $SPECIMEN $TMPDIR/upload/DIR/FILE2 +upload && download && check +echo "OK" + +#--------------------------------------------- + +echo -n " - tar custom compression codec : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -a --compress bzip2 && download && check +grep 'FILE1.tar.bz2' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - tar custom options : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +cp $SPECIMEN $TMPDIR/upload/EXCLUDE +upload -a --archive-options "'--exclude=EXCLUDE'" +rm $TMPDIR/upload/EXCLUDE +download && check +echo "OK" + +#--------------------------------------------- + +echo -n " - tar file with custom name : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -a --name foobar.tar.gz && download && check +grep 'foobar.tar.gz' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - tar directory with custom name : " +before +mkdir $TMPDIR/upload/DIR +cp $SPECIMEN $TMPDIR/upload/DIR/FILE1 +cp $SPECIMEN $TMPDIR/upload/DIR/FILE2 +upload -a --name foobar.tar.gz && download && check +grep 'foobar.tar.gz' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +### +# Zip archive +### + +echo -n " - zip single file : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --archive zip && download +# Unzip manually +cd $TMPDIR/download +test -f FILE1.zip || exit 1 +unzip FILE1.zip >/dev/null 2>/dev/null +rm FILE1.zip +check +echo "OK" + +#--------------------------------------------- + +echo -n " - zip directory : " +before +mkdir $TMPDIR/upload/DIR +cp $SPECIMEN $TMPDIR/upload/DIR/FILE1 +cp $SPECIMEN $TMPDIR/upload/DIR/FILE2 +upload --archive zip && download +# Unzip manually +cd $TMPDIR/download +test -f DIR.zip || exit 1 +unzip DIR.zip >/dev/null 2>/dev/null +rm DIR.zip +check +echo "OK" + +#--------------------------------------------- + +echo -n " - zip custom options : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +cp $SPECIMEN $TMPDIR/upload/EXCLUDE +upload --archive zip --archive-options "'--exclude EXCLUDE'" +rm $TMPDIR/upload/EXCLUDE +download +# Unzip manually +cd $TMPDIR/download +test -f archive.zip || exit 1 +unzip archive.zip >/dev/null 2>/dev/null +rm archive.zip +check +echo "OK" + +#--------------------------------------------- + +echo -n " - zip file with custom name : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --archive zip --name foobar.zip && download +# Unzip manually +cd $TMPDIR/download +test -f foobar.zip || exit 1 +unzip foobar.zip >/dev/null 2>/dev/null +rm foobar.zip +check +echo "OK" + +#--------------------------------------------- + +echo -n " - zip directory with custom name : " +before +mkdir $TMPDIR/upload/DIR +cp $SPECIMEN $TMPDIR/upload/DIR/FILE1 +cp $SPECIMEN $TMPDIR/upload/DIR/FILE2 +upload --archive zip --name foobar.zip && download +# Unzip manually +cd $TMPDIR/download +test -f foobar.zip || exit 1 +unzip foobar.zip >/dev/null 2>/dev/null +rm foobar.zip +check +echo "OK" + +### +# Openssl +### + +echo -n " - openssl auto passphrase : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -s && download && check +grep 'Passphrase' $CLIENT_LOG >/dev/null 2>/dev/null +grep 'openssl.*pass' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - openssl custom passphrase : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -s --passphrase foobar && download && check +grep 'openssl.*pass.*foobar' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - openssl prompted passphrase : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +echo "foobar" | upload -s --passphrase - && download && check +grep 'openssl.*pass.*foobar' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - openssl custom cipher : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +echo "foobar" | upload -s --cipher blowfish && download && check +grep 'openssl.*blowfish' $CLIENT_LOG >/dev/null 2>/dev/null +echo "OK" + +#--------------------------------------------- + +echo -n " - openssl custom options : " +before +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload -s --secure-options '-a' && download && check +curl $(cat $CLIENT_LOG | grep "curl" | sed -n 's/^.*"\(.*\)".*$/\1/p') >$TMPDIR/download/ARMORED 2>/dev/null +file $TMPDIR/download/ARMORED | grep "ASCII text" >/dev/null 2>/dev/null +echo "OK" + +### +# PGP +### + +echo -n " - pgp : " +before + +export GNUPGHOME="$TMPDIR" + +cat >$TMPDIR/pgp.key << EOF +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lQHYBFYLA7cBBACgMFkOEqqWop6bQYp4LGq0A79XakKj1vYVYom7Jg+V9utPQsK3 +29rKzSBAiq2yWAQLNyJ6dpyFctabSU1pJ/OAsvssuMxio/M6Kf+mvMHmAjyW9s5F +fqeZROKjyOswFwkPKS36ifOKm2CigfJlMavV74h3p4f9JznWYn0MBrCDMQARAQAB +AAP+I5ZKGonEEx4CjWxUllkLxX01o3ZsYpitZ9fR0F1mxgKqiRvERXNW2ooSmbQV +XZMXJuSzSLCUGkOGcM4qn+truXhE3vbxEtyNpKQP1ae/m2zLcUJk3JJWWmnt/BPD +JSRsOtOGovoasKVxK58BQ1UfkV0Mred31NxYVvU3LxAhCAkCAMRt1aWgfWgAA007 +j+wDtXz9qeAuBQ8jb5OZ/O0WU9mNfGauFVeby0Mld0m7ofUyAVdIGn26woY/hPCL +zCMcDZ0CANDE7vyYHRWcNclETzqkDuCKB/MG62jPRerF9QLKozDcP8fu8xm//iYd +K8v3UTHbhZ5X2wvcyMIQxm7Iov18oaUB/iJH5jUlhyHZOWIZXJ/xpV/fFZr+DIAi +kL7KP+nknyFrP4czcfzhSLkQKyU67ODkPfxqltf7SVZlnqk5GKpXB4WfBbQrcGxp +ay5yb290LmdnIChwbGlrIHRlc3Qga2V5KSA8cGxpa0Byb290LmdnPoi4BBMBAgAi +BQJWCwO3AhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBqKML4FUjUXW18 +A/9Rutp5SWnk+Vi4nbFh2QAyl5rwDdF45mzZBY1DQsBzpkREg8URvBLN0lpWDr4k +4Mm7ONIqmAta23NvPe4yR1f68Q2SGsheUbL27vGcbQ/bY1pkzTRSGZFnWu3Q6Oo2 +0gOo8b0HsbJMF4VJvwmAhXk+IiIbUpQ0Zep27BwQWagmDp0B2ARWCwO3AQQAp1GU +ZsAPOUgtm/gLA6fYf9OuUvaUprVL7GBpdhjIA1r5syJrCxRtWocvxH+EMHgF6CCq +Qe03PODI8NhjK1zCCZr1CQRontD1a59CHdSFk+2eTa40CNsJ17f16eiDwE8GvhNT +T/ZGEztS9b5uCp0higrAqtKTvx0NsX/V3juJBtMAEQEAAQAD/1OARJn0voQ9T7m3 +U7Pa15KPjz+LHIuIDeBlCyyrWGJITDZIdnhclOhpb/7WDp/rvjLm3mExY/BHVDDS +JMe2roSyraBj86SnejJDuA1JWhCLvBF8bQKrXNMdKH76gAdcT++tEuYMRmlur22z +PcW+FDZspr4lRn33AZPtHn21mrdNAgDCeasfTHlgXrOQ9o/iNVC9tfxFjVEe3JCC +nOaoBNNpDrOe8xpJ2amYbW0I3KkoYx2Q2hZKuIgj88WyoGdiirRnAgDcQId88AcH +vKPKunU4oFfePtqLjX5s5TKffcqmTtQRW0sqcoo8tNECXq8lsk9PUoihs15Ux2X4 +r6LexhGrMHa1AfwKZJBWXwoxzUKYVQW3qRh1MokzHS+ZLT25w/7Co/IF+CjLeSiU +IzKv2YBRXHV9YeTpqUxFSGOyIiAgC6kap0HVnbCInwQYAQIACQUCVgsDtwIbDAAK +CRBqKML4FUjUXRfBA/4pLcWcBOJ8suh7kTgmicZA55bAbY+CTnNlHma7pzW1rcqD +TojG/RllyilI8QHfR9+da/iEGoAcY8eTgpAYZfNnd8tCy1bQQM+YkjAgh7lFEUdV +Wslu8jCqJpbcKUL7k2mfTKwJ97h1Go5LMurSR9W2psZrmyHXbCccu0CghK/Y7g== +=/xyA +-----END PGP PRIVATE KEY BLOCK----- +EOF +gpg --import $TMPDIR/pgp.key >/dev/null 2>/dev/null + +# Use temporary keyring +cat >$PLIKRC << EOF +URL = "$URL" + +[SecureOptions] + Keyring = "$TMPDIR/pubring.gpg" + +EOF + +cp $SPECIMEN $TMPDIR/upload/FILE1 +upload --debug --secure pgp --recipient 'plik.root.gg' +download && check +echo "OK" + +### +# UPDATE +### + +shutdown +before +rm $SERVER_LOG +rm $CLIENT_LOG +cd $ORIGIN + +echo " - upgrade :" +./test_upgrade.sh 2>/dev/null | grep "Update from" +echo " - downgrade :" +./test_downgrade.sh 2>/dev/null | grep "Update to" + +exit 0 \ No newline at end of file diff --git a/client/test_downgrade.sh b/client/test_downgrade.sh new file mode 100755 index 00000000..681fb543 --- /dev/null +++ b/client/test_downgrade.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +### +# The MIT License (MIT) +# +# Copyright (c) <2015> +# - Mathieu Bodjikian +# - Charles-Antoine Mathieu +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +### + +set -e +cd $(dirname $0) + +### +# Try to downgrade cli client to older target release +### + +RELEASES=( + 1.0 + 1.0.1 + 1.1-RC1 +) + +### +# Check that no plikd is running +### + +URL="http://127.0.0.1:8080" +if curl $URL 2>/dev/null | grep plik > /dev/null ; then + echo "A plik instance is running @ $URL" + exit 1 +fi + +### +# Build current client +### + +go build -o plik +CLIENT=$(readlink -f plik) + +### +# Setup temporary build environment +### + +PLIK_PACKAGE="github.com/root-gg/plik" +TMPDIR=$(mktemp -d) +export GOPATH="$TMPDIR" +BUILD_PATH="$GOPATH/src/$PLIK_PACKAGE" + +### +# Create .plikrc file +### + +export PLIKRC="$TMPDIR/.plikrc" +echo "URL = \"$URL\"" > $PLIKRC + +### +# Downgrade client +### + +for RELEASE in ${RELEASES[@]} +do + # Clean + cd $TMPDIR + rm -rf $TMPDIR/* + mkdir -p $BUILD_PATH + + # Git clone + echo "Cloning git repository at tag $RELEASE :" + git clone -b $RELEASE --depth 1 https://$PLIK_PACKAGE $BUILD_PATH + cd $BUILD_PATH + + # Build server and clients + echo "Compiling server and clients v$RELEASE :" + + if grep deps Makefile ; then + make deps + fi + + # 1.0.1 fix + if [ "$RELEASE" == "1.0.1" ] ; then + ( cd server && go get -v ) + fi + + make clients server + + # Run server + echo "Start server v$RELEASE :" + (cd server && ./plikd)& + + # Verify that server is running + sleep 1 + if ! curl $URL 2>/dev/null | grep plik > /dev/null ; then + echo "Plik server did not start @ $URL" + exit 1 + fi + + # Try to downgrade client + cp $CLIENT ./plik + echo "y" | ./plik --update + + # Verify updated client + SERVER_MD5=$(md5sum "clients/$(go env GOOS)-$(go env GOARCH)/plik" | awk '{print $1}') + CLIENT_MD5=$(md5sum ./plik | awk '{print $1}') + if [ "$SERVER_MD5" == "$CLIENT_MD5" ];then + echo -e "\nUpdate to v$RELEASE success\n" + else + echo -e "\nUpdate to v$RELEASE fail : md5sum mismatch server($SERVER_MD5) != target($TARGET_MD5)\n" + exit 1 + fi + + # Shutdown server + echo "Shutting down plik server" + PID=$(ps a | grep plikd | grep -v grep | awk '{print $1}') + if [ "x$PID" != "x" ];then + kill $PID + sleep 1 + PID=$(ps a | grep plikd | grep -v grep | awk '{print $1}') + if [ "x$PID" != "x" ];then + kill -9 $PID + fi + fi +done \ No newline at end of file diff --git a/client/test_upgrade.sh b/client/test_upgrade.sh new file mode 100755 index 00000000..fa158e3f --- /dev/null +++ b/client/test_upgrade.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +### +# The MIT License (MIT) +# +# Copyright (c) <2015> +# - Mathieu Bodjikian +# - Charles-Antoine Mathieu +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +### + +set -e +cd $(dirname $0) +cd .. + +### +# Try to upgrade cli client from older target releases +### + +RELEASES=( + 1.0 + 1.0.1 + 1.1-RC1 +) + +### +# Check that no plikd is running +### + +URL="http://127.0.0.1:8080" +if curl $URL 2>/dev/null | grep plik > /dev/null ; then + echo "A plik instance is running @ $URL" + exit 1 +fi + +### +# Build server and clients +### + +echo "Build current server and clients" +make clean clients server + +### +# Run server +### + +echo "Start Plik server :" +(cd server && ./plikd)& + +# Verify that server is running +sleep 1 +if ! curl $URL 2>/dev/null | grep plik > /dev/null ; then + echo "Plik server did not start @ $URL" + exit 1 +fi + +# Defer server shutdown +function shutdown { + echo "Shutting down plik server" + PID=$(ps a | grep plikd | grep -v grep | awk '{print $1}') + if [ "x$PID" != "x" ];then + kill $PID + sleep 1 + PID=$(ps a | grep plikd | grep -v grep | awk '{print $1}') + if [ "x$PID" != "x" ];then + kill -9 $PID + fi + fi +} +trap shutdown EXIT + +### +# Get current client info +### + +CLIENT_DIR="clients/$(go env GOOS)-$(go env GOARCH)" + +CLIENT_BIN="$CLIENT_DIR/plik" +if [ ! -f "$CLIENT_BIN" ];then + echo "Missing $CLIENT_BIN" + exit 1 +fi + +MD5SUM_FILE="$CLIENT_DIR/MD5SUM" +if [ ! -f "$MD5SUM_FILE" ];then + echo "Missing $MD5SUM_FILE" + exit 1 +fi + +CLIENT_MD5=$(md5sum $CLIENT_BIN | awk '{print $1}') +SERVER_MD5=$(cat $MD5SUM_FILE) + +if [ "$CLIENT_MD5" != "$SERVER_MD5" ];then + echo "md5sum mismatch real($CLIENT_MD5) != server($SERVER_MD5)" + exit 1 +fi + +### +# Setup temporary build environment +### + +PLIK_PACKAGE="github.com/root-gg/plik" +TMPDIR=$(mktemp -d) +export GOPATH="$TMPDIR" +BUILD_PATH="$GOPATH/src/$PLIK_PACKAGE" + +### +# Create .plikrc file +### + +export PLIKRC="$TMPDIR/.plikrc" +echo "URL = \"$URL\"" > $PLIKRC + +### +# Upgrade clients +### + +for RELEASE in ${RELEASES[@]} +do + # Clean + cd $TMPDIR + rm -rf $TMPDIR/* + mkdir -p $BUILD_PATH + + # Git clone + echo "Cloning git repository at tag $RELEASE :" + git clone -b $RELEASE --depth 1 https://$PLIK_PACKAGE $BUILD_PATH + cd $BUILD_PATH + + # Build client + echo "Compiling client v$RELEASE :" + make client + + # Update client + echo "Update client from v$RELEASE :" + echo "Y" | client/plik --update + + # Verify updated client + TARGET_MD5=$(md5sum "client/plik" | awk '{print $1}') + if [ "$SERVER_MD5" == "$TARGET_MD5" ];then + echo -e "\nUpdate from v$RELEASE success\n" + else + echo -e "\nUpdate from v$RELEASE fail : md5sum mismatch server($SERVER_MD5) != target($TARGET_MD5)\n" + exit 1 + fi +done \ No newline at end of file diff --git a/server/Godeps/Godeps.json b/server/Godeps/Godeps.json index ed291183..ed448656 100644 --- a/server/Godeps/Godeps.json +++ b/server/Godeps/Godeps.json @@ -1,9 +1,6 @@ { "ImportPath": "github.com/root-gg/plik/server", - "GoVersion": "go1.3.1", - "Packages": [ - "./..." - ], + "GoVersion": "go1.5.1", "Deps": [ { "ImportPath": "github.com/BurntSushi/toml", @@ -14,6 +11,26 @@ "ImportPath": "github.com/GeertJohan/yubigo", "Rev": "b1764f04aa9ba3c98a15084e7e13c1a69753e1da" }, + { + "ImportPath": "github.com/facebookgo/clock", + "Rev": "600d898af40aa09a7a93ecb9265d87b0504b6f03" + }, + { + "ImportPath": "github.com/facebookgo/httpdown", + "Rev": "1fa03998d20119dfe4ef73f56a638d83048052e2" + }, + { + "ImportPath": "github.com/facebookgo/stats", + "Rev": "31fb71caf5a4f04c9f8bb3fa8e7c2597ba6eb50a" + }, + { + "ImportPath": "github.com/boombuler/barcode", + "Rev": "63f4aa2c46ebfac93952223a52df876f30b064c0" + }, + { + "ImportPath": "github.com/boombuler/barcode", + "Rev": "63f4aa2c46ebfac93952223a52df876f30b064c0" + }, { "ImportPath": "github.com/gorilla/context", "Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da" diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/LICENSE b/server/Godeps/_workspace/src/github.com/boombuler/barcode/LICENSE new file mode 100644 index 00000000..862b0ddc --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Florian Sundermann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/README.md b/server/Godeps/_workspace/src/github.com/boombuler/barcode/README.md new file mode 100644 index 00000000..d7abc4ee --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/README.md @@ -0,0 +1,17 @@ +##Introduction## +This is a package for GO which can be used to create different types of barcodes. + +##Supported Barcode Types## +* Codabar +* Code 128 +* Code 39 +* EAN 8 +* EAN 13 +* Datamatrix +* QR Codes +* 2 of 5 + +##Documentation## +See [GoDoc](https://godoc.org/github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode) + +To create a barcode use the Encode function from one of the subpackages. diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/barcode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/barcode.go new file mode 100644 index 00000000..13b8d13e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/barcode.go @@ -0,0 +1,20 @@ +package barcode + +import "image" + +// Contains some meta information about a barcode +type Metadata struct { + // the name of the barcode kind + CodeKind string + // contains 1 for 1D barcodes or 2 for 2D barcodes + Dimensions byte +} + +// a rendered and encoded barcode +type Barcode interface { + image.Image + // returns some meta information about the barcode + Metadata() Metadata + // the data that was encoded in this barcode + Content() string +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/codabar/encoder.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/codabar/encoder.go new file mode 100644 index 00000000..e3e31f47 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/codabar/encoder.go @@ -0,0 +1,49 @@ +// Package codabar can create Codabar barcodes +package codabar + +import ( + "fmt" + "regexp" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +var encodingTable = map[rune][]bool{ + '0': []bool{true, false, true, false, true, false, false, true, true}, + '1': []bool{true, false, true, false, true, true, false, false, true}, + '2': []bool{true, false, true, false, false, true, false, true, true}, + '3': []bool{true, true, false, false, true, false, true, false, true}, + '4': []bool{true, false, true, true, false, true, false, false, true}, + '5': []bool{true, true, false, true, false, true, false, false, true}, + '6': []bool{true, false, false, true, false, true, false, true, true}, + '7': []bool{true, false, false, true, false, true, true, false, true}, + '8': []bool{true, false, false, true, true, false, true, false, true}, + '9': []bool{true, true, false, true, false, false, true, false, true}, + '-': []bool{true, false, true, false, false, true, true, false, true}, + '$': []bool{true, false, true, true, false, false, true, false, true}, + ':': []bool{true, true, false, true, false, true, true, false, true, true}, + '/': []bool{true, true, false, true, true, false, true, false, true, true}, + '.': []bool{true, true, false, true, true, false, true, true, false, true}, + '+': []bool{true, false, true, true, false, false, true, true, false, false, true, true}, + 'A': []bool{true, false, true, true, false, false, true, false, false, true}, + 'B': []bool{true, false, true, false, false, true, false, false, true, true}, + 'C': []bool{true, false, false, true, false, false, true, false, true, true}, + 'D': []bool{true, false, true, false, false, true, true, false, false, true}, +} + +// Encode creates a codabar barcode for the given content +func Encode(content string) (barcode.Barcode, error) { + checkValid, _ := regexp.Compile(`[ABCD][0123456789\-\$\:/\.\+]*[ABCD]$`) + if content == "!" || checkValid.ReplaceAllString(content, "!") != "!" { + return nil, fmt.Errorf("can not encode \"%s\"", content) + } + resBits := new(utils.BitList) + for i, r := range content { + if i > 0 { + resBits.AddBit(false) + } + resBits.AddBit(encodingTable[r]...) + } + return utils.New1DCode("Codabar", content, resBits), nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/codabar/encoder_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/codabar/encoder_test.go new file mode 100644 index 00000000..5f13009e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/codabar/encoder_test.go @@ -0,0 +1,32 @@ +package codabar + +import ( + "image/color" + "testing" +) + +func Test_Encode(t *testing.T) { + _, err := Encode("FOOBAR") + if err == nil { + t.Error("\"FOOBAR\" should not be encodable") + } + + testEncode := func(txt, testResult string) { + code, err := Encode(txt) + if err != nil || code == nil { + t.Fail() + } else { + if code.Bounds().Max.X != len(testResult) { + t.Errorf("%v: length missmatch", txt) + } else { + for i, r := range testResult { + if (code.At(i, 0) == color.Black) != (r == '1') { + t.Errorf("%v: code missmatch on position %d", txt, i) + } + } + } + } + } + + testEncode("A40156B", "10110010010101101001010101001101010110010110101001010010101101010010011") +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encode.go new file mode 100644 index 00000000..f76ae534 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encode.go @@ -0,0 +1,120 @@ +// Package code128 can create Code128 barcodes +package code128 + +import ( + "fmt" + "strings" + "unicode/utf8" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +func strToRunes(str string) []rune { + result := make([]rune, utf8.RuneCountInString(str)) + i := 0 + for _, r := range str { + result[i] = r + i++ + } + return result +} + +func shouldUseCTable(nextRunes []rune, curEncoding byte) bool { + requiredDigits := 4 + if curEncoding == startCSymbol { + requiredDigits = 2 + } + if len(nextRunes) < requiredDigits { + return false + } + for i := 0; i < requiredDigits; i++ { + if nextRunes[i] < '0' || nextRunes[i] > '9' { + return false + } + } + return true +} + +func getCodeIndexList(content []rune) *utils.BitList { + result := new(utils.BitList) + curEncoding := byte(0) + for i := 0; i < len(content); i++ { + + if shouldUseCTable(content[i:], curEncoding) { + if curEncoding != startCSymbol { + if curEncoding == byte(0) { + result.AddByte(startCSymbol) + } else { + result.AddByte(codeCSymbol) + } + curEncoding = startCSymbol + } + idx := (content[i] - '0') * 10 + i++ + idx = idx + (content[i] - '0') + + result.AddByte(byte(idx)) + } else { + if curEncoding != startBSymbol { + if curEncoding == byte(0) { + result.AddByte(startBSymbol) + } else { + result.AddByte(codeBSymbol) + } + curEncoding = startBSymbol + } + var idx int + switch content[i] { + case FNC1: + idx = 102 + break + case FNC2: + idx = 97 + break + case FNC3: + idx = 96 + break + case FNC4: + idx = 100 + break + default: + idx = strings.IndexRune(bTable, content[i]) + break + } + + if idx < 0 { + return nil + } + result.AddByte(byte(idx)) + } + } + return result +} + +// Encode creates a Code 128 barcode for the given content +func Encode(content string) (barcode.Barcode, error) { + contentRunes := strToRunes(content) + if len(contentRunes) <= 0 || len(contentRunes) > 80 { + return nil, fmt.Errorf("content length should be between 1 and 80 runes but got %d", len(contentRunes)) + } + idxList := getCodeIndexList(contentRunes) + + if idxList == nil { + return nil, fmt.Errorf("\"%s\" could not be encoded", content) + } + + result := new(utils.BitList) + sum := 0 + for i, idx := range idxList.GetBytes() { + if i == 0 { + sum = int(idx) + } else { + sum += i * int(idx) + } + result.AddBit(encodingTable[idx]...) + } + result.AddBit(encodingTable[sum%103]...) + result.AddBit(encodingTable[stopSymbol]...) + return utils.New1DCode("Code 128", content, result), nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encode_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encode_test.go new file mode 100644 index 00000000..a0e2a837 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encode_test.go @@ -0,0 +1,51 @@ +package code128 + +import ( + "image/color" + "testing" +) + +func testEncode(t *testing.T, txt, testResult string) { + code, err := Encode(txt) + if err != nil || code == nil { + t.Error(err) + } else { + if code.Bounds().Max.X != len(testResult) { + t.Errorf("%v: length missmatch", txt) + } else { + for i, r := range testResult { + if (code.At(i, 0) == color.Black) != (r == '1') { + t.Errorf("%v: code missmatch on position %d", txt, i) + } + } + } + } +} + +func Test_EncodeFunctionChars(t *testing.T) { + encFNC1 := "11110101110" + encFNC2 := "11110101000" + encFNC3 := "10111100010" + encFNC4 := "10111101110" + encStartB := "11010010000" + encStop := "1100011101011" + + testEncode(t, string(FNC1)+"123", encStartB+encFNC1+"10011100110"+"11001110010"+"11001011100"+"11001000010"+encStop) + testEncode(t, string(FNC2)+"123", encStartB+encFNC2+"10011100110"+"11001110010"+"11001011100"+"11100010110"+encStop) + testEncode(t, string(FNC3)+"123", encStartB+encFNC3+"10011100110"+"11001110010"+"11001011100"+"11101000110"+encStop) + testEncode(t, string(FNC4)+"123", encStartB+encFNC4+"10011100110"+"11001110010"+"11001011100"+"11100011010"+encStop) +} + +func Test_Unencodable(t *testing.T) { + if _, err := Encode(""); err == nil { + t.Fail() + } + if _, err := Encode("ä"); err == nil { + t.Fail() + } +} + +func Test_EncodeCTable(t *testing.T) { + testEncode(t, "HI345678H", "110100100001100010100011000100010101110111101000101100011100010110110000101001011110111011000101000111011000101100011101011") + testEncode(t, "334455", "11010011100101000110001000110111011101000110100100111101100011101011") +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encodingtable.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encodingtable.go new file mode 100644 index 00000000..a32b31cd --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code128/encodingtable.go @@ -0,0 +1,133 @@ +package code128 + +var encodingTable = [107][]bool{ + []bool{true, true, false, true, true, false, false, true, true, false, false}, + []bool{true, true, false, false, true, true, false, true, true, false, false}, + []bool{true, true, false, false, true, true, false, false, true, true, false}, + []bool{true, false, false, true, false, false, true, true, false, false, false}, + []bool{true, false, false, true, false, false, false, true, true, false, false}, + []bool{true, false, false, false, true, false, false, true, true, false, false}, + []bool{true, false, false, true, true, false, false, true, false, false, false}, + []bool{true, false, false, true, true, false, false, false, true, false, false}, + []bool{true, false, false, false, true, true, false, false, true, false, false}, + []bool{true, true, false, false, true, false, false, true, false, false, false}, + []bool{true, true, false, false, true, false, false, false, true, false, false}, + []bool{true, true, false, false, false, true, false, false, true, false, false}, + []bool{true, false, true, true, false, false, true, true, true, false, false}, + []bool{true, false, false, true, true, false, true, true, true, false, false}, + []bool{true, false, false, true, true, false, false, true, true, true, false}, + []bool{true, false, true, true, true, false, false, true, true, false, false}, + []bool{true, false, false, true, true, true, false, true, true, false, false}, + []bool{true, false, false, true, true, true, false, false, true, true, false}, + []bool{true, true, false, false, true, true, true, false, false, true, false}, + []bool{true, true, false, false, true, false, true, true, true, false, false}, + []bool{true, true, false, false, true, false, false, true, true, true, false}, + []bool{true, true, false, true, true, true, false, false, true, false, false}, + []bool{true, true, false, false, true, true, true, false, true, false, false}, + []bool{true, true, true, false, true, true, false, true, true, true, false}, + []bool{true, true, true, false, true, false, false, true, true, false, false}, + []bool{true, true, true, false, false, true, false, true, true, false, false}, + []bool{true, true, true, false, false, true, false, false, true, true, false}, + []bool{true, true, true, false, true, true, false, false, true, false, false}, + []bool{true, true, true, false, false, true, true, false, true, false, false}, + []bool{true, true, true, false, false, true, true, false, false, true, false}, + []bool{true, true, false, true, true, false, true, true, false, false, false}, + []bool{true, true, false, true, true, false, false, false, true, true, false}, + []bool{true, true, false, false, false, true, true, false, true, true, false}, + []bool{true, false, true, false, false, false, true, true, false, false, false}, + []bool{true, false, false, false, true, false, true, true, false, false, false}, + []bool{true, false, false, false, true, false, false, false, true, true, false}, + []bool{true, false, true, true, false, false, false, true, false, false, false}, + []bool{true, false, false, false, true, true, false, true, false, false, false}, + []bool{true, false, false, false, true, true, false, false, false, true, false}, + []bool{true, true, false, true, false, false, false, true, false, false, false}, + []bool{true, true, false, false, false, true, false, true, false, false, false}, + []bool{true, true, false, false, false, true, false, false, false, true, false}, + []bool{true, false, true, true, false, true, true, true, false, false, false}, + []bool{true, false, true, true, false, false, false, true, true, true, false}, + []bool{true, false, false, false, true, true, false, true, true, true, false}, + []bool{true, false, true, true, true, false, true, true, false, false, false}, + []bool{true, false, true, true, true, false, false, false, true, true, false}, + []bool{true, false, false, false, true, true, true, false, true, true, false}, + []bool{true, true, true, false, true, true, true, false, true, true, false}, + []bool{true, true, false, true, false, false, false, true, true, true, false}, + []bool{true, true, false, false, false, true, false, true, true, true, false}, + []bool{true, true, false, true, true, true, false, true, false, false, false}, + []bool{true, true, false, true, true, true, false, false, false, true, false}, + []bool{true, true, false, true, true, true, false, true, true, true, false}, + []bool{true, true, true, false, true, false, true, true, false, false, false}, + []bool{true, true, true, false, true, false, false, false, true, true, false}, + []bool{true, true, true, false, false, false, true, false, true, true, false}, + []bool{true, true, true, false, true, true, false, true, false, false, false}, + []bool{true, true, true, false, true, true, false, false, false, true, false}, + []bool{true, true, true, false, false, false, true, true, false, true, false}, + []bool{true, true, true, false, true, true, true, true, false, true, false}, + []bool{true, true, false, false, true, false, false, false, false, true, false}, + []bool{true, true, true, true, false, false, false, true, false, true, false}, + []bool{true, false, true, false, false, true, true, false, false, false, false}, + []bool{true, false, true, false, false, false, false, true, true, false, false}, + []bool{true, false, false, true, false, true, true, false, false, false, false}, + []bool{true, false, false, true, false, false, false, false, true, true, false}, + []bool{true, false, false, false, false, true, false, true, true, false, false}, + []bool{true, false, false, false, false, true, false, false, true, true, false}, + []bool{true, false, true, true, false, false, true, false, false, false, false}, + []bool{true, false, true, true, false, false, false, false, true, false, false}, + []bool{true, false, false, true, true, false, true, false, false, false, false}, + []bool{true, false, false, true, true, false, false, false, false, true, false}, + []bool{true, false, false, false, false, true, true, false, true, false, false}, + []bool{true, false, false, false, false, true, true, false, false, true, false}, + []bool{true, true, false, false, false, false, true, false, false, true, false}, + []bool{true, true, false, false, true, false, true, false, false, false, false}, + []bool{true, true, true, true, false, true, true, true, false, true, false}, + []bool{true, true, false, false, false, false, true, false, true, false, false}, + []bool{true, false, false, false, true, true, true, true, false, true, false}, + []bool{true, false, true, false, false, true, true, true, true, false, false}, + []bool{true, false, false, true, false, true, true, true, true, false, false}, + []bool{true, false, false, true, false, false, true, true, true, true, false}, + []bool{true, false, true, true, true, true, false, false, true, false, false}, + []bool{true, false, false, true, true, true, true, false, true, false, false}, + []bool{true, false, false, true, true, true, true, false, false, true, false}, + []bool{true, true, true, true, false, true, false, false, true, false, false}, + []bool{true, true, true, true, false, false, true, false, true, false, false}, + []bool{true, true, true, true, false, false, true, false, false, true, false}, + []bool{true, true, false, true, true, false, true, true, true, true, false}, + []bool{true, true, false, true, true, true, true, false, true, true, false}, + []bool{true, true, true, true, false, true, true, false, true, true, false}, + []bool{true, false, true, false, true, true, true, true, false, false, false}, + []bool{true, false, true, false, false, false, true, true, true, true, false}, + []bool{true, false, false, false, true, false, true, true, true, true, false}, + []bool{true, false, true, true, true, true, false, true, false, false, false}, + []bool{true, false, true, true, true, true, false, false, false, true, false}, + []bool{true, true, true, true, false, true, false, true, false, false, false}, + []bool{true, true, true, true, false, true, false, false, false, true, false}, + []bool{true, false, true, true, true, false, true, true, true, true, false}, + []bool{true, false, true, true, true, true, false, true, true, true, false}, + []bool{true, true, true, false, true, false, true, true, true, true, false}, + []bool{true, true, true, true, false, true, false, true, true, true, false}, + []bool{true, true, false, true, false, false, false, false, true, false, false}, + []bool{true, true, false, true, false, false, true, false, false, false, false}, + []bool{true, true, false, true, false, false, true, true, true, false, false}, + []bool{true, true, false, false, false, true, true, true, false, true, false, true, true}, +} + +// const startASymbol byte = 103 +const startBSymbol byte = 104 +const startCSymbol byte = 105 + +const codeBSymbol byte = 100 +const codeCSymbol byte = 99 + +const stopSymbol byte = 106 + +const ( + // FNC1 - Special Function 1 + FNC1 = '\u00f1' + // FNC2 - Special Function 2 + FNC2 = '\u00f2' + // FNC3 - Special Function 3 + FNC3 = '\u00f3' + // FNC4 - Special Function 4 + FNC4 = '\u00f4' +) + +const bTable = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/code39/encoder.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code39/encoder.go new file mode 100644 index 00000000..c35a2e49 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/code39/encoder.go @@ -0,0 +1,147 @@ +// Package code39 can create Code39 barcodes +package code39 + +import ( + "errors" + "strings" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type encodeInfo struct { + value int + data []bool +} + +var encodeTable = map[rune]encodeInfo{ + '0': encodeInfo{0, []bool{true, false, true, false, false, true, true, false, true, true, false, true}}, + '1': encodeInfo{1, []bool{true, true, false, true, false, false, true, false, true, false, true, true}}, + '2': encodeInfo{2, []bool{true, false, true, true, false, false, true, false, true, false, true, true}}, + '3': encodeInfo{3, []bool{true, true, false, true, true, false, false, true, false, true, false, true}}, + '4': encodeInfo{4, []bool{true, false, true, false, false, true, true, false, true, false, true, true}}, + '5': encodeInfo{5, []bool{true, true, false, true, false, false, true, true, false, true, false, true}}, + '6': encodeInfo{6, []bool{true, false, true, true, false, false, true, true, false, true, false, true}}, + '7': encodeInfo{7, []bool{true, false, true, false, false, true, false, true, true, false, true, true}}, + '8': encodeInfo{8, []bool{true, true, false, true, false, false, true, false, true, true, false, true}}, + '9': encodeInfo{9, []bool{true, false, true, true, false, false, true, false, true, true, false, true}}, + 'A': encodeInfo{10, []bool{true, true, false, true, false, true, false, false, true, false, true, true}}, + 'B': encodeInfo{11, []bool{true, false, true, true, false, true, false, false, true, false, true, true}}, + 'C': encodeInfo{12, []bool{true, true, false, true, true, false, true, false, false, true, false, true}}, + 'D': encodeInfo{13, []bool{true, false, true, false, true, true, false, false, true, false, true, true}}, + 'E': encodeInfo{14, []bool{true, true, false, true, false, true, true, false, false, true, false, true}}, + 'F': encodeInfo{15, []bool{true, false, true, true, false, true, true, false, false, true, false, true}}, + 'G': encodeInfo{16, []bool{true, false, true, false, true, false, false, true, true, false, true, true}}, + 'H': encodeInfo{17, []bool{true, true, false, true, false, true, false, false, true, true, false, true}}, + 'I': encodeInfo{18, []bool{true, false, true, true, false, true, false, false, true, true, false, true}}, + 'J': encodeInfo{19, []bool{true, false, true, false, true, true, false, false, true, true, false, true}}, + 'K': encodeInfo{20, []bool{true, true, false, true, false, true, false, true, false, false, true, true}}, + 'L': encodeInfo{21, []bool{true, false, true, true, false, true, false, true, false, false, true, true}}, + 'M': encodeInfo{22, []bool{true, true, false, true, true, false, true, false, true, false, false, true}}, + 'N': encodeInfo{23, []bool{true, false, true, false, true, true, false, true, false, false, true, true}}, + 'O': encodeInfo{24, []bool{true, true, false, true, false, true, true, false, true, false, false, true}}, + 'P': encodeInfo{25, []bool{true, false, true, true, false, true, true, false, true, false, false, true}}, + 'Q': encodeInfo{26, []bool{true, false, true, false, true, false, true, true, false, false, true, true}}, + 'R': encodeInfo{27, []bool{true, true, false, true, false, true, false, true, true, false, false, true}}, + 'S': encodeInfo{28, []bool{true, false, true, true, false, true, false, true, true, false, false, true}}, + 'T': encodeInfo{29, []bool{true, false, true, false, true, true, false, true, true, false, false, true}}, + 'U': encodeInfo{30, []bool{true, true, false, false, true, false, true, false, true, false, true, true}}, + 'V': encodeInfo{31, []bool{true, false, false, true, true, false, true, false, true, false, true, true}}, + 'W': encodeInfo{32, []bool{true, true, false, false, true, true, false, true, false, true, false, true}}, + 'X': encodeInfo{33, []bool{true, false, false, true, false, true, true, false, true, false, true, true}}, + 'Y': encodeInfo{34, []bool{true, true, false, false, true, false, true, true, false, true, false, true}}, + 'Z': encodeInfo{35, []bool{true, false, false, true, true, false, true, true, false, true, false, true}}, + '-': encodeInfo{36, []bool{true, false, false, true, false, true, false, true, true, false, true, true}}, + '.': encodeInfo{37, []bool{true, true, false, false, true, false, true, false, true, true, false, true}}, + ' ': encodeInfo{38, []bool{true, false, false, true, true, false, true, false, true, true, false, true}}, + '$': encodeInfo{39, []bool{true, false, false, true, false, false, true, false, false, true, false, true}}, + '/': encodeInfo{40, []bool{true, false, false, true, false, false, true, false, true, false, false, true}}, + '+': encodeInfo{41, []bool{true, false, false, true, false, true, false, false, true, false, false, true}}, + '%': encodeInfo{42, []bool{true, false, true, false, false, true, false, false, true, false, false, true}}, + '*': encodeInfo{-1, []bool{true, false, false, true, false, true, true, false, true, true, false, true}}, +} + +var extendedTable = map[rune]string{ + 0: `%U`, 1: `$A`, 2: `$B`, 3: `$C`, 4: `$D`, 5: `$E`, 6: `$F`, 7: `$G`, 8: `$H`, 9: `$I`, 10: `$J`, + 11: `$K`, 12: `$L`, 13: `$M`, 14: `$N`, 15: `$O`, 16: `$P`, 17: `$Q`, 18: `$R`, 19: `$S`, 20: `$T`, + 21: `$U`, 22: `$V`, 23: `$W`, 24: `$X`, 25: `$Y`, 26: `$Z`, 27: `%A`, 28: `%B`, 29: `%C`, 30: `%D`, + 31: `%E`, 33: `/A`, 34: `/B`, 35: `/C`, 36: `/D`, 37: `/E`, 38: `/F`, 39: `/G`, 40: `/H`, 41: `/I`, + 42: `/J`, 43: `/K`, 44: `/L`, 47: `/O`, 58: `/Z`, 59: `%F`, 60: `%G`, 61: `%H`, 62: `%I`, 63: `%J`, + 64: `%V`, 91: `%K`, 92: `%L`, 93: `%M`, 94: `%N`, 95: `%O`, 96: `%W`, 97: `+A`, 98: `+B`, 99: `+C`, + 100: `+D`, 101: `+E`, 102: `+F`, 103: `+G`, 104: `+H`, 105: `+I`, 106: `+J`, 107: `+K`, 108: `+L`, + 109: `+M`, 110: `+N`, 111: `+O`, 112: `+P`, 113: `+Q`, 114: `+R`, 115: `+S`, 116: `+T`, 117: `+U`, + 118: `+V`, 119: `+W`, 120: `+X`, 121: `+Y`, 122: `+Z`, 123: `%P`, 124: `%Q`, 125: `%R`, 126: `%S`, + 127: `%T`, +} + +func getChecksum(content string) string { + sum := 0 + for _, r := range content { + info, ok := encodeTable[r] + if !ok || info.value < 0 { + return "#" + } + + sum += info.value + } + + sum = sum % 43 + for r, v := range encodeTable { + if v.value == sum { + return string(r) + } + } + return "#" +} + +func prepare(content string) (string, error) { + result := "" + for _, r := range content { + if r > 127 { + return "", errors.New("Only ASCII strings can be encoded") + } + val, ok := extendedTable[r] + if ok { + result += val + } else { + result += string([]rune{r}) + } + } + return result, nil +} + +// Encode returns a code39 barcode for the given content +// if includeChecksum is set to true, a checksum character is calculated and added to the content +func Encode(content string, includeChecksum bool, fullASCIIMode bool) (barcode.Barcode, error) { + if fullASCIIMode { + var err error + content, err = prepare(content) + if err != nil { + return nil, err + } + } else if strings.ContainsRune(content, '*') { + return nil, errors.New("invalid data! try full ascii mode") + } + + data := "*" + content + if includeChecksum { + data += getChecksum(content) + } + data += "*" + + result := new(utils.BitList) + + for i, r := range data { + if i != 0 { + result.AddBit(false) + } + + info, ok := encodeTable[r] + if !ok { + return nil, errors.New("invalid data! try full ascii mode") + } + result.AddBit(info.data...) + } + + return utils.New1DCode("Code 39", content, result), nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/codelayout.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/codelayout.go new file mode 100644 index 00000000..be90c6c0 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/codelayout.go @@ -0,0 +1,213 @@ +package datamatrix + +import ( + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type setValFunc func(byte) + +type codeLayout struct { + matrix *utils.BitList + occupy *utils.BitList + size *dmCodeSize +} + +func newCodeLayout(size *dmCodeSize) *codeLayout { + result := new(codeLayout) + result.matrix = utils.NewBitList(size.MatrixColumns() * size.MatrixRows()) + result.occupy = utils.NewBitList(size.MatrixColumns() * size.MatrixRows()) + result.size = size + return result +} + +func (l *codeLayout) Occupied(row, col int) bool { + return l.occupy.GetBit(col + row*l.size.MatrixColumns()) +} + +func (l *codeLayout) Set(row, col int, value, bitNum byte, occupy bool) { + val := ((value >> (7 - bitNum)) & 1) == 1 + if row < 0 { + row += l.size.MatrixRows() + col += 4 - ((l.size.MatrixRows() + 4) % 8) + } + if col < 0 { + col += l.size.MatrixColumns() + row += 4 - ((l.size.MatrixColumns() + 4) % 8) + } + l.matrix.SetBit(col+row*l.size.MatrixColumns(), val) + if occupy { + l.occupy.SetBit(col+row*l.size.MatrixColumns(), true) + } +} + +func (l *codeLayout) SetSimple(row, col int, value byte) { + l.Set(row-2, col-2, value, 0, true) + l.Set(row-2, col-1, value, 1, true) + l.Set(row-1, col-2, value, 2, true) + l.Set(row-1, col-1, value, 3, true) + l.Set(row-1, col-0, value, 4, true) + l.Set(row-0, col-2, value, 5, true) + l.Set(row-0, col-1, value, 6, true) + l.Set(row-0, col-0, value, 7, true) +} + +func (l *codeLayout) Corner1(value byte) { + l.Set(l.size.MatrixRows()-1, 0, value, 0, true) + l.Set(l.size.MatrixRows()-1, 1, value, 1, true) + l.Set(l.size.MatrixRows()-1, 2, value, 2, true) + l.Set(0, l.size.MatrixColumns()-2, value, 3, true) + l.Set(0, l.size.MatrixColumns()-1, value, 4, true) + l.Set(1, l.size.MatrixColumns()-1, value, 5, true) + l.Set(2, l.size.MatrixColumns()-1, value, 6, true) + l.Set(3, l.size.MatrixColumns()-1, value, 7, true) +} + +func (l *codeLayout) Corner2(value byte) { + l.Set(l.size.MatrixRows()-3, 0, value, 0, true) + l.Set(l.size.MatrixRows()-2, 0, value, 1, true) + l.Set(l.size.MatrixRows()-1, 0, value, 2, true) + l.Set(0, l.size.MatrixColumns()-4, value, 3, true) + l.Set(0, l.size.MatrixColumns()-3, value, 4, true) + l.Set(0, l.size.MatrixColumns()-2, value, 5, true) + l.Set(0, l.size.MatrixColumns()-1, value, 6, true) + l.Set(1, l.size.MatrixColumns()-1, value, 7, true) +} + +func (l *codeLayout) Corner3(value byte) { + l.Set(l.size.MatrixRows()-3, 0, value, 0, true) + l.Set(l.size.MatrixRows()-2, 0, value, 1, true) + l.Set(l.size.MatrixRows()-1, 0, value, 2, true) + l.Set(0, l.size.MatrixColumns()-2, value, 3, true) + l.Set(0, l.size.MatrixColumns()-1, value, 4, true) + l.Set(1, l.size.MatrixColumns()-1, value, 5, true) + l.Set(2, l.size.MatrixColumns()-1, value, 6, true) + l.Set(3, l.size.MatrixColumns()-1, value, 7, true) +} + +func (l *codeLayout) Corner4(value byte) { + l.Set(l.size.MatrixRows()-1, 0, value, 0, true) + l.Set(l.size.MatrixRows()-1, l.size.MatrixColumns()-1, value, 1, true) + l.Set(0, l.size.MatrixColumns()-3, value, 2, true) + l.Set(0, l.size.MatrixColumns()-2, value, 3, true) + l.Set(0, l.size.MatrixColumns()-1, value, 4, true) + l.Set(1, l.size.MatrixColumns()-3, value, 5, true) + l.Set(1, l.size.MatrixColumns()-2, value, 6, true) + l.Set(1, l.size.MatrixColumns()-1, value, 7, true) +} + +func (l *codeLayout) IterateSetter() <-chan setValFunc { + result := make(chan setValFunc) + go func() { + row := 4 + col := 0 + + for (row < l.size.MatrixRows()) || (col < l.size.MatrixColumns()) { + if (row == l.size.MatrixRows()) && (col == 0) { + result <- l.Corner1 + } + if (row == l.size.MatrixRows()-2) && (col == 0) && (l.size.MatrixColumns()%4 != 0) { + result <- l.Corner2 + } + if (row == l.size.MatrixRows()-2) && (col == 0) && (l.size.MatrixColumns()%8 == 4) { + result <- l.Corner3 + } + + if (row == l.size.MatrixRows()+4) && (col == 2) && (l.size.MatrixColumns()%8 == 0) { + result <- l.Corner4 + } + + for true { + if (row < l.size.MatrixRows()) && (col >= 0) && !l.Occupied(row, col) { + r := row + c := col + result <- func(b byte) { + l.SetSimple(r, c, b) + } + } + row -= 2 + col += 2 + if (row < 0) || (col >= l.size.MatrixColumns()) { + break + } + } + + row += 1 + col += 3 + + for true { + if (row >= 0) && (col < l.size.MatrixColumns()) && !l.Occupied(row, col) { + r := row + c := col + result <- func(b byte) { + l.SetSimple(r, c, b) + } + } + row += 2 + col -= 2 + if (row >= l.size.MatrixRows()) || (col < 0) { + break + } + } + row += 3 + col += 1 + + } + close(result) + }() + + return result +} + +func (l *codeLayout) Merge() *datamatrixCode { + result := newDataMatrixCode(l.size) + + //dotted horizontal lines + for r := 0; r < l.size.Rows; r += (l.size.RegionRows() + 2) { + for c := 0; c < l.size.Columns; c += 2 { + result.set(c, r, true) + } + } + + //solid horizontal line + for r := l.size.RegionRows() + 1; r < l.size.Rows; r += (l.size.RegionRows() + 2) { + for c := 0; c < l.size.Columns; c++ { + result.set(c, r, true) + } + } + + //dotted vertical lines + for c := l.size.RegionColumns() + 1; c < l.size.Columns; c += (l.size.RegionColumns() + 2) { + for r := 1; r < l.size.Rows; r += 2 { + result.set(c, r, true) + } + } + + //solid vertical line + for c := 0; c < l.size.Columns; c += (l.size.RegionColumns() + 2) { + for r := 0; r < l.size.Rows; r++ { + result.set(c, r, true) + } + } + count := 0 + for hRegion := 0; hRegion < l.size.RegionCountHorizontal; hRegion++ { + for vRegion := 0; vRegion < l.size.RegionCountVertical; vRegion++ { + for x := 0; x < l.size.RegionColumns(); x++ { + colMatrix := (l.size.RegionColumns() * hRegion) + x + colResult := ((2 + l.size.RegionColumns()) * hRegion) + x + 1 + + for y := 0; y < l.size.RegionRows(); y++ { + rowMatrix := (l.size.RegionRows() * vRegion) + y + rowResult := ((2 + l.size.RegionRows()) * vRegion) + y + 1 + val := l.matrix.GetBit(colMatrix + rowMatrix*l.size.MatrixColumns()) + if val { + count++ + } + + result.set(colResult, rowResult, val) + } + } + } + } + + return result +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/codesize.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/codesize.go new file mode 100644 index 00000000..78418128 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/codesize.go @@ -0,0 +1,65 @@ +package datamatrix + +type dmCodeSize struct { + Rows int + Columns int + RegionCountHorizontal int + RegionCountVertical int + ECCCount int + BlockCount int +} + +func (s *dmCodeSize) RegionRows() int { + return (s.Rows - (s.RegionCountHorizontal * 2)) / s.RegionCountHorizontal +} + +func (s *dmCodeSize) RegionColumns() int { + return (s.Columns - (s.RegionCountVertical * 2)) / s.RegionCountVertical +} + +func (s *dmCodeSize) MatrixRows() int { + return s.RegionRows() * s.RegionCountHorizontal +} + +func (s *dmCodeSize) MatrixColumns() int { + return s.RegionColumns() * s.RegionCountVertical +} + +func (s *dmCodeSize) DataCodewords() int { + return ((s.MatrixColumns() * s.MatrixRows()) / 8) - s.ECCCount +} + +func (s *dmCodeSize) DataCodewordsPerBlock() int { + return s.DataCodewords() / s.BlockCount +} + +func (s *dmCodeSize) ErrorCorrectionCodewordsPerBlock() int { + return s.ECCCount / s.BlockCount +} + +var codeSizes []*dmCodeSize = []*dmCodeSize{ + &dmCodeSize{10, 10, 1, 1, 5, 1}, + &dmCodeSize{12, 12, 1, 1, 7, 1}, + &dmCodeSize{14, 14, 1, 1, 10, 1}, + &dmCodeSize{16, 16, 1, 1, 12, 1}, + &dmCodeSize{18, 18, 1, 1, 14, 1}, + &dmCodeSize{20, 20, 1, 1, 18, 1}, + &dmCodeSize{22, 22, 1, 1, 20, 1}, + &dmCodeSize{24, 24, 1, 1, 24, 1}, + &dmCodeSize{26, 26, 1, 1, 28, 1}, + &dmCodeSize{32, 32, 2, 2, 36, 1}, + &dmCodeSize{36, 36, 2, 2, 42, 1}, + &dmCodeSize{40, 40, 2, 2, 48, 1}, + &dmCodeSize{44, 44, 2, 2, 56, 1}, + &dmCodeSize{48, 48, 2, 2, 68, 1}, + &dmCodeSize{52, 52, 2, 2, 84, 2}, + &dmCodeSize{64, 64, 4, 4, 112, 2}, + &dmCodeSize{72, 72, 4, 4, 144, 4}, + &dmCodeSize{80, 80, 4, 4, 192, 4}, + &dmCodeSize{88, 88, 4, 4, 224, 4}, + &dmCodeSize{96, 96, 4, 4, 272, 4}, + &dmCodeSize{104, 104, 4, 4, 336, 6}, + &dmCodeSize{120, 120, 6, 6, 408, 6}, + &dmCodeSize{132, 132, 6, 6, 496, 8}, + &dmCodeSize{144, 144, 6, 6, 620, 10}, +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/datamatrixcode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/datamatrixcode.go new file mode 100644 index 00000000..7da740a8 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/datamatrixcode.go @@ -0,0 +1,49 @@ +package datamatrix + +import ( + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" + "image" + "image/color" +) + +type datamatrixCode struct { + *utils.BitList + *dmCodeSize + content string +} + +func newDataMatrixCode(size *dmCodeSize) *datamatrixCode { + return &datamatrixCode{utils.NewBitList(size.Rows * size.Columns), size, ""} +} + +func (c *datamatrixCode) Content() string { + return c.content +} + +func (c *datamatrixCode) Metadata() barcode.Metadata { + return barcode.Metadata{"DataMatrix", 2} +} + +func (c *datamatrixCode) ColorModel() color.Model { + return color.Gray16Model +} + +func (c *datamatrixCode) Bounds() image.Rectangle { + return image.Rect(0, 0, c.Columns, c.Rows) +} + +func (c *datamatrixCode) At(x, y int) color.Color { + if c.get(x, y) { + return color.Black + } + return color.White +} + +func (c *datamatrixCode) get(x, y int) bool { + return c.GetBit(x*c.Rows + y) +} + +func (c *datamatrixCode) set(x, y int, value bool) { + c.SetBit(x*c.Rows+y, value) +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/encoder.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/encoder.go new file mode 100644 index 00000000..c408843e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/encoder.go @@ -0,0 +1,77 @@ +// Package datamatrix can create Datamatrix barcodes +package datamatrix + +import ( + "errors" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" +) + +// Encode returns a Datamatrix barcode for the given content +func Encode(content string) (barcode.Barcode, error) { + data := encodeText(content) + + var size *dmCodeSize + for _, s := range codeSizes { + if s.DataCodewords() >= len(data) { + size = s + break + } + } + if size == nil { + return nil, errors.New("to much data to encode") + } + data = addPadding(data, size.DataCodewords()) + data = ec.calcECC(data, size) + code := render(data, size) + if code != nil { + code.content = content + return code, nil + } + return nil, errors.New("unable to render barcode") +} + +func render(data []byte, size *dmCodeSize) *datamatrixCode { + cl := newCodeLayout(size) + + setters := cl.IterateSetter() + for _, b := range data { + (<-setters)(b) + } + return cl.Merge() +} + +func encodeText(content string) []byte { + var result []byte + input := []byte(content) + + for i := 0; i < len(input); { + c := input[i] + i++ + + if c >= '0' && c <= '9' && i < len(input) && input[i] >= '0' && input[i] <= '9' { + // two numbers... + c2 := input[i] + i++ + cw := byte(((c-'0')*10 + (c2 - '0')) + 130) + result = append(result, cw) + } else if c > 127 { + // not correct... needs to be redone later... + result = append(result, 235, c-127) + } else { + result = append(result, c+1) + } + } + return result +} + +func addPadding(data []byte, toCount int) []byte { + if len(data) < toCount { + data = append(data, 129) + } + for len(data) < toCount { + R := ((149 * (len(data) + 1)) % 253) + 1 + data = append(data, byte((129+R)%254)) + } + return data +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/errorcorrection.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/errorcorrection.go new file mode 100644 index 00000000..e7cf1ba5 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/errorcorrection.go @@ -0,0 +1,82 @@ +package datamatrix + +import ( + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type errorCorrection struct { + fld *utils.GaloisField + polynomes map[int][]int +} + +var ec *errorCorrection = newErrorCorrection() + +func newErrorCorrection() *errorCorrection { + result := new(errorCorrection) + result.fld = utils.NewGaloisField(301) + result.polynomes = make(map[int][]int) + return result +} + +func (ec *errorCorrection) getPolynomial(count int) []int { + poly, ok := ec.polynomes[count] + if !ok { + idx := 1 + poly = make([]int, count+1) + poly[0] = 1 + for i := 1; i <= count; i++ { + poly[i] = 1 + for j := i - 1; j > 0; j-- { + if poly[j] != 0 { + poly[j] = ec.fld.ALogTbl[(int(ec.fld.LogTbl[poly[j]])+idx)%255] + } + poly[j] = ec.fld.AddOrSub(poly[j], poly[j-1]) + } + poly[0] = ec.fld.ALogTbl[(int(ec.fld.LogTbl[poly[0]])+idx)%255] + idx++ + } + poly = poly[0:count] + ec.polynomes[count] = poly + } + return poly +} + +func (ec *errorCorrection) calcECCBlock(data []byte, poly []int) []byte { + ecc := make([]byte, len(poly)+1) + + for i := 0; i < len(data); i++ { + k := ec.fld.AddOrSub(int(ecc[0]), int(data[i])) + for j := 0; j < len(ecc)-1; j++ { + ecc[j] = byte(ec.fld.AddOrSub(int(ecc[j+1]), ec.fld.Multiply(k, poly[len(ecc)-j-2]))) + } + } + return ecc +} + +func (ec *errorCorrection) calcECC(data []byte, size *dmCodeSize) []byte { + buff := make([]byte, size.DataCodewordsPerBlock()) + poly := ec.getPolynomial(size.ErrorCorrectionCodewordsPerBlock()) + + dataSize := len(data) + // make some space for error correction codes + data = append(data, make([]byte, size.ECCCount)...) + + for block := 0; block < size.BlockCount; block++ { + // copy the data for the current block to buff + j := 0 + for i := block; i < dataSize; i += size.BlockCount { + buff[j] = data[i] + j++ + } + // calc the error correction codes + ecc := ec.calcECCBlock(buff, poly) + // and append them to the result + j = 0 + for i := block; i < size.ErrorCorrectionCodewordsPerBlock()*size.BlockCount; i += size.BlockCount { + data[dataSize+i] = ecc[j] + j++ + } + } + + return data +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/errorcorrection_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/errorcorrection_test.go new file mode 100644 index 00000000..78de8f70 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/datamatrix/errorcorrection_test.go @@ -0,0 +1,61 @@ +package datamatrix + +import ( + "bytes" + "testing" +) + +func Test_GetPolynomial(t *testing.T) { + var gf_polys map[int][]int = map[int][]int{ + 5: []int{228, 48, 15, 111, 62}, + 7: []int{23, 68, 144, 134, 240, 92, 254}, + 10: []int{28, 24, 185, 166, 223, 248, 116, 255, 110, 61}, + 11: []int{175, 138, 205, 12, 194, 168, 39, 245, 60, 97, 120}, + 12: []int{41, 153, 158, 91, 61, 42, 142, 213, 97, 178, 100, 242}, + 14: []int{156, 97, 192, 252, 95, 9, 157, 119, 138, 45, 18, 186, 83, 185}, + 18: []int{83, 195, 100, 39, 188, 75, 66, 61, 241, 213, 109, 129, 94, 254, 225, 48, 90, 188}, + 20: []int{15, 195, 244, 9, 233, 71, 168, 2, 188, 160, 153, 145, 253, 79, 108, 82, 27, 174, 186, 172}, + 24: []int{52, 190, 88, 205, 109, 39, 176, 21, 155, 197, 251, 223, 155, 21, 5, 172, 254, 124, 12, 181, 184, 96, 50, 193}, + 28: []int{211, 231, 43, 97, 71, 96, 103, 174, 37, 151, 170, 53, 75, 34, 249, 121, 17, 138, 110, 213, 141, 136, 120, 151, 233, 168, 93, 255}, + 36: []int{245, 127, 242, 218, 130, 250, 162, 181, 102, 120, 84, 179, 220, 251, 80, 182, 229, 18, 2, 4, 68, 33, 101, 137, 95, 119, 115, 44, 175, 184, 59, 25, 225, 98, 81, 112}, + 42: []int{77, 193, 137, 31, 19, 38, 22, 153, 247, 105, 122, 2, 245, 133, 242, 8, 175, 95, 100, 9, 167, 105, 214, 111, 57, 121, 21, 1, 253, 57, 54, 101, 248, 202, 69, 50, 150, 177, 226, 5, 9, 5}, + 48: []int{245, 132, 172, 223, 96, 32, 117, 22, 238, 133, 238, 231, 205, 188, 237, 87, 191, 106, 16, 147, 118, 23, 37, 90, 170, 205, 131, 88, 120, 100, 66, 138, 186, 240, 82, 44, 176, 87, 187, 147, 160, 175, 69, 213, 92, 253, 225, 19}, + 56: []int{175, 9, 223, 238, 12, 17, 220, 208, 100, 29, 175, 170, 230, 192, 215, 235, 150, 159, 36, 223, 38, 200, 132, 54, 228, 146, 218, 234, 117, 203, 29, 232, 144, 238, 22, 150, 201, 117, 62, 207, 164, 13, 137, 245, 127, 67, 247, 28, 155, 43, 203, 107, 233, 53, 143, 46}, + 62: []int{242, 93, 169, 50, 144, 210, 39, 118, 202, 188, 201, 189, 143, 108, 196, 37, 185, 112, 134, 230, 245, 63, 197, 190, 250, 106, 185, 221, 175, 64, 114, 71, 161, 44, 147, 6, 27, 218, 51, 63, 87, 10, 40, 130, 188, 17, 163, 31, 176, 170, 4, 107, 232, 7, 94, 166, 224, 124, 86, 47, 11, 204}, + 68: []int{220, 228, 173, 89, 251, 149, 159, 56, 89, 33, 147, 244, 154, 36, 73, 127, 213, 136, 248, 180, 234, 197, 158, 177, 68, 122, 93, 213, 15, 160, 227, 236, 66, 139, 153, 185, 202, 167, 179, 25, 220, 232, 96, 210, 231, 136, 223, 239, 181, 241, 59, 52, 172, 25, 49, 232, 211, 189, 64, 54, 108, 153, 132, 63, 96, 103, 82, 186}, + } + + for i, tst := range gf_polys { + res := ec.getPolynomial(i) + if len(res) != len(tst) { + t.Fail() + } + for i := 0; i < len(res); i++ { + if res[i] != tst[i] { + t.Fail() + } + } + } +} + +func Test_CalcECC(t *testing.T) { + data := []byte{142, 164, 186} + var size *dmCodeSize = nil + for _, s := range codeSizes { + if s.DataCodewords() >= len(data) { + size = s + break + } + } + if size == nil { + t.Error("size not found") + } + + if bytes.Compare(ec.calcECC(data, size), []byte{142, 164, 186, 114, 25, 5, 88, 102}) != 0 { + t.Error("ECC Test 1 failed") + } + data = []byte{66, 129, 70} + if bytes.Compare(ec.calcECC(data, size), []byte{66, 129, 70, 138, 234, 82, 82, 95}) != 0 { + t.Error("ECC Test 2 failed") + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/ean/encoder.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/ean/encoder.go new file mode 100644 index 00000000..0467607f --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/ean/encoder.go @@ -0,0 +1,184 @@ +// Package ean can create EAN 8 and EAN 13 barcodes. +package ean + +import ( + "errors" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type encodedNumber struct { + LeftOdd []bool + LeftEven []bool + Right []bool + CheckSum []bool +} + +var encoderTable = map[rune]encodedNumber{ + '0': encodedNumber{ + []bool{false, false, false, true, true, false, true}, + []bool{false, true, false, false, true, true, true}, + []bool{true, true, true, false, false, true, false}, + []bool{false, false, false, false, false, false}, + }, + '1': encodedNumber{ + []bool{false, false, true, true, false, false, true}, + []bool{false, true, true, false, false, true, true}, + []bool{true, true, false, false, true, true, false}, + []bool{false, false, true, false, true, true}, + }, + '2': encodedNumber{ + []bool{false, false, true, false, false, true, true}, + []bool{false, false, true, true, false, true, true}, + []bool{true, true, false, true, true, false, false}, + []bool{false, false, true, true, false, true}, + }, + '3': encodedNumber{ + []bool{false, true, true, true, true, false, true}, + []bool{false, true, false, false, false, false, true}, + []bool{true, false, false, false, false, true, false}, + []bool{false, false, true, true, true, false}, + }, + '4': encodedNumber{ + []bool{false, true, false, false, false, true, true}, + []bool{false, false, true, true, true, false, true}, + []bool{true, false, true, true, true, false, false}, + []bool{false, true, false, false, true, true}, + }, + '5': encodedNumber{ + []bool{false, true, true, false, false, false, true}, + []bool{false, true, true, true, false, false, true}, + []bool{true, false, false, true, true, true, false}, + []bool{false, true, true, false, false, true}, + }, + '6': encodedNumber{ + []bool{false, true, false, true, true, true, true}, + []bool{false, false, false, false, true, false, true}, + []bool{true, false, true, false, false, false, false}, + []bool{false, true, true, true, false, false}, + }, + '7': encodedNumber{ + []bool{false, true, true, true, false, true, true}, + []bool{false, false, true, false, false, false, true}, + []bool{true, false, false, false, true, false, false}, + []bool{false, true, false, true, false, true}, + }, + '8': encodedNumber{ + []bool{false, true, true, false, true, true, true}, + []bool{false, false, false, true, false, false, true}, + []bool{true, false, false, true, false, false, false}, + []bool{false, true, false, true, true, false}, + }, + '9': encodedNumber{ + []bool{false, false, false, true, false, true, true}, + []bool{false, false, true, false, true, true, true}, + []bool{true, true, true, false, true, false, false}, + []bool{false, true, true, false, true, false}, + }, +} + +func calcCheckNum(code string) rune { + x3 := len(code) == 7 + sum := 0 + for _, r := range code { + curNum := utils.RuneToInt(r) + if curNum < 0 || curNum > 9 { + return 'B' + } + if x3 { + curNum = curNum * 3 + } + x3 = !x3 + sum += curNum + } + + return utils.IntToRune((10 - (sum % 10)) % 10) +} + +func encodeEAN8(code string) *utils.BitList { + result := new(utils.BitList) + result.AddBit(true, false, true) + + for cpos, r := range code { + num, ok := encoderTable[r] + if !ok { + return nil + } + var data []bool + if cpos < 4 { + data = num.LeftOdd + } else { + data = num.Right + } + + if cpos == 4 { + result.AddBit(false, true, false, true, false) + } + result.AddBit(data...) + } + result.AddBit(true, false, true) + + return result +} + +func encodeEAN13(code string) *utils.BitList { + result := new(utils.BitList) + result.AddBit(true, false, true) + + var firstNum []bool + for cpos, r := range code { + num, ok := encoderTable[r] + if !ok { + return nil + } + if cpos == 0 { + firstNum = num.CheckSum + continue + } + + var data []bool + if cpos < 7 { // Left + if firstNum[cpos-1] { + data = num.LeftEven + } else { + data = num.LeftOdd + } + } else { + data = num.Right + } + + if cpos == 7 { + result.AddBit(false, true, false, true, false) + } + result.AddBit(data...) + } + result.AddBit(true, false, true) + return result +} + +// Encode returns a EAN 8 or EAN 13 barcode for the given code +func Encode(code string) (barcode.Barcode, error) { + if len(code) == 7 || len(code) == 12 { + code += string(calcCheckNum(code)) + } else if len(code) == 8 || len(code) == 13 { + check := code[0 : len(code)-1] + check += string(calcCheckNum(check)) + if check != code { + return nil, errors.New("checksum missmatch") + } + } + + if len(code) == 8 { + result := encodeEAN8(code) + if result != nil { + return utils.New1DCode("EAN 8", code, result), nil + } + } else if len(code) == 13 { + result := encodeEAN13(code) + if result != nil { + return utils.New1DCode("EAN 13", code, result), nil + } + } + return nil, errors.New("invalid ean code data") +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/ean/encoder_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/ean/encoder_test.go new file mode 100644 index 00000000..6f6da3f3 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/ean/encoder_test.go @@ -0,0 +1,46 @@ +package ean + +import ( + "image/color" + "testing" +) + +func testHelper(t *testing.T, testCode, testResult, kind string, checkMetadata bool) { + code, err := Encode(testCode) + if err != nil { + t.Error(err) + } + if checkMetadata && (code.Metadata().Dimensions != 1 || code.Content() != testCode || code.Metadata().CodeKind != kind) { + t.Error("Metadata missmatch") + } + if len(testResult) != code.Bounds().Max.X { + t.Fail() + } + for i, r := range testResult { + if (code.At(i, 0) == color.Black) != (r == '1') { + t.Fail() + } + } +} + +func Test_EncodeEAN(t *testing.T) { + testHelper(t, "5901234123457", "10100010110100111011001100100110111101001110101010110011011011001000010101110010011101000100101", "EAN 13", true) + testHelper(t, "55123457", "1010110001011000100110010010011010101000010101110010011101000100101", "EAN 8", true) + testHelper(t, "5512345", "1010110001011000100110010010011010101000010101110010011101000100101", "EAN 8", false) + _, err := Encode("55123458") //<-- Invalid checksum + if err == nil { + t.Error("Invalid checksum not detected") + } + _, err = Encode("invalid") + if err == nil { + t.Error("\"invalid\" should not be encodable") + } + _, err = Encode("invalid") + if err == nil { + t.Error("\"invalid\" should not be encodable") + } + bits := encodeEAN13("invalid error") + if bits != nil { + t.Error("\"invalid error\" should not be encodable") + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/alphanumeric.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/alphanumeric.go new file mode 100644 index 00000000..dbca6d8d --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/alphanumeric.go @@ -0,0 +1,66 @@ +package qr + +import ( + "errors" + "fmt" + "strings" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" + +func stringToAlphaIdx(content string) <-chan int { + result := make(chan int) + go func() { + for _, r := range content { + idx := strings.IndexRune(charSet, r) + result <- idx + if idx < 0 { + break + } + } + close(result) + }() + + return result +} + +func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) { + + contentLenIsOdd := len(content)%2 == 1 + contentBitCount := (len(content) / 2) * 11 + if contentLenIsOdd { + contentBitCount += 6 + } + vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount) + if vi == nil { + return nil, nil, errors.New("To much data to encode") + } + + res := new(utils.BitList) + res.AddBits(int(alphaNumericMode), 4) + res.AddBits(len(content), vi.charCountBits(alphaNumericMode)) + + encoder := stringToAlphaIdx(content) + + for idx := 0; idx < len(content)/2; idx++ { + c1 := <-encoder + c2 := <-encoder + if c1 < 0 || c2 < 0 { + return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric) + } + res.AddBits(c1*45+c2, 11) + } + if contentLenIsOdd { + c := <-encoder + if c < 0 { + return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric) + } + res.AddBits(c, 6) + } + + addPaddingAndTerminator(res, vi) + + return res, vi, nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/alphanumeric_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/alphanumeric_test.go new file mode 100644 index 00000000..d8b1d38f --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/alphanumeric_test.go @@ -0,0 +1,44 @@ +package qr + +import ( + "bytes" + "testing" +) + +func makeString(length int, content string) string { + res := "" + + for i := 0; i < length; i++ { + res += content + } + + return res +} + +func Test_AlphaNumericEncoding(t *testing.T) { + encode := AlphaNumeric.getEncoder() + + x, vi, err := encode("HELLO WORLD", M) + + if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17}) != 0 { + t.Errorf("\"HELLO WORLD\" failed to encode: %s", err) + } + + x, vi, err = encode(makeString(4296, "A"), L) + if x == nil || vi == nil || err != nil { + t.Fail() + } + x, vi, err = encode(makeString(4297, "A"), L) + if x != nil || vi != nil || err == nil { + t.Fail() + } + x, vi, err = encode("ABc", L) + if x != nil || vi != nil || err == nil { + t.Fail() + } + x, vi, err = encode("hello world", M) + + if x != nil || vi != nil || err == nil { + t.Error("\"hello world\" should not be encodable in alphanumeric mode") + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/automatic.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/automatic.go new file mode 100644 index 00000000..c037ee73 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/automatic.go @@ -0,0 +1,23 @@ +package qr + +import ( + "fmt" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) { + bits, vi, _ := Numeric.getEncoder()(content, ecl) + if bits != nil && vi != nil { + return bits, vi, nil + } + bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl) + if bits != nil && vi != nil { + return bits, vi, nil + } + bits, vi, _ = Unicode.getEncoder()(content, ecl) + if bits != nil && vi != nil { + return bits, vi, nil + } + return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content) +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/automatic_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/automatic_test.go new file mode 100644 index 00000000..07587d42 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/automatic_test.go @@ -0,0 +1,30 @@ +package qr + +import ( + "bytes" + "testing" +) + +func Test_AutomaticEncoding(t *testing.T) { + tests := map[string]encodeFn{ + "0123456789": Numeric.getEncoder(), + "ALPHA NUMERIC": AlphaNumeric.getEncoder(), + "unicode encoing": Unicode.getEncoder(), + "very long unicode encoding" + makeString(3000, "A"): nil, + } + + for str, enc := range tests { + testValue, _, _ := Auto.getEncoder()(str, M) + if enc != nil { + correctValue, _, _ := enc(str, M) + if testValue == nil || bytes.Compare(correctValue.GetBytes(), testValue.GetBytes()) != 0 { + t.Errorf("wrong encoding used for '%s'", str) + } + } else { + if testValue != nil { + t.Errorf("wrong encoding used for '%s'", str) + } + } + + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/blocks.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/blocks.go new file mode 100644 index 00000000..d3173787 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/blocks.go @@ -0,0 +1,59 @@ +package qr + +type block struct { + data []byte + ecc []byte +} +type blockList []*block + +func splitToBlocks(data <-chan byte, vi *versionInfo) blockList { + result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2) + + for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ { + blk := new(block) + blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1) + for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ { + blk.data[cw] = <-data + } + blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock) + result[b] = blk + } + + for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ { + blk := new(block) + blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2) + for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ { + blk.data[cw] = <-data + } + blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock) + result[int(vi.NumberOfBlocksInGroup1)+b] = blk + } + + return result +} + +func (bl blockList) interleave(vi *versionInfo) []byte { + var maxCodewordCount int + if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 { + maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1) + } else { + maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2) + } + resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 + + (vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2 + + result := make([]byte, 0, resultLen) + for i := 0; i < maxCodewordCount; i++ { + for b := 0; b < len(bl); b++ { + if len(bl[b].data) > i { + result = append(result, bl[b].data[i]) + } + } + } + for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ { + for b := 0; b < len(bl); b++ { + result = append(result, bl[b].ecc[i]) + } + } + return result +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/blocks_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/blocks_test.go new file mode 100644 index 00000000..656fba05 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/blocks_test.go @@ -0,0 +1,36 @@ +package qr + +import ( + "bytes" + "testing" +) + +func Test_Blocks(t *testing.T) { + byteIt := make(chan byte) + go func() { + for _, b := range []byte{67, 85, 70, 134, 87, 38, 85, 194, 119, 50, 6, 18, 6, 103, 38, 246, 246, 66, 7, 118, 134, 242, 7, 38, 86, 22, 198, 199, 146, 6, 182, 230, 247, 119, 50, 7, 118, 134, 87, 38, 82, 6, 134, 151, 50, 7, 70, 247, 118, 86, 194, 6, 151, 50, 16, 236, 17, 236, 17, 236, 17, 236} { + byteIt <- b + } + close(byteIt) + }() + vi := &versionInfo{5, Q, 18, 2, 15, 2, 16} + + data := splitToBlocks(byteIt, vi).interleave(vi) + if bytes.Compare(data, []byte{67, 246, 182, 70, 85, 246, 230, 247, 70, 66, 247, 118, 134, 7, 119, 86, 87, 118, 50, 194, 38, 134, 7, 6, 85, 242, 118, 151, 194, 7, 134, 50, 119, 38, 87, 16, 50, 86, 38, 236, 6, 22, 82, 17, 18, 198, 6, 236, 6, 199, 134, 17, 103, 146, 151, 236, 38, 6, 50, 17, 7, 236, 213, 87, 148, 235, 199, 204, 116, 159, 11, 96, 177, 5, 45, 60, 212, 173, 115, 202, 76, 24, 247, 182, 133, 147, 241, 124, 75, 59, 223, 157, 242, 33, 229, 200, 238, 106, 248, 134, 76, 40, 154, 27, 195, 255, 117, 129, 230, 172, 154, 209, 189, 82, 111, 17, 10, 2, 86, 163, 108, 131, 161, 163, 240, 32, 111, 120, 192, 178, 39, 133, 141, 236}) != 0 { + t.Fail() + } + + byteIt2 := make(chan byte) + go func() { + for _, b := range []byte{67, 85, 70, 134, 87, 38, 85, 194, 119, 50, 6, 18, 6, 103, 38, 246, 246, 66, 7, 118, 134, 242, 7, 38, 86, 22, 198, 199, 146, 6, 182, 230, 247, 119, 50, 7, 118, 134, 87, 38, 82, 6, 134, 151, 50, 7, 70, 247, 118, 86, 194, 6, 151, 50, 16, 236, 17, 236, 17, 236, 17, 236} { + byteIt2 <- b + } + close(byteIt2) + }() + vi = &versionInfo{5, Q, 18, 2, 16, 2, 15} + + data = splitToBlocks(byteIt2, vi).interleave(vi) + if bytes.Compare(data, []byte{67, 246, 247, 247, 85, 66, 119, 118, 70, 7, 50, 86, 134, 118, 7, 194, 87, 134, 118, 6, 38, 242, 134, 151, 85, 7, 87, 50, 194, 38, 38, 16, 119, 86, 82, 236, 50, 22, 6, 17, 6, 198, 134, 236, 18, 199, 151, 17, 6, 146, 50, 236, 103, 6, 7, 17, 38, 182, 70, 236, 246, 230, 71, 101, 27, 62, 13, 91, 166, 86, 138, 16, 78, 229, 102, 11, 199, 107, 2, 182, 132, 103, 89, 66, 136, 69, 78, 255, 116, 129, 126, 163, 219, 234, 158, 216, 42, 234, 97, 62, 186, 59, 123, 148, 220, 191, 254, 145, 82, 95, 129, 79, 236, 254, 30, 174, 228, 50, 181, 110, 150, 205, 34, 235, 242, 0, 115, 147, 58, 243, 28, 140, 221, 219}) != 0 { + t.Fail() + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/encoder.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/encoder.go new file mode 100644 index 00000000..200925b0 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/encoder.go @@ -0,0 +1,416 @@ +// Package qr can be used to create QR barcodes. +package qr + +import ( + "image" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) + +// Encoding mode for QR Codes. +type Encoding byte + +const ( + // Auto will choose ths best matching encoding + Auto Encoding = iota + // Numeric encoding only encodes numbers [0-9] + Numeric + // AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, : + AlphaNumeric + // Unicode encoding encodes the string as utf-8 + Unicode + // only for testing purpose + unknownEncoding +) + +func (e Encoding) getEncoder() encodeFn { + switch e { + case Auto: + return encodeAuto + case Numeric: + return encodeNumeric + case AlphaNumeric: + return encodeAlphaNumeric + case Unicode: + return encodeUnicode + } + return nil +} + +func (e Encoding) String() string { + switch e { + case Auto: + return "Auto" + case Numeric: + return "Numeric" + case AlphaNumeric: + return "AlphaNumeric" + case Unicode: + return "Unicode" + } + return "" +} + +// Encode returns a QR barcode with the given content, error correction level and uses the given encoding +func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) { + bits, vi, err := mode.getEncoder()(content, level) + if err != nil { + return nil, err + } + + blocks := splitToBlocks(bits.IterateBytes(), vi) + data := blocks.interleave(vi) + result := render(data, vi) + result.content = content + return result, nil +} + +func render(data []byte, vi *versionInfo) *qrcode { + dim := vi.modulWidth() + results := make([]*qrcode, 8) + for i := 0; i < 8; i++ { + results[i] = newBarcode(dim) + } + + occupied := newBarcode(dim) + + setAll := func(x int, y int, val bool) { + occupied.Set(x, y, true) + for i := 0; i < 8; i++ { + results[i].Set(x, y, val) + } + } + + drawFinderPatterns(vi, setAll) + drawAlignmentPatterns(occupied, vi, setAll) + + //Timing Pattern: + var i int + for i = 0; i < dim; i++ { + if !occupied.Get(i, 6) { + setAll(i, 6, i%2 == 0) + } + if !occupied.Get(6, i) { + setAll(6, i, i%2 == 0) + } + } + // Dark Module + setAll(8, dim-8, true) + + drawVersionInfo(vi, setAll) + drawFormatInfo(vi, -1, occupied.Set) + for i := 0; i < 8; i++ { + drawFormatInfo(vi, i, results[i].Set) + } + + // Write the data + var curBitNo int + + for pos := range iterateModules(occupied) { + var curBit bool + if curBitNo < len(data)*8 { + curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1 + } else { + curBit = false + } + + for i := 0; i < 8; i++ { + setMasked(pos.X, pos.Y, curBit, i, results[i].Set) + } + curBitNo++ + } + + lowestPenalty := ^uint(0) + lowestPenaltyIdx := -1 + for i := 0; i < 8; i++ { + p := results[i].calcPenalty() + if p < lowestPenalty { + lowestPenalty = p + lowestPenaltyIdx = i + } + } + return results[lowestPenaltyIdx] +} + +func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) { + switch mask { + case 0: + val = val != (((y + x) % 2) == 0) + break + case 1: + val = val != ((y % 2) == 0) + break + case 2: + val = val != ((x % 3) == 0) + break + case 3: + val = val != (((y + x) % 3) == 0) + break + case 4: + val = val != (((y/2 + x/3) % 2) == 0) + break + case 5: + val = val != (((y*x)%2)+((y*x)%3) == 0) + break + case 6: + val = val != ((((y*x)%2)+((y*x)%3))%2 == 0) + break + case 7: + val = val != ((((y+x)%2)+((y*x)%3))%2 == 0) + } + set(x, y, val) +} + +func iterateModules(occupied *qrcode) <-chan image.Point { + result := make(chan image.Point) + allPoints := make(chan image.Point) + go func() { + curX := occupied.dimension - 1 + curY := occupied.dimension - 1 + isUpward := true + + for true { + if isUpward { + allPoints <- image.Pt(curX, curY) + allPoints <- image.Pt(curX-1, curY) + curY-- + if curY < 0 { + curY = 0 + curX -= 2 + if curX == 6 { + curX-- + } + if curX < 0 { + break + } + isUpward = false + } + } else { + allPoints <- image.Pt(curX, curY) + allPoints <- image.Pt(curX-1, curY) + curY++ + if curY >= occupied.dimension { + curY = occupied.dimension - 1 + curX -= 2 + if curX == 6 { + curX-- + } + isUpward = true + if curX < 0 { + break + } + } + } + } + + close(allPoints) + }() + go func() { + for pt := range allPoints { + if !occupied.Get(pt.X, pt.Y) { + result <- pt + } + } + close(result) + }() + return result +} + +func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) { + dim := vi.modulWidth() + drawPattern := func(xoff int, yoff int) { + for x := -1; x < 8; x++ { + for y := -1; y < 8; y++ { + val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0) + + if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim { + set(x+xoff, y+yoff, val) + } + } + } + } + drawPattern(0, 0) + drawPattern(0, dim-7) + drawPattern(dim-7, 0) +} + +func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) { + drawPattern := func(xoff int, yoff int) { + for x := -2; x <= 2; x++ { + for y := -2; y <= 2; y++ { + val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0) + set(x+xoff, y+yoff, val) + } + } + } + positions := vi.alignmentPatternPlacements() + + for _, x := range positions { + for _, y := range positions { + if occupied.Get(x, y) { + continue + } + drawPattern(x, y) + } + } +} + +var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{ + L: { + 0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false}, + 1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true}, + 2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false}, + 3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true}, + 4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true}, + 5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false}, + 6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true}, + 7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false}, + }, + M: { + 0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false}, + 1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true}, + 2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false}, + 3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true}, + 4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true}, + 5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false}, + 6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true}, + 7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false}, + }, + Q: { + 0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true}, + 1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false}, + 2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true}, + 3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false}, + 4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false}, + 5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true}, + 6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false}, + 7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true}, + }, + H: { + 0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true}, + 1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false}, + 2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true}, + 3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false}, + 4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false}, + 5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true}, + 6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false}, + 7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true}, + }, +} + +func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) { + var formatInfo []bool + + if usedMask == -1 { + formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask. + } else { + formatInfo = formatInfos[vi.Level][usedMask] + } + + if len(formatInfo) == 15 { + dim := vi.modulWidth() + set(0, 8, formatInfo[0]) + set(1, 8, formatInfo[1]) + set(2, 8, formatInfo[2]) + set(3, 8, formatInfo[3]) + set(4, 8, formatInfo[4]) + set(5, 8, formatInfo[5]) + set(7, 8, formatInfo[6]) + set(8, 8, formatInfo[7]) + set(8, 7, formatInfo[8]) + set(8, 5, formatInfo[9]) + set(8, 4, formatInfo[10]) + set(8, 3, formatInfo[11]) + set(8, 2, formatInfo[12]) + set(8, 1, formatInfo[13]) + set(8, 0, formatInfo[14]) + + set(8, dim-1, formatInfo[0]) + set(8, dim-2, formatInfo[1]) + set(8, dim-3, formatInfo[2]) + set(8, dim-4, formatInfo[3]) + set(8, dim-5, formatInfo[4]) + set(8, dim-6, formatInfo[5]) + set(8, dim-7, formatInfo[6]) + set(dim-8, 8, formatInfo[7]) + set(dim-7, 8, formatInfo[8]) + set(dim-6, 8, formatInfo[9]) + set(dim-5, 8, formatInfo[10]) + set(dim-4, 8, formatInfo[11]) + set(dim-3, 8, formatInfo[12]) + set(dim-2, 8, formatInfo[13]) + set(dim-1, 8, formatInfo[14]) + } +} + +var versionInfoBitsByVersion = map[byte][]bool{ + 7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false}, + 8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false}, + 9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true}, + 10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true}, + 11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false}, + 12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false}, + 13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true}, + 14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true}, + 15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false}, + 16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false}, + 17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true}, + 18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true}, + 19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false}, + 20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false}, + 21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true}, + 22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true}, + 23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false}, + 24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false}, + 25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true}, + 26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true}, + 27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false}, + 28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false}, + 29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true}, + 30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true}, + 31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false}, + 32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true}, + 33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false}, + 34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false}, + 35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true}, + 36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true}, + 37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false}, + 38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false}, + 39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true}, + 40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true}, +} + +func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) { + versionInfoBits, ok := versionInfoBitsByVersion[vi.Version] + + if ok && len(versionInfoBits) > 0 { + for i := 0; i < len(versionInfoBits); i++ { + x := (vi.modulWidth() - 11) + i%3 + y := i / 3 + set(x, y, versionInfoBits[len(versionInfoBits)-i-1]) + set(y, x, versionInfoBits[len(versionInfoBits)-i-1]) + } + } + +} + +func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) { + for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ { + bl.AddBit(false) + } + + for bl.Len()%8 != 0 { + bl.AddBit(false) + } + + for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ { + if i%2 == 0 { + bl.AddByte(236) + } else { + bl.AddByte(17) + } + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/encoder_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/encoder_test.go new file mode 100644 index 00000000..10479b6f --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/encoder_test.go @@ -0,0 +1,134 @@ +package qr + +import ( + "fmt" + "image/png" + "os" + "testing" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" +) + +type test struct { + Text string + Mode Encoding + ECL ErrorCorrectionLevel + Result string +} + +var tests = []test{ + test{ + Text: "hello world", + Mode: Unicode, + ECL: H, + Result: ` ++++++++.+.+.+...+.+++++++ ++.....+.++...+++..+.....+ ++.+++.+.+.+.++.++.+.+++.+ ++.+++.+....++.++..+.+++.+ ++.+++.+..+...++.+.+.+++.+ ++.....+.+..+..+++.+.....+ ++++++++.+.+.+.+.+.+++++++ +........++..+..+......... +..+++.+.+++.+.++++++..+++ ++++..+..+...++.+...+..+.. ++...+.++++....++.+..++.++ +++.+.+.++...+...+.+....++ +..+..+++.+.+++++.++++++++ ++.+++...+..++..++..+..+.. ++.....+..+.+.....+++++.++ ++.+++.....+...+.+.+++...+ ++.+..+++...++.+.+++++++.. +........+....++.+...+.+.. ++++++++......++++.+.+.+++ ++.....+....+...++...++.+. ++.+++.+.+.+...+++++++++.. ++.+++.+.++...++...+.++..+ ++.+++.+.++.+++++..++.+..+ ++.....+..+++..++.+.++...+ ++++++++....+..+.+..+..+++`, + }, +} + +func Test_GetUnknownEncoder(t *testing.T) { + if unknownEncoding.getEncoder() != nil { + t.Fail() + } +} + +func Test_EncodingStringer(t *testing.T) { + tests := map[Encoding]string{ + Auto: "Auto", + Numeric: "Numeric", + AlphaNumeric: "AlphaNumeric", + Unicode: "Unicode", + unknownEncoding: "", + } + + for enc, str := range tests { + if enc.String() != str { + t.Fail() + } + } +} + +func Test_InvalidEncoding(t *testing.T) { + _, err := Encode("hello world", H, Numeric) + if err == nil { + t.Fail() + } +} + +func imgStrToBools(str string) []bool { + res := make([]bool, 0, len(str)) + for _, r := range str { + if r == '+' { + res = append(res, true) + } else if r == '.' { + res = append(res, false) + } + } + return res +} + +func Test_Encode(t *testing.T) { + for _, tst := range tests { + res, err := Encode(tst.Text, tst.ECL, tst.Mode) + if err != nil { + t.Error(err) + } + qrCode, ok := res.(*qrcode) + if !ok { + t.Fail() + } + testRes := imgStrToBools(tst.Result) + if (qrCode.dimension * qrCode.dimension) != len(testRes) { + t.Fail() + } + t.Logf("dim %d", qrCode.dimension) + for i := 0; i < len(testRes); i++ { + x := i % qrCode.dimension + y := i / qrCode.dimension + if qrCode.Get(x, y) != testRes[i] { + t.Errorf("Failed at index %d", i) + } + } + } +} + +func ExampleEncode() { + f, _ := os.Create("qrcode.png") + defer f.Close() + + qrcode, err := Encode("hello world", L, Auto) + if err != nil { + fmt.Println(err) + } else { + qrcode, err = barcode.Scale(qrcode, 100, 100) + if err != nil { + fmt.Println(err) + } else { + png.Encode(f, qrcode) + } + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/errorcorrection.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/errorcorrection.go new file mode 100644 index 00000000..88da1038 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/errorcorrection.go @@ -0,0 +1,47 @@ +package qr + +import ( + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type errorCorrection struct { + fld *utils.GaloisField + polynomes []*utils.GFPoly +} + +var ec = newGF() + +func newGF() *errorCorrection { + fld := utils.NewGaloisField(285) + + return &errorCorrection{fld, + []*utils.GFPoly{ + utils.NewGFPoly(fld, []byte{1}), + }, + } +} + +func (ec *errorCorrection) getPolynomial(degree int) *utils.GFPoly { + if degree >= len(ec.polynomes) { + last := ec.polynomes[len(ec.polynomes)-1] + for d := len(ec.polynomes); d <= degree; d++ { + next := last.Multiply(utils.NewGFPoly(ec.fld, []byte{1, byte(ec.fld.ALogTbl[d-1])})) + ec.polynomes = append(ec.polynomes, next) + last = next + } + } + return ec.polynomes[degree] +} + +func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte { + generator := ec.getPolynomial(int(eccCount)) + info := utils.NewGFPoly(ec.fld, data) + info = info.MultByMonominal(int(eccCount), 1) + + _, remainder := info.Divide(generator) + + result := make([]byte, eccCount) + numZero := int(eccCount) - len(remainder.Coefficients) + copy(result[numZero:], remainder.Coefficients) + return result +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/errorcorrection_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/errorcorrection_test.go new file mode 100644 index 00000000..56ed3a14 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/errorcorrection_test.go @@ -0,0 +1,73 @@ +package qr + +import ( + "bytes" + "testing" +) + +func Test_LogTables(t *testing.T) { + for i := 1; i <= 255; i++ { + tmp := ec.fld.LogTbl[i] + if i != ec.fld.ALogTbl[tmp] { + t.Errorf("Invalid LogTables: %d", i) + } + } + + if ec.fld.ALogTbl[11] != 232 || ec.fld.ALogTbl[87] != 127 || ec.fld.ALogTbl[225] != 36 { + t.Fail() + } +} + +func Test_ErrorCorrection(t *testing.T) { + doTest := func(b []byte, ecc []byte) { + cnt := byte(len(ecc)) + res := ec.calcECC(b, cnt) + if bytes.Compare(res, ecc) != 0 { + t.Errorf("ECC error!\nGot: %v\nExpected:%v", res, ecc) + } + } + // Issue #5 + doTest([]byte{66, 196, 148, 21, 99, 19, 151, 151, 53, 149, 54, 195, 4, 133, 87, 84, 115, 85, 22, 148, 52, 71, 102, 68, 134, 182, 247, 119, 22, 68, 117, 134, 35, 4, 134, 38, 21, 84, 21, 117, 87, 164, 135, 115, 211, 208, 236, 17, 236, 17, 236, 17, 236, 17, 236}, []byte{187, 187, 171, 253, 164, 129, 104, 133, 3, 75, 87, 98, 241, 146, 138}) + + // Other tests + doTest([]byte{17, 168, 162, 241, 255, 205, 240, 179, 88, 101, 71, 130, 2, 54, 147, 111, 232, 58, 202, 171, 85, 22, 229, 187}, []byte{30, 142, 171, 131, 189}) + doTest([]byte{36, 153, 55, 100, 228, 252, 0, 35, 85, 7, 237, 117, 182, 73, 83, 244, 8, 64, 55, 252, 200, 250, 72, 92, 97, 125, 96}, []byte{129, 124, 218, 148, 49, 108, 68, 255, 58, 212, 56, 60, 142, 45, 216, 124, 253, 214, 206, 208, 145, 169, 43}) + doTest([]byte{250, 195, 230, 128, 31, 168, 86, 123, 244, 129, 74, 130, 222, 225, 140, 129, 114, 132, 128, 88, 96, 13, 165, 132, 116, 22, 42, 81, 219, 3, 102, 156, 69, 70, 90, 68, 7, 245, 150, 160, 252, 121, 20}, []byte{124, 23, 233, 71, 200, 211, 54, 141, 10, 23, 206, 147, 116, 35, 45, 218, 158, 193, 80, 194, 129, 147, 8, 78, 229, 112, 89, 161, 167, 203, 11, 245, 186, 187, 17, 7, 175}) + doTest([]byte{121, 234, 24, 188, 218, 238, 248, 223, 98, 124, 237, 30, 98, 12, 9, 126, 5, 160, 240, 27, 174, 60, 152, 134, 71, 122, 125, 238, 223, 91, 231, 248, 230, 152, 250, 44, 17, 149, 0, 20, 109, 188, 227, 202}, []byte{209, 71, 225, 216, 240, 127, 111, 98, 194, 133, 114, 63, 35, 167, 184, 4, 209, 211, 40, 14, 74, 37, 21, 76, 95, 206, 90, 152, 110, 64, 6, 92, 80, 255, 127, 35, 111, 25, 1, 73}) + doTest([]byte{165, 233, 141, 34, 247, 216, 35, 163, 61, 61, 81, 146, 116, 96, 113, 10, 0, 6, 148, 244, 55, 201, 17, 220, 109, 111}, []byte{93, 173, 231, 160}) + doTest([]byte{173, 242, 89, 205, 24, 33, 213, 147, 96, 189, 100, 15, 213, 67, 91, 189, 218, 127, 32, 160, 162, 99, 187, 221, 53, 121, 238, 219, 215, 176, 181, 135, 56, 71, 246, 74, 228}, []byte{194, 130, 43, 168, 223, 144, 223, 49, 5, 162, 62, 218, 50, 205, 249, 84, 188, 25, 109, 110, 49, 224, 194, 244, 83, 221, 236, 71, 197, 159, 182}) + doTest([]byte{82, 138, 221, 169, 67, 161, 132, 31, 243, 110, 83, 1, 238, 79, 255, 57, 74, 54, 123, 151, 159, 50, 250, 188, 176, 8, 221, 215, 141, 77, 16}, []byte{197, 122, 225, 65, 40, 69, 153, 100, 73, 245, 150, 213, 104, 127, 3}) + doTest([]byte{5, 206, 21, 196, 185, 120, 60, 177, 90, 251, 109, 131, 174, 199, 55, 56, 14, 171, 19, 104, 236, 218, 31, 144, 33, 249, 58, 195, 173, 145, 166, 93, 122, 171, 232, 128, 233, 116, 144, 189, 62, 230, 68, 55, 140, 56, 1, 65, 165, 158, 127}, []byte{73, 141, 230, 252, 225, 173, 251, 194, 150, 98, 141, 241, 246, 11, 16, 8, 42}) + doTest([]byte{112, 106, 43, 174, 133, 163, 192, 61, 121, 3, 200, 84, 15, 9, 3, 222, 183, 78, 153, 26, 85, 41, 5, 149, 232, 3, 233, 247, 249, 29, 15, 18, 4, 96, 9, 64, 188, 210}, []byte{16, 254, 143, 110, 63, 167, 213, 242, 95, 78, 215, 145, 231, 59, 158, 36, 149, 247, 123, 114, 247, 202, 15, 56, 229, 163, 186, 73, 82, 230, 111, 108, 111, 182, 193, 46, 116}) + doTest([]byte{208, 128, 197, 227, 124, 226, 125, 46, 253, 98, 238, 80, 229, 134, 167, 70, 101, 150, 198, 130, 185, 200, 68, 91}, []byte{229, 167, 187, 39, 92, 90, 210, 25, 206, 237, 90, 194, 206, 39, 2, 11, 78, 48, 247}) + doTest([]byte{79, 175, 255, 194, 34, 229, 234, 200, 74, 213, 100, 33, 24, 5, 133, 186, 249, 151, 46, 190, 44, 126, 184, 195, 219, 37, 11, 225, 23, 8, 59, 106, 239, 198, 146, 205, 47, 59, 63, 9, 102, 29, 60, 209, 226, 67, 126, 193, 252, 255, 206, 172, 44, 53, 137, 209, 246}, []byte{237, 8, 12, 44, 90, 243, 24, 100, 123, 216, 185, 91, 182, 60, 9, 145, 126, 254, 139, 24, 211, 150, 219, 28, 138, 197, 13, 109, 227, 31, 60, 128, 237, 181, 183, 2, 138, 232, 112, 5}) + doTest([]byte{253, 217, 8, 176, 66, 153, 249, 49, 82, 114, 184, 139, 190, 87}, []byte{28, 55, 193, 193, 179, 246, 222, 5, 95, 96, 13, 242}) + doTest([]byte{15, 65, 231, 224, 151, 167, 74, 228, 23}, []byte{200, 90, 82}) + doTest([]byte{61, 186, 61, 193, 215, 243, 84, 66, 48, 93, 108, 249, 55, 232}, []byte{0, 180, 53, 152, 134, 252, 165, 168}) + doTest([]byte{78, 68, 116, 15, 85}, []byte{36}) + doTest([]byte{122, 143}, []byte{245}) + doTest([]byte{78, 85, 143, 35}, []byte{226, 85}) + doTest([]byte{11, 188, 118, 21, 177, 224, 151, 105, 21, 245, 251, 162, 72, 175, 248, 134, 123, 251, 160, 163, 42, 57, 53, 222, 195, 49, 199, 151, 5, 236, 160, 57, 212, 241, 44, 43}, []byte{186, 106}) + doTest([]byte{157, 99, 220, 166, 63, 18, 225, 215, 71, 95, 99, 200, 218, 147, 131, 245, 222, 209, 135, 152, 82, 128, 24, 0, 100, 40, 84, 193, 205, 86, 130, 204, 235, 100, 94, 61}, []byte{41, 171, 66, 233}) + doTest([]byte{249, 34, 253, 235, 233, 104, 52, 60, 17, 13, 182, 223, 19, 91, 164, 2, 196, 29, 74, 219, 65, 23, 190, 31, 10, 241, 221, 150, 221, 118, 53, 69, 45, 90, 215, 100, 155, 102, 150, 176, 203, 39, 22, 70, 10, 238}, []byte{161, 49, 179, 149, 178, 146, 208, 144, 19, 158, 180, 152, 243, 138, 143, 243, 82, 112, 229, 10, 113, 255, 139, 246}) + doTest([]byte{39, 232, 159, 64, 242, 235, 66, 226, 100, 221, 225, 247, 139, 157, 95, 155}, []byte{41, 9, 244}) + doTest([]byte{177, 185, 131, 64, 103, 93, 134, 153, 15, 26, 0, 119, 21, 27, 174, 181, 111, 245, 214, 244, 83, 66, 24, 244, 255, 189, 133, 158, 37, 46, 199, 123, 110, 153, 61, 137, 163, 231, 129, 65, 186, 89, 219, 39, 226, 236, 199, 197, 73, 213}, []byte{37, 59, 125, 211, 249, 177, 107, 79, 107, 47, 242, 168, 49, 38, 168, 198, 199, 91, 212, 22, 107, 244}) + doTest([]byte{196, 226, 29, 110, 161, 143, 64, 169, 216, 231, 115}, []byte{253, 93, 218, 129, 37}) + doTest([]byte{133, 8, 124, 221, 36, 17, 135, 115, 149, 58, 250, 103, 241, 18, 19, 246, 191, 85, 80, 255, 93, 182, 140, 123, 206, 232, 20, 166, 216, 105, 210, 229, 249, 212, 93, 227, 75, 231, 36, 195, 166, 246, 47, 168, 35, 7, 176, 124, 44, 179, 24, 145}, []byte{78, 57, 134, 181, 215, 149, 111, 51, 172, 58, 114, 3, 140, 186, 126, 40, 190}) + doTest([]byte{245, 206, 124, 0, 15, 59, 253, 225, 155}, []byte{65, 14, 188, 213, 18, 113, 161, 16}) + doTest([]byte{20, 109, 28, 180, 48, 170, 216, 48, 140, 89, 103}, []byte{193, 147, 50, 209, 160}) + doTest([]byte{87, 198, 56, 151, 121, 37, 81, 64, 193, 24, 222, 142, 102, 74, 216, 233, 198, 197, 90, 4, 65, 14, 154, 147, 200, 252, 8, 64, 97, 150, 136, 141}, []byte{231, 190, 32, 90, 100, 40, 41, 103, 200, 200, 243, 75, 177, 7, 93, 28, 83, 47, 188, 236, 20, 95, 69, 104, 155, 102, 110, 197}) + doTest([]byte{168, 72, 2, 101, 103, 118, 218, 38, 82, 85, 62, 37, 201, 96, 255, 71, 198}, []byte{129, 33, 28, 228, 195, 120, 101, 46, 119, 126}) + doTest([]byte{130, 162, 73, 44, 165, 207, 124, 28, 17, 223, 43, 143, 81, 70, 205, 161, 143, 230, 97, 94, 228, 41, 26, 187, 69, 85, 162, 51, 168, 64, 26, 207, 245, 128}, []byte{6, 171}) + doTest([]byte{95, 28, 93, 149, 234, 89, 201, 71, 39, 197, 236, 223, 251, 190, 112, 96, 101, 53, 40, 88, 136, 141, 230, 80, 45, 73, 116, 208, 197, 91, 154, 209, 128, 214, 66, 114, 137, 204, 115, 139, 96, 211, 148, 127, 104, 194}, []byte{10, 102, 57, 95, 61, 212, 130, 71, 74, 58, 82, 115, 238, 213, 251, 184, 203, 250, 55, 186, 37, 16, 71, 247, 146, 194, 74, 208, 221, 6, 81, 172, 204, 73, 102, 40, 247, 174, 213, 37, 225, 246, 8, 58}) + doTest([]byte{207, 185, 106, 191, 87, 109, 110, 210, 54, 12, 103, 161, 228}, []byte{214, 138, 159, 195, 154, 236, 33, 243, 53, 79, 227}) + doTest([]byte{203, 43, 26, 94, 37, 123, 254, 215, 153, 193, 157, 248, 180, 249, 103, 232, 107, 17, 138, 0, 11, 240, 218, 122, 19, 103, 112, 60, 125, 100, 209, 166, 103, 81, 200, 84, 77, 100, 18, 110, 209, 225, 209, 254, 185, 116, 186, 216, 206, 36, 252, 144, 90, 247, 117, 219, 81, 160}, []byte{185, 176, 106, 253, 76, 153, 185, 211, 187, 153, 210, 31, 99, 4, 46, 145, 221, 99, 236, 19, 126, 138, 66, 26, 40, 217, 170, 217, 147}) + doTest([]byte{11, 193, 90, 52, 239, 247, 144, 99, 48, 19, 154, 6, 255, 28, 47, 41, 30, 220}, []byte{235, 165, 125, 82, 28, 116, 21, 133, 243, 222, 241, 20, 134}) + doTest([]byte{173, 151, 109, 88, 104, 65, 76, 111, 219, 237, 2, 173, 25, 84, 98, 16, 135, 157, 14, 194, 228, 86, 167, 187, 137, 245, 144, 61, 200, 76, 188, 117, 223, 172, 16, 116, 84, 1, 203, 173, 170, 32, 135, 67, 16}, []byte{150, 31, 11, 211, 82, 221, 251, 84, 254, 121, 68, 34, 211, 142, 197, 246, 138, 204, 60, 197, 210, 238, 142, 234, 187, 200, 179, 228}) + doTest([]byte{171, 185, 30, 162, 129, 205, 254, 186, 86, 239, 178, 206, 115, 177, 14, 166, 143, 48, 141, 205, 109, 67, 238, 187, 134, 210, 96, 23, 195, 206, 100, 171, 156, 8, 229, 131, 169, 169, 59, 167, 224, 241, 185, 132, 162, 50, 87, 252, 156, 122, 248, 19, 130, 31, 127}, []byte{62, 42, 216, 109, 23, 176, 255, 137, 139, 90, 7, 186, 175, 243, 160, 206, 37, 94, 157, 217, 11, 169, 126, 41, 73, 133, 212, 232, 249, 117, 70, 147, 137, 156, 43, 243, 234, 155, 94, 38, 59, 211, 218, 165, 3, 33, 231, 237, 92, 16, 128}) + doTest([]byte{98, 28, 174, 108, 231, 247, 135, 139, 6, 50, 107, 203, 138, 252, 229, 245, 230, 236, 124, 138, 105, 25, 83, 122}, []byte{97, 214, 25, 2, 14, 48, 65, 212, 241, 200, 81, 57, 176, 59, 16, 55, 20, 91, 66}) + doTest([]byte{73, 214, 80, 41, 125, 136, 126, 184, 70, 141, 140, 58, 249, 250, 49, 249, 155, 0, 236, 49, 17, 125, 18, 29}, []byte{128, 16, 47, 235, 125, 128, 97, 245, 177, 210, 219, 195}) + doTest([]byte{3, 220, 98, 73, 200, 52, 8, 107, 173, 177, 58, 221, 180, 226, 76, 210, 182, 88, 104, 171, 243, 129, 88, 112, 126, 83, 141, 50, 106, 204, 195, 51, 141, 75, 132, 161}, []byte{110, 178, 213, 174, 1, 241, 95}) + doTest([]byte{196, 88, 50, 142, 76, 128, 190, 189, 76, 9, 228, 62, 198, 186, 180, 240, 62, 130, 132, 242}, []byte{244, 89, 17, 143, 3, 180, 150, 242, 167, 214, 209, 133, 120, 213, 173, 59, 25, 158, 251}) + doTest([]byte{166, 214, 1, 225, 237, 7, 80, 104, 94, 170, 125, 184, 148, 16, 121, 101, 52, 216, 177, 192, 6, 132, 77, 44, 5, 9, 126, 156, 12, 2, 29, 99, 51, 78, 177, 92, 140, 107, 146, 183, 109, 227, 171, 57, 193, 14, 37}, []byte{245, 46, 189, 11, 202, 195, 89, 53, 215, 172, 132, 196, 145, 141, 239, 160, 242, 7, 85, 251, 193, 85}) +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/numeric.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/numeric.go new file mode 100644 index 00000000..044124dd --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/numeric.go @@ -0,0 +1,56 @@ +package qr + +import ( + "errors" + "fmt" + "strconv" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) { + contentBitCount := (len(content) / 3) * 10 + switch len(content) % 3 { + case 1: + contentBitCount += 4 + case 2: + contentBitCount += 7 + } + vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount) + if vi == nil { + return nil, nil, errors.New("To much data to encode") + } + res := new(utils.BitList) + res.AddBits(int(numericMode), 4) + res.AddBits(len(content), vi.charCountBits(numericMode)) + + for pos := 0; pos < len(content); pos += 3 { + var curStr string + if pos+3 <= len(content) { + curStr = content[pos : pos+3] + } else { + curStr = content[pos:] + } + + i, err := strconv.Atoi(curStr) + if err != nil || i < 0 { + return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric) + } + var bitCnt byte + switch len(curStr) % 3 { + case 0: + bitCnt = 10 + case 1: + bitCnt = 4 + break + case 2: + bitCnt = 7 + break + } + + res.AddBits(i, bitCnt) + } + + addPaddingAndTerminator(res, vi) + return res, vi, nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/numeric_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/numeric_test.go new file mode 100644 index 00000000..21aa9e34 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/numeric_test.go @@ -0,0 +1,26 @@ +package qr + +import ( + "bytes" + "testing" +) + +func Test_NumericEncoding(t *testing.T) { + encode := Numeric.getEncoder() + x, vi, err := encode("01234567", H) + if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{16, 32, 12, 86, 97, 128, 236, 17, 236}) != 0 { + t.Error("\"01234567\" failed to encode") + } + x, vi, err = encode("0123456789012345", H) + if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{16, 64, 12, 86, 106, 110, 20, 234, 80}) != 0 { + t.Error("\"0123456789012345\" failed to encode") + } + x, vi, err = encode("foo", H) + if err == nil { + t.Error("Numeric encoding should not be able to encode \"foo\"") + } + x, vi, err = encode(makeString(14297, "1"), H) + if x != nil || vi != nil || err == nil { + t.Fail() + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/qrcode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/qrcode.go new file mode 100644 index 00000000..f54e1e16 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/qrcode.go @@ -0,0 +1,166 @@ +package qr + +import ( + "image" + "image/color" + "math" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +type qrcode struct { + dimension int + data *utils.BitList + content string +} + +func (qr *qrcode) Content() string { + return qr.content +} + +func (qr *qrcode) Metadata() barcode.Metadata { + return barcode.Metadata{"QR Code", 2} +} + +func (qr *qrcode) ColorModel() color.Model { + return color.Gray16Model +} + +func (qr *qrcode) Bounds() image.Rectangle { + return image.Rect(0, 0, qr.dimension, qr.dimension) +} + +func (qr *qrcode) At(x, y int) color.Color { + if qr.Get(x, y) { + return color.Black + } + return color.White +} + +func (qr *qrcode) Get(x, y int) bool { + return qr.data.GetBit(x*qr.dimension + y) +} + +func (qr *qrcode) Set(x, y int, val bool) { + qr.data.SetBit(x*qr.dimension+y, val) +} + +func (qr *qrcode) calcPenalty() uint { + return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4() +} + +func (qr *qrcode) calcPenaltyRule1() uint { + var result uint + for x := 0; x < qr.dimension; x++ { + checkForX := false + var cntX uint + checkForY := false + var cntY uint + + for y := 0; y < qr.dimension; y++ { + if qr.Get(x, y) == checkForX { + cntX++ + } else { + checkForX = !checkForX + if cntX >= 5 { + result += cntX - 2 + } + cntX = 1 + } + + if qr.Get(y, x) == checkForY { + cntY++ + } else { + checkForY = !checkForY + if cntY >= 5 { + result += cntY - 2 + } + cntY = 1 + } + } + + if cntX >= 5 { + result += cntX - 2 + } + if cntY >= 5 { + result += cntY - 2 + } + } + + return result +} + +func (qr *qrcode) calcPenaltyRule2() uint { + var result uint + for x := 0; x < qr.dimension-1; x++ { + for y := 0; y < qr.dimension-1; y++ { + check := qr.Get(x, y) + if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check { + result += 3 + } + } + } + return result +} + +func (qr *qrcode) calcPenaltyRule3() uint { + pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false} + pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true} + + var result uint + for x := 0; x <= qr.dimension-len(pattern1); x++ { + for y := 0; y < qr.dimension; y++ { + pattern1XFound := true + pattern2XFound := true + pattern1YFound := true + pattern2YFound := true + + for i := 0; i < len(pattern1); i++ { + iv := qr.Get(x+i, y) + if iv != pattern1[i] { + pattern1XFound = false + } + if iv != pattern2[i] { + pattern2XFound = false + } + iv = qr.Get(y, x+i) + if iv != pattern1[i] { + pattern1YFound = false + } + if iv != pattern2[i] { + pattern2YFound = false + } + } + if pattern1XFound || pattern2XFound { + result += 40 + } + if pattern1YFound || pattern2YFound { + result += 40 + } + } + } + + return result +} + +func (qr *qrcode) calcPenaltyRule4() uint { + totalNum := qr.data.Len() + trueCnt := 0 + for i := 0; i < totalNum; i++ { + if qr.data.GetBit(i) { + trueCnt++ + } + } + percDark := float64(trueCnt) * 100 / float64(totalNum) + floor := math.Abs(math.Floor(percDark/5) - 10) + ceil := math.Abs(math.Ceil(percDark/5) - 10) + return uint(math.Min(floor, ceil) * 10) +} + +func newBarcode(dim int) *qrcode { + res := new(qrcode) + res.dimension = dim + res.data = utils.NewBitList(dim * dim) + return res +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/qrcode_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/qrcode_test.go new file mode 100644 index 00000000..8cc17e3e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/qrcode_test.go @@ -0,0 +1,126 @@ +package qr + +import ( + "image/color" + "testing" +) + +func Test_NewQRCode(t *testing.T) { + bc := newBarcode(2) + if bc == nil { + t.Fail() + } + if bc.data.Len() != 4 { + t.Fail() + } + if bc.dimension != 2 { + t.Fail() + } +} + +func Test_QRBasics(t *testing.T) { + qr := newBarcode(10) + if qr.ColorModel() != color.Gray16Model { + t.Fail() + } + code, _ := Encode("test", L, Unicode) + if code.Content() != "test" { + t.Fail() + } + if code.Metadata().Dimensions != 2 { + t.Fail() + } + bounds := code.Bounds() + if bounds.Min.X != 0 || bounds.Min.Y != 0 || bounds.Max.X != 21 || bounds.Max.Y != 21 { + t.Fail() + } + if code.At(0, 0) != color.Black || code.At(0, 7) != color.White { + t.Fail() + } + qr = code.(*qrcode) + if !qr.Get(0, 0) || qr.Get(0, 7) { + t.Fail() + } + sum := qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4() + if qr.calcPenalty() != sum { + t.Fail() + } +} + +func Test_Penalty1(t *testing.T) { + qr := newBarcode(7) + if qr.calcPenaltyRule1() != 70 { + t.Fail() + } + qr.Set(0, 0, true) + if qr.calcPenaltyRule1() != 68 { + t.Fail() + } + qr.Set(0, 6, true) + if qr.calcPenaltyRule1() != 66 { + t.Fail() + } +} + +func Test_Penalty2(t *testing.T) { + qr := newBarcode(3) + if qr.calcPenaltyRule2() != 12 { + t.Fail() + } + qr.Set(0, 0, true) + qr.Set(1, 1, true) + qr.Set(2, 0, true) + if qr.calcPenaltyRule2() != 0 { + t.Fail() + } + qr.Set(1, 1, false) + if qr.calcPenaltyRule2() != 6 { + t.Fail() + } +} + +func Test_Penalty3(t *testing.T) { + runTest := func(content string, result uint) { + code, _ := Encode(content, L, AlphaNumeric) + qr := code.(*qrcode) + if qr.calcPenaltyRule3() != result { + t.Errorf("Failed Penalty Rule 3 for content \"%s\" got %d but expected %d", content, qr.calcPenaltyRule3(), result) + } + } + runTest("A", 80) + runTest("FOO", 40) + runTest("0815", 0) +} + +func Test_Penalty4(t *testing.T) { + qr := newBarcode(3) + if qr.calcPenaltyRule4() != 100 { + t.Fail() + } + qr.Set(0, 0, true) + if qr.calcPenaltyRule4() != 70 { + t.Fail() + } + qr.Set(0, 1, true) + if qr.calcPenaltyRule4() != 50 { + t.Fail() + } + qr.Set(0, 2, true) + if qr.calcPenaltyRule4() != 30 { + t.Fail() + } + qr.Set(1, 0, true) + if qr.calcPenaltyRule4() != 10 { + t.Fail() + } + qr.Set(1, 1, true) + if qr.calcPenaltyRule4() != 10 { + t.Fail() + } + qr = newBarcode(2) + qr.Set(0, 0, true) + qr.Set(1, 0, true) + if qr.calcPenaltyRule4() != 0 { + t.Fail() + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/unicode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/unicode.go new file mode 100644 index 00000000..23416334 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/unicode.go @@ -0,0 +1,27 @@ +package qr + +import ( + "errors" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) { + data := []byte(content) + + vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8) + if vi == nil { + return nil, nil, errors.New("To much data to encode") + } + + // It's not correct to add the unicode bytes to the result directly but most readers can't handle the + // required ECI header... + res := new(utils.BitList) + res.AddBits(int(byteMode), 4) + res.AddBits(len(content), vi.charCountBits(byteMode)) + for _, b := range data { + res.AddByte(b) + } + addPaddingAndTerminator(res, vi) + return res, vi, nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/unicode_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/unicode_test.go new file mode 100644 index 00000000..76e5fbfe --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/unicode_test.go @@ -0,0 +1,18 @@ +package qr + +import ( + "bytes" + "testing" +) + +func Test_UnicodeEncoding(t *testing.T) { + encode := Unicode.getEncoder() + x, vi, err := encode("A", H) // 65 + if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{64, 20, 16, 236, 17, 236, 17, 236, 17}) != 0 { + t.Errorf("\"A\" failed to encode: %s", err) + } + _, _, err = encode(makeString(3000, "A"), H) + if err == nil { + t.Error("Unicode encoding should not be able to encode a 3kb string") + } +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/versioninfo.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/versioninfo.go new file mode 100644 index 00000000..6852a576 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/versioninfo.go @@ -0,0 +1,310 @@ +package qr + +import "math" + +// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code +type ErrorCorrectionLevel byte + +const ( + // L recovers 7% of data + L ErrorCorrectionLevel = iota + // M recovers 15% of data + M + // Q recovers 25% of data + Q + // H recovers 30% of data + H +) + +func (ecl ErrorCorrectionLevel) String() string { + switch ecl { + case L: + return "L" + case M: + return "M" + case Q: + return "Q" + case H: + return "H" + } + return "unknown" +} + +type encodingMode byte + +const ( + numericMode encodingMode = 1 + alphaNumericMode encodingMode = 2 + byteMode encodingMode = 4 + kanjiMode encodingMode = 8 +) + +type versionInfo struct { + Version byte + Level ErrorCorrectionLevel + ErrorCorrectionCodewordsPerBlock byte + NumberOfBlocksInGroup1 byte + DataCodeWordsPerBlockInGroup1 byte + NumberOfBlocksInGroup2 byte + DataCodeWordsPerBlockInGroup2 byte +} + +var versionInfos = []*versionInfo{ + &versionInfo{1, L, 7, 1, 19, 0, 0}, + &versionInfo{1, M, 10, 1, 16, 0, 0}, + &versionInfo{1, Q, 13, 1, 13, 0, 0}, + &versionInfo{1, H, 17, 1, 9, 0, 0}, + &versionInfo{2, L, 10, 1, 34, 0, 0}, + &versionInfo{2, M, 16, 1, 28, 0, 0}, + &versionInfo{2, Q, 22, 1, 22, 0, 0}, + &versionInfo{2, H, 28, 1, 16, 0, 0}, + &versionInfo{3, L, 15, 1, 55, 0, 0}, + &versionInfo{3, M, 26, 1, 44, 0, 0}, + &versionInfo{3, Q, 18, 2, 17, 0, 0}, + &versionInfo{3, H, 22, 2, 13, 0, 0}, + &versionInfo{4, L, 20, 1, 80, 0, 0}, + &versionInfo{4, M, 18, 2, 32, 0, 0}, + &versionInfo{4, Q, 26, 2, 24, 0, 0}, + &versionInfo{4, H, 16, 4, 9, 0, 0}, + &versionInfo{5, L, 26, 1, 108, 0, 0}, + &versionInfo{5, M, 24, 2, 43, 0, 0}, + &versionInfo{5, Q, 18, 2, 15, 2, 16}, + &versionInfo{5, H, 22, 2, 11, 2, 12}, + &versionInfo{6, L, 18, 2, 68, 0, 0}, + &versionInfo{6, M, 16, 4, 27, 0, 0}, + &versionInfo{6, Q, 24, 4, 19, 0, 0}, + &versionInfo{6, H, 28, 4, 15, 0, 0}, + &versionInfo{7, L, 20, 2, 78, 0, 0}, + &versionInfo{7, M, 18, 4, 31, 0, 0}, + &versionInfo{7, Q, 18, 2, 14, 4, 15}, + &versionInfo{7, H, 26, 4, 13, 1, 14}, + &versionInfo{8, L, 24, 2, 97, 0, 0}, + &versionInfo{8, M, 22, 2, 38, 2, 39}, + &versionInfo{8, Q, 22, 4, 18, 2, 19}, + &versionInfo{8, H, 26, 4, 14, 2, 15}, + &versionInfo{9, L, 30, 2, 116, 0, 0}, + &versionInfo{9, M, 22, 3, 36, 2, 37}, + &versionInfo{9, Q, 20, 4, 16, 4, 17}, + &versionInfo{9, H, 24, 4, 12, 4, 13}, + &versionInfo{10, L, 18, 2, 68, 2, 69}, + &versionInfo{10, M, 26, 4, 43, 1, 44}, + &versionInfo{10, Q, 24, 6, 19, 2, 20}, + &versionInfo{10, H, 28, 6, 15, 2, 16}, + &versionInfo{11, L, 20, 4, 81, 0, 0}, + &versionInfo{11, M, 30, 1, 50, 4, 51}, + &versionInfo{11, Q, 28, 4, 22, 4, 23}, + &versionInfo{11, H, 24, 3, 12, 8, 13}, + &versionInfo{12, L, 24, 2, 92, 2, 93}, + &versionInfo{12, M, 22, 6, 36, 2, 37}, + &versionInfo{12, Q, 26, 4, 20, 6, 21}, + &versionInfo{12, H, 28, 7, 14, 4, 15}, + &versionInfo{13, L, 26, 4, 107, 0, 0}, + &versionInfo{13, M, 22, 8, 37, 1, 38}, + &versionInfo{13, Q, 24, 8, 20, 4, 21}, + &versionInfo{13, H, 22, 12, 11, 4, 12}, + &versionInfo{14, L, 30, 3, 115, 1, 116}, + &versionInfo{14, M, 24, 4, 40, 5, 41}, + &versionInfo{14, Q, 20, 11, 16, 5, 17}, + &versionInfo{14, H, 24, 11, 12, 5, 13}, + &versionInfo{15, L, 22, 5, 87, 1, 88}, + &versionInfo{15, M, 24, 5, 41, 5, 42}, + &versionInfo{15, Q, 30, 5, 24, 7, 25}, + &versionInfo{15, H, 24, 11, 12, 7, 13}, + &versionInfo{16, L, 24, 5, 98, 1, 99}, + &versionInfo{16, M, 28, 7, 45, 3, 46}, + &versionInfo{16, Q, 24, 15, 19, 2, 20}, + &versionInfo{16, H, 30, 3, 15, 13, 16}, + &versionInfo{17, L, 28, 1, 107, 5, 108}, + &versionInfo{17, M, 28, 10, 46, 1, 47}, + &versionInfo{17, Q, 28, 1, 22, 15, 23}, + &versionInfo{17, H, 28, 2, 14, 17, 15}, + &versionInfo{18, L, 30, 5, 120, 1, 121}, + &versionInfo{18, M, 26, 9, 43, 4, 44}, + &versionInfo{18, Q, 28, 17, 22, 1, 23}, + &versionInfo{18, H, 28, 2, 14, 19, 15}, + &versionInfo{19, L, 28, 3, 113, 4, 114}, + &versionInfo{19, M, 26, 3, 44, 11, 45}, + &versionInfo{19, Q, 26, 17, 21, 4, 22}, + &versionInfo{19, H, 26, 9, 13, 16, 14}, + &versionInfo{20, L, 28, 3, 107, 5, 108}, + &versionInfo{20, M, 26, 3, 41, 13, 42}, + &versionInfo{20, Q, 30, 15, 24, 5, 25}, + &versionInfo{20, H, 28, 15, 15, 10, 16}, + &versionInfo{21, L, 28, 4, 116, 4, 117}, + &versionInfo{21, M, 26, 17, 42, 0, 0}, + &versionInfo{21, Q, 28, 17, 22, 6, 23}, + &versionInfo{21, H, 30, 19, 16, 6, 17}, + &versionInfo{22, L, 28, 2, 111, 7, 112}, + &versionInfo{22, M, 28, 17, 46, 0, 0}, + &versionInfo{22, Q, 30, 7, 24, 16, 25}, + &versionInfo{22, H, 24, 34, 13, 0, 0}, + &versionInfo{23, L, 30, 4, 121, 5, 122}, + &versionInfo{23, M, 28, 4, 47, 14, 48}, + &versionInfo{23, Q, 30, 11, 24, 14, 25}, + &versionInfo{23, H, 30, 16, 15, 14, 16}, + &versionInfo{24, L, 30, 6, 117, 4, 118}, + &versionInfo{24, M, 28, 6, 45, 14, 46}, + &versionInfo{24, Q, 30, 11, 24, 16, 25}, + &versionInfo{24, H, 30, 30, 16, 2, 17}, + &versionInfo{25, L, 26, 8, 106, 4, 107}, + &versionInfo{25, M, 28, 8, 47, 13, 48}, + &versionInfo{25, Q, 30, 7, 24, 22, 25}, + &versionInfo{25, H, 30, 22, 15, 13, 16}, + &versionInfo{26, L, 28, 10, 114, 2, 115}, + &versionInfo{26, M, 28, 19, 46, 4, 47}, + &versionInfo{26, Q, 28, 28, 22, 6, 23}, + &versionInfo{26, H, 30, 33, 16, 4, 17}, + &versionInfo{27, L, 30, 8, 122, 4, 123}, + &versionInfo{27, M, 28, 22, 45, 3, 46}, + &versionInfo{27, Q, 30, 8, 23, 26, 24}, + &versionInfo{27, H, 30, 12, 15, 28, 16}, + &versionInfo{28, L, 30, 3, 117, 10, 118}, + &versionInfo{28, M, 28, 3, 45, 23, 46}, + &versionInfo{28, Q, 30, 4, 24, 31, 25}, + &versionInfo{28, H, 30, 11, 15, 31, 16}, + &versionInfo{29, L, 30, 7, 116, 7, 117}, + &versionInfo{29, M, 28, 21, 45, 7, 46}, + &versionInfo{29, Q, 30, 1, 23, 37, 24}, + &versionInfo{29, H, 30, 19, 15, 26, 16}, + &versionInfo{30, L, 30, 5, 115, 10, 116}, + &versionInfo{30, M, 28, 19, 47, 10, 48}, + &versionInfo{30, Q, 30, 15, 24, 25, 25}, + &versionInfo{30, H, 30, 23, 15, 25, 16}, + &versionInfo{31, L, 30, 13, 115, 3, 116}, + &versionInfo{31, M, 28, 2, 46, 29, 47}, + &versionInfo{31, Q, 30, 42, 24, 1, 25}, + &versionInfo{31, H, 30, 23, 15, 28, 16}, + &versionInfo{32, L, 30, 17, 115, 0, 0}, + &versionInfo{32, M, 28, 10, 46, 23, 47}, + &versionInfo{32, Q, 30, 10, 24, 35, 25}, + &versionInfo{32, H, 30, 19, 15, 35, 16}, + &versionInfo{33, L, 30, 17, 115, 1, 116}, + &versionInfo{33, M, 28, 14, 46, 21, 47}, + &versionInfo{33, Q, 30, 29, 24, 19, 25}, + &versionInfo{33, H, 30, 11, 15, 46, 16}, + &versionInfo{34, L, 30, 13, 115, 6, 116}, + &versionInfo{34, M, 28, 14, 46, 23, 47}, + &versionInfo{34, Q, 30, 44, 24, 7, 25}, + &versionInfo{34, H, 30, 59, 16, 1, 17}, + &versionInfo{35, L, 30, 12, 121, 7, 122}, + &versionInfo{35, M, 28, 12, 47, 26, 48}, + &versionInfo{35, Q, 30, 39, 24, 14, 25}, + &versionInfo{35, H, 30, 22, 15, 41, 16}, + &versionInfo{36, L, 30, 6, 121, 14, 122}, + &versionInfo{36, M, 28, 6, 47, 34, 48}, + &versionInfo{36, Q, 30, 46, 24, 10, 25}, + &versionInfo{36, H, 30, 2, 15, 64, 16}, + &versionInfo{37, L, 30, 17, 122, 4, 123}, + &versionInfo{37, M, 28, 29, 46, 14, 47}, + &versionInfo{37, Q, 30, 49, 24, 10, 25}, + &versionInfo{37, H, 30, 24, 15, 46, 16}, + &versionInfo{38, L, 30, 4, 122, 18, 123}, + &versionInfo{38, M, 28, 13, 46, 32, 47}, + &versionInfo{38, Q, 30, 48, 24, 14, 25}, + &versionInfo{38, H, 30, 42, 15, 32, 16}, + &versionInfo{39, L, 30, 20, 117, 4, 118}, + &versionInfo{39, M, 28, 40, 47, 7, 48}, + &versionInfo{39, Q, 30, 43, 24, 22, 25}, + &versionInfo{39, H, 30, 10, 15, 67, 16}, + &versionInfo{40, L, 30, 19, 118, 6, 119}, + &versionInfo{40, M, 28, 18, 47, 31, 48}, + &versionInfo{40, Q, 30, 34, 24, 34, 25}, + &versionInfo{40, H, 30, 20, 15, 61, 16}, +} + +func (vi *versionInfo) totalDataBytes() int { + g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1) + g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2) + return (g1Data + g2Data) +} + +func (vi *versionInfo) charCountBits(m encodingMode) byte { + switch m { + case numericMode: + if vi.Version < 10 { + return 10 + } else if vi.Version < 27 { + return 12 + } + return 14 + + case alphaNumericMode: + if vi.Version < 10 { + return 9 + } else if vi.Version < 27 { + return 11 + } + return 13 + + case byteMode: + if vi.Version < 10 { + return 8 + } + return 16 + + case kanjiMode: + if vi.Version < 10 { + return 8 + } else if vi.Version < 27 { + return 10 + } + return 12 + default: + return 0 + } +} + +func (vi *versionInfo) modulWidth() int { + return ((int(vi.Version) - 1) * 4) + 21 +} + +func (vi *versionInfo) alignmentPatternPlacements() []int { + if vi.Version == 1 { + return make([]int, 0) + } + + first := 6 + last := vi.modulWidth() - 7 + space := float64(last - first) + count := int(math.Ceil(space/28)) + 1 + + result := make([]int, count) + result[0] = first + result[len(result)-1] = last + if count > 2 { + step := int(math.Ceil(float64(last-first) / float64(count-1))) + if step%2 == 1 { + frac := float64(last-first) / float64(count-1) + _, x := math.Modf(frac) + if x >= 0.5 { + frac = math.Ceil(frac) + } else { + frac = math.Floor(frac) + } + + if int(frac)%2 == 0 { + step-- + } else { + step++ + } + } + + for i := 1; i <= count-2; i++ { + result[i] = last - (step * (count - 1 - i)) + } + } + + return result +} + +func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo { + dataBits = dataBits + 4 // mode indicator + for _, vi := range versionInfos { + if vi.Level == ecl { + if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) { + return vi + } + } + } + return nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/versioninfo_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/versioninfo_test.go new file mode 100644 index 00000000..f41aa37e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr/versioninfo_test.go @@ -0,0 +1,157 @@ +package qr + +import "testing" + +var testvi = &versionInfo{7, M, 0, 1, 10, 2, 5} // Fake versionInfo to run some of the tests + +func Test_ErrorCorrectionStringer(t *testing.T) { + tests := map[ErrorCorrectionLevel]string{ + L: "L", M: "M", Q: "Q", H: "H", ErrorCorrectionLevel(99): "unknown", + } + for ecl, str := range tests { + if ecl.String() != str { + t.Fail() + } + } +} + +func Test_CharCountBits(t *testing.T) { + v1 := &versionInfo{5, M, 0, 0, 0, 0, 0} + v2 := &versionInfo{15, M, 0, 0, 0, 0, 0} + v3 := &versionInfo{30, M, 0, 0, 0, 0, 0} + + if v1.charCountBits(numericMode) != 10 { + t.Fail() + } + if v1.charCountBits(alphaNumericMode) != 9 { + t.Fail() + } + if v1.charCountBits(byteMode) != 8 { + t.Fail() + } + if v1.charCountBits(kanjiMode) != 8 { + t.Fail() + } + if v2.charCountBits(numericMode) != 12 { + t.Fail() + } + if v2.charCountBits(alphaNumericMode) != 11 { + t.Fail() + } + if v2.charCountBits(byteMode) != 16 { + t.Fail() + } + if v2.charCountBits(kanjiMode) != 10 { + t.Fail() + } + if v3.charCountBits(numericMode) != 14 { + t.Fail() + } + if v3.charCountBits(alphaNumericMode) != 13 { + t.Fail() + } + if v3.charCountBits(byteMode) != 16 { + t.Fail() + } + if v3.charCountBits(kanjiMode) != 12 { + t.Fail() + } + if v1.charCountBits(encodingMode(3)) != 0 { + t.Fail() + } +} + +func Test_TotalDataBytes(t *testing.T) { + if testvi.totalDataBytes() != 20 { + t.Fail() + } +} + +func Test_ModulWidth(t *testing.T) { + if testvi.modulWidth() != 45 { + t.Fail() + } +} + +func Test_FindSmallestVersionInfo(t *testing.T) { + if findSmallestVersionInfo(H, alphaNumericMode, 10208) != nil { + t.Error("there should be no version with this capacity") + } + test := func(cap int, tVersion byte) { + v := findSmallestVersionInfo(H, alphaNumericMode, cap) + if v == nil || v.Version != tVersion { + t.Errorf("version %d should be returned.", tVersion) + } + } + test(10191, 40) + test(5591, 29) + test(5592, 30) + test(190, 3) + test(200, 4) +} + +type aligmnentTest struct { + version byte + patterns []int +} + +var allAligmnentTests = []*aligmnentTest{ + &aligmnentTest{1, []int{}}, + &aligmnentTest{2, []int{6, 18}}, + &aligmnentTest{3, []int{6, 22}}, + &aligmnentTest{4, []int{6, 26}}, + &aligmnentTest{5, []int{6, 30}}, + &aligmnentTest{6, []int{6, 34}}, + &aligmnentTest{7, []int{6, 22, 38}}, + &aligmnentTest{8, []int{6, 24, 42}}, + &aligmnentTest{9, []int{6, 26, 46}}, + &aligmnentTest{10, []int{6, 28, 50}}, + &aligmnentTest{11, []int{6, 30, 54}}, + &aligmnentTest{12, []int{6, 32, 58}}, + &aligmnentTest{13, []int{6, 34, 62}}, + &aligmnentTest{14, []int{6, 26, 46, 66}}, + &aligmnentTest{15, []int{6, 26, 48, 70}}, + &aligmnentTest{16, []int{6, 26, 50, 74}}, + &aligmnentTest{17, []int{6, 30, 54, 78}}, + &aligmnentTest{18, []int{6, 30, 56, 82}}, + &aligmnentTest{19, []int{6, 30, 58, 86}}, + &aligmnentTest{20, []int{6, 34, 62, 90}}, + &aligmnentTest{21, []int{6, 28, 50, 72, 94}}, + &aligmnentTest{22, []int{6, 26, 50, 74, 98}}, + &aligmnentTest{23, []int{6, 30, 54, 78, 102}}, + &aligmnentTest{24, []int{6, 28, 54, 80, 106}}, + &aligmnentTest{25, []int{6, 32, 58, 84, 110}}, + &aligmnentTest{26, []int{6, 30, 58, 86, 114}}, + &aligmnentTest{27, []int{6, 34, 62, 90, 118}}, + &aligmnentTest{28, []int{6, 26, 50, 74, 98, 122}}, + &aligmnentTest{29, []int{6, 30, 54, 78, 102, 126}}, + &aligmnentTest{30, []int{6, 26, 52, 78, 104, 130}}, + &aligmnentTest{31, []int{6, 30, 56, 82, 108, 134}}, + &aligmnentTest{32, []int{6, 34, 60, 86, 112, 138}}, + &aligmnentTest{33, []int{6, 30, 58, 86, 114, 142}}, + &aligmnentTest{34, []int{6, 34, 62, 90, 118, 146}}, + &aligmnentTest{35, []int{6, 30, 54, 78, 102, 126, 150}}, + &aligmnentTest{36, []int{6, 24, 50, 76, 102, 128, 154}}, + &aligmnentTest{37, []int{6, 28, 54, 80, 106, 132, 158}}, + &aligmnentTest{38, []int{6, 32, 58, 84, 110, 136, 162}}, + &aligmnentTest{39, []int{6, 26, 54, 82, 110, 138, 166}}, + &aligmnentTest{40, []int{6, 30, 58, 86, 114, 142, 170}}, +} + +func Test_AlignmentPatternPlacements(t *testing.T) { + for _, at := range allAligmnentTests { + vi := &versionInfo{at.version, M, 0, 0, 0, 0, 0} + + res := vi.alignmentPatternPlacements() + if len(res) != len(at.patterns) { + t.Errorf("number of alignmentpatterns missmatch for version %d", at.version) + } + for i := 0; i < len(res); i++ { + if res[i] != at.patterns[i] { + t.Errorf("alignmentpatterns for version %d missmatch on index %d", at.version, i) + } + } + + } + +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/scaledbarcode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/scaledbarcode.go new file mode 100644 index 00000000..0a330efb --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/scaledbarcode.go @@ -0,0 +1,111 @@ +package barcode + +import ( + "errors" + "fmt" + "image" + "image/color" + "math" +) + +type wrapFunc func(x, y int) color.Color + +type scaledBarcode struct { + wrapped Barcode + wrapperFunc wrapFunc + rect image.Rectangle +} + +func (bc *scaledBarcode) Content() string { + return bc.wrapped.Content() +} + +func (bc *scaledBarcode) Metadata() Metadata { + return bc.wrapped.Metadata() +} + +func (bc *scaledBarcode) ColorModel() color.Model { + return bc.wrapped.ColorModel() +} + +func (bc *scaledBarcode) Bounds() image.Rectangle { + return bc.rect +} + +func (bc *scaledBarcode) At(x, y int) color.Color { + return bc.wrapperFunc(x, y) +} + +// Scale returns a resized barcode with the given width and height. +func Scale(bc Barcode, width, height int) (Barcode, error) { + switch bc.Metadata().Dimensions { + case 1: + return scale1DCode(bc, width, height) + case 2: + return scale2DCode(bc, width, height) + } + + return nil, errors.New("unsupported barcode format") +} + +func scale2DCode(bc Barcode, width, height int) (Barcode, error) { + orgBounds := bc.Bounds() + orgWidth := orgBounds.Max.X - orgBounds.Min.X + orgHeight := orgBounds.Max.Y - orgBounds.Min.Y + + factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight))) + if factor <= 0 { + return nil, fmt.Errorf("can not scale barcode to an image smaller then %dx%d", orgWidth, orgHeight) + } + + offsetX := (width - (orgWidth * factor)) / 2 + offsetY := (height - (orgHeight * factor)) / 2 + + wrap := func(x, y int) color.Color { + if x < offsetX || y < offsetY { + return color.White + } + x = (x - offsetX) / factor + y = (y - offsetY) / factor + if x >= orgWidth || y >= orgHeight { + return color.White + } + return bc.At(x, y) + } + + return &scaledBarcode{ + bc, + wrap, + image.Rect(0, 0, width, height), + }, nil +} + +func scale1DCode(bc Barcode, width, height int) (Barcode, error) { + orgBounds := bc.Bounds() + orgWidth := orgBounds.Max.X - orgBounds.Min.X + factor := int(float64(width) / float64(orgWidth)) + + if factor <= 0 { + return nil, fmt.Errorf("can not scale barcode to an image smaller then %dx1", orgWidth) + } + offsetX := (width - (orgWidth * factor)) / 2 + + wrap := func(x, y int) color.Color { + if x < offsetX { + return color.White + } + x = (x - offsetX) / factor + + if x >= orgWidth { + return color.White + } + return bc.At(x, 0) + } + + return &scaledBarcode{ + bc, + wrap, + image.Rect(0, 0, width, height), + }, nil + +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/twooffive/encoder.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/twooffive/encoder.go new file mode 100644 index 00000000..0d612a57 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/twooffive/encoder.go @@ -0,0 +1,138 @@ +// Package twooffive can create interleaved and standard "2 of 5" barcodes. +package twooffive + +import ( + "errors" + "fmt" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils" +) + +const patternWidth = 5 + +type pattern [patternWidth]bool +type encodeInfo struct { + start []bool + end []bool + widths map[bool]int +} + +var ( + encodingTable = map[rune]pattern{ + '0': pattern{false, false, true, true, false}, + '1': pattern{true, false, false, false, true}, + '2': pattern{false, true, false, false, true}, + '3': pattern{true, true, false, false, false}, + '4': pattern{false, false, true, false, true}, + '5': pattern{true, false, true, false, false}, + '6': pattern{false, true, true, false, false}, + '7': pattern{false, false, false, true, true}, + '8': pattern{true, false, false, true, false}, + '9': pattern{false, true, false, true, false}, + } + + modes = map[bool]encodeInfo{ + false: encodeInfo{ // non-interleaved + start: []bool{true, true, false, true, true, false, true, false}, + end: []bool{true, true, false, true, false, true, true}, + widths: map[bool]int{ + true: 3, + false: 1, + }, + }, + true: encodeInfo{ // interleaved + start: []bool{true, false, true, false}, + end: []bool{true, true, false, true}, + widths: map[bool]int{ + true: 2, + false: 1, + }, + }, + } + nonInterleavedSpace = pattern{false, false, false, false, false} +) + +// AddCheckSum calculates the correct check-digit and appends it to the given content. +func AddCheckSum(content string) (string, error) { + if content == "" { + return "", errors.New("content is empty") + } + + even := len(content)%2 == 1 + sum := 0 + for _, r := range content { + if _, ok := encodingTable[r]; ok { + value := utils.RuneToInt(r) + if even { + sum += value * 3 + } else { + sum += value + } + even = !even + } else { + return "", fmt.Errorf("can not encode \"%s\"", content) + } + } + + return content + string(utils.IntToRune(sum%10)), nil +} + +// Encode creates a codabar barcode for the given content +func Encode(content string, interleaved bool) (barcode.Barcode, error) { + if content == "" { + return nil, errors.New("content is empty") + } + + if interleaved && len(content)%2 == 1 { + return nil, errors.New("can only encode even number of digits in interleaved mode") + } + + mode := modes[interleaved] + resBits := new(utils.BitList) + resBits.AddBit(mode.start...) + + var lastRune *rune + for _, r := range content { + var a, b pattern + if interleaved { + if lastRune == nil { + lastRune = new(rune) + *lastRune = r + continue + } else { + var o1, o2 bool + a, o1 = encodingTable[*lastRune] + b, o2 = encodingTable[r] + if !o1 || !o2 { + return nil, fmt.Errorf("can not encode \"%s\"", content) + } + lastRune = nil + } + } else { + var ok bool + a, ok = encodingTable[r] + if !ok { + return nil, fmt.Errorf("can not encode \"%s\"", content) + } + b = nonInterleavedSpace + } + + for i := 0; i < patternWidth; i++ { + for x := 0; x < mode.widths[a[i]]; x++ { + resBits.AddBit(true) + } + for x := 0; x < mode.widths[b[i]]; x++ { + resBits.AddBit(false) + } + } + } + + resBits.AddBit(mode.end...) + + kindTxt := "" + if interleaved { + kindTxt = " (interleaved)" + } + return utils.New1DCode("2 of 5"+kindTxt, content, resBits), nil +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/twooffive/encoder_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/twooffive/encoder_test.go new file mode 100644 index 00000000..007e5c61 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/twooffive/encoder_test.go @@ -0,0 +1,45 @@ +package twooffive + +import ( + "image/color" + "testing" +) + +func Test_AddCheckSum(t *testing.T) { + if sum, err := AddCheckSum("1234567"); err != nil || sum != "12345670" { + t.Fail() + } + if _, err := AddCheckSum("1ABC"); err == nil { + t.Fail() + } + if _, err := AddCheckSum(""); err == nil { + t.Fail() + } +} + +func Test_Encode(t *testing.T) { + _, err := Encode("FOOBAR", false) + if err == nil { + t.Error("\"FOOBAR\" should not be encodable") + } + + testEncode := func(interleaved bool, txt, testResult string) { + code, err := Encode(txt, interleaved) + if err != nil || code == nil { + t.Fail() + } else { + if code.Bounds().Max.X != len(testResult) { + t.Errorf("%v: length missmatch! %v != %v", txt, code.Bounds().Max.X, len(testResult)) + } else { + for i, r := range testResult { + if (code.At(i, 0) == color.Black) != (r == '1') { + t.Errorf("%v: code missmatch on position %d", txt, i) + } + } + } + } + } + + testEncode(false, "12345670", "1101101011101010101110101110101011101110111010101010101110101110111010111010101011101110101010101011101110101011101110101101011") + testEncode(true, "12345670", "1010110100101011001101101001010011010011001010101010011001101101") +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/base1dcode.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/base1dcode.go new file mode 100644 index 00000000..f11d4854 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/base1dcode.go @@ -0,0 +1,43 @@ +// Package utils contain some utilities which are needed to create barcodes +package utils + +import ( + "image" + "image/color" + + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" +) + +type base1DCode struct { + *BitList + kind string + content string +} + +func (c *base1DCode) Content() string { + return c.content +} + +func (c *base1DCode) Metadata() barcode.Metadata { + return barcode.Metadata{c.kind, 1} +} + +func (c *base1DCode) ColorModel() color.Model { + return color.Gray16Model +} + +func (c *base1DCode) Bounds() image.Rectangle { + return image.Rect(0, 0, c.Len(), 1) +} + +func (c *base1DCode) At(x, y int) color.Color { + if c.GetBit(x) { + return color.Black + } + return color.White +} + +// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList +func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode { + return &base1DCode{bars, codeKind, content} +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/bitlist.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/bitlist.go new file mode 100644 index 00000000..5cdb581a --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/bitlist.go @@ -0,0 +1,119 @@ +package utils + +// BitList is a list that contains bits +type BitList struct { + count int + data []int32 +} + +// NewBitList returns a new BitList with the given length +// all bits are initialize with false +func NewBitList(capacity int) *BitList { + bl := new(BitList) + bl.count = capacity + x := 0 + if capacity%32 != 0 { + x = 1 + } + bl.data = make([]int32, capacity/32+x) + return bl +} + +// Len returns the number of contained bits +func (bl *BitList) Len() int { + return bl.count +} + +func (bl *BitList) grow() { + growBy := len(bl.data) + if growBy < 128 { + growBy = 128 + } else if growBy >= 1024 { + growBy = 1024 + } + + nd := make([]int32, len(bl.data)+growBy) + copy(nd, bl.data) + bl.data = nd +} + +// AddBit appends the given bits to the end of the list +func (bl *BitList) AddBit(bits ...bool) { + for _, bit := range bits { + itmIndex := bl.count / 32 + for itmIndex >= len(bl.data) { + bl.grow() + } + bl.SetBit(bl.count, bit) + bl.count++ + } +} + +// SetBit sets the bit at the given index to the given value +func (bl *BitList) SetBit(index int, value bool) { + itmIndex := index / 32 + itmBitShift := 31 - (index % 32) + if value { + bl.data[itmIndex] = bl.data[itmIndex] | 1<> uint(itmBitShift)) & 1) == 1 +} + +// AddByte appends all 8 bits of the given byte to the end of the list +func (bl *BitList) AddByte(b byte) { + for i := 7; i >= 0; i-- { + bl.AddBit(((b >> uint(i)) & 1) == 1) + } +} + +// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list +func (bl *BitList) AddBits(b int, count byte) { + for i := int(count - 1); i >= 0; i-- { + bl.AddBit(((b >> uint(i)) & 1) == 1) + } +} + +// GetBytes returns all bits of the BitList as a []byte +func (bl *BitList) GetBytes() []byte { + len := bl.count >> 3 + if (bl.count % 8) != 0 { + len++ + } + result := make([]byte, len) + for i := 0; i < len; i++ { + shift := (3 - (i % 4)) * 8 + result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF) + } + return result +} + +// IterateBytes iterates through all bytes contained in the BitList +func (bl *BitList) IterateBytes() <-chan byte { + res := make(chan byte) + + go func() { + c := bl.count + shift := 24 + i := 0 + for c > 0 { + res <- byte((bl.data[i] >> uint(shift)) & 0xFF) + shift -= 8 + if shift < 0 { + shift = 24 + i++ + } + c -= 8 + } + close(res) + }() + + return res +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/galoisfield.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/galoisfield.go new file mode 100644 index 00000000..6f077d7e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/galoisfield.go @@ -0,0 +1,62 @@ +package utils + +// GaloisField encapsulates galois field arithmetics +type GaloisField struct { + ALogTbl []int + LogTbl []int +} + +// NewGaloisField creates a new falois field +func NewGaloisField(pp int) *GaloisField { + result := new(GaloisField) + fldSize := 256 + + result.ALogTbl = make([]int, fldSize) + result.LogTbl = make([]int, fldSize) + + x := 1 + for i := 0; i < fldSize; i++ { + result.ALogTbl[i] = x + x = x * 2 + if x >= fldSize { + x = (x ^ pp) & (fldSize - 1) + } + } + + for i := 0; i < fldSize; i++ { + result.LogTbl[result.ALogTbl[i]] = int(i) + } + + return result +} + +func (gf *GaloisField) Zero() *GFPoly { + return NewGFPoly(gf, []byte{0}) +} + +// AddOrSub add or substract two numbers +func (gf *GaloisField) AddOrSub(a, b int) int { + return a ^ b +} + +// Multiply multiplys two numbers +func (gf *GaloisField) Multiply(a, b int) int { + if a == 0 || b == 0 { + return 0 + } + return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%255] +} + +// Divide divides two numbers +func (gf *GaloisField) Divide(a, b int) int { + if b == 0 { + panic("divide by zero") + } else if a == 0 { + return 0 + } + return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%255] +} + +func (gf *GaloisField) Invers(num int) int { + return gf.ALogTbl[255-gf.LogTbl[num]] +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/galoisfield_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/galoisfield_test.go new file mode 100644 index 00000000..fc8134fa --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/galoisfield_test.go @@ -0,0 +1,59 @@ +package utils + +import ( + "testing" +) + +func Test_GF(t *testing.T) { + log := []int{ + 0, 255, 1, 240, 2, 225, 241, 53, 3, 38, 226, 133, 242, 43, 54, 210, + 4, 195, 39, 114, 227, 106, 134, 28, 243, 140, 44, 23, 55, 118, 211, 234, + 5, 219, 196, 96, 40, 222, 115, 103, 228, 78, 107, 125, 135, 8, 29, 162, + 244, 186, 141, 180, 45, 99, 24, 49, 56, 13, 119, 153, 212, 199, 235, 91, + 6, 76, 220, 217, 197, 11, 97, 184, 41, 36, 223, 253, 116, 138, 104, 193, + 229, 86, 79, 171, 108, 165, 126, 145, 136, 34, 9, 74, 30, 32, 163, 84, + 245, 173, 187, 204, 142, 81, 181, 190, 46, 88, 100, 159, 25, 231, 50, 207, + 57, 147, 14, 67, 120, 128, 154, 248, 213, 167, 200, 63, 236, 110, 92, 176, + 7, 161, 77, 124, 221, 102, 218, 95, 198, 90, 12, 152, 98, 48, 185, 179, + 42, 209, 37, 132, 224, 52, 254, 239, 117, 233, 139, 22, 105, 27, 194, 113, + 230, 206, 87, 158, 80, 189, 172, 203, 109, 175, 166, 62, 127, 247, 146, 66, + 137, 192, 35, 252, 10, 183, 75, 216, 31, 83, 33, 73, 164, 144, 85, 170, + 246, 65, 174, 61, 188, 202, 205, 157, 143, 169, 82, 72, 182, 215, 191, 251, + 47, 178, 89, 151, 101, 94, 160, 123, 26, 112, 232, 21, 51, 238, 208, 131, + 58, 69, 148, 18, 15, 16, 68, 17, 121, 149, 129, 19, 155, 59, 249, 70, + 214, 250, 168, 71, 201, 156, 64, 60, 237, 130, 111, 20, 93, 122, 177, 150, + } + + alog := []int{ + 1, 2, 4, 8, 16, 32, 64, 128, 45, 90, 180, 69, 138, 57, 114, 228, + 229, 231, 227, 235, 251, 219, 155, 27, 54, 108, 216, 157, 23, 46, 92, 184, + 93, 186, 89, 178, 73, 146, 9, 18, 36, 72, 144, 13, 26, 52, 104, 208, + 141, 55, 110, 220, 149, 7, 14, 28, 56, 112, 224, 237, 247, 195, 171, 123, + 246, 193, 175, 115, 230, 225, 239, 243, 203, 187, 91, 182, 65, 130, 41, 82, + 164, 101, 202, 185, 95, 190, 81, 162, 105, 210, 137, 63, 126, 252, 213, 135, + 35, 70, 140, 53, 106, 212, 133, 39, 78, 156, 21, 42, 84, 168, 125, 250, + 217, 159, 19, 38, 76, 152, 29, 58, 116, 232, 253, 215, 131, 43, 86, 172, + 117, 234, 249, 223, 147, 11, 22, 44, 88, 176, 77, 154, 25, 50, 100, 200, + 189, 87, 174, 113, 226, 233, 255, 211, 139, 59, 118, 236, 245, 199, 163, 107, + 214, 129, 47, 94, 188, 85, 170, 121, 242, 201, 191, 83, 166, 97, 194, 169, + 127, 254, 209, 143, 51, 102, 204, 181, 71, 142, 49, 98, 196, 165, 103, 206, + 177, 79, 158, 17, 34, 68, 136, 61, 122, 244, 197, 167, 99, 198, 161, 111, + 222, 145, 15, 30, 60, 120, 240, 205, 183, 67, 134, 33, 66, 132, 37, 74, + 148, 5, 10, 20, 40, 80, 160, 109, 218, 153, 31, 62, 124, 248, 221, 151, + 3, 6, 12, 24, 48, 96, 192, 173, 119, 238, 241, 207, 179, 75, 150, 1, + } + + gf := NewGaloisField(301) + if len(gf.LogTbl) != len(gf.ALogTbl) || len(gf.LogTbl) != len(log) { + t.Fail() + } + for i := 0; i < len(log); i++ { + if gf.LogTbl[i] != log[i] { + t.Error("Invalid Log Table") + } + if gf.ALogTbl[i] != alog[i] { + t.Error("Invalid ALog Table") + } + } + +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/gfpoly.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/gfpoly.go new file mode 100644 index 00000000..54686de2 --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/gfpoly.go @@ -0,0 +1,103 @@ +package utils + +type GFPoly struct { + gf *GaloisField + Coefficients []byte +} + +func (gp *GFPoly) Degree() int { + return len(gp.Coefficients) - 1 +} + +func (gp *GFPoly) Zero() bool { + return gp.Coefficients[0] == 0 +} + +// GetCoefficient returns the coefficient of x ^ degree +func (gp *GFPoly) GetCoefficient(degree int) byte { + return gp.Coefficients[gp.Degree()-degree] +} + +func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly { + if gp.Zero() { + return other + } else if other.Zero() { + return gp + } + smallCoeff := gp.Coefficients + largeCoeff := other.Coefficients + if len(smallCoeff) > len(largeCoeff) { + largeCoeff, smallCoeff = smallCoeff, largeCoeff + } + sumDiff := make([]byte, len(largeCoeff)) + lenDiff := len(largeCoeff) - len(smallCoeff) + copy(sumDiff, largeCoeff[:lenDiff]) + for i := lenDiff; i < len(largeCoeff); i++ { + sumDiff[i] = byte(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i]))) + } + return NewGFPoly(gp.gf, sumDiff) +} + +func (gp *GFPoly) MultByMonominal(degree int, coeff byte) *GFPoly { + if coeff == 0 { + return gp.gf.Zero() + } + size := len(gp.Coefficients) + result := make([]byte, size+degree) + for i := 0; i < size; i++ { + result[i] = byte(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff))) + } + return NewGFPoly(gp.gf, result) +} + +func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly { + if gp.Zero() || other.Zero() { + return gp.gf.Zero() + } + aCoeff := gp.Coefficients + aLen := len(aCoeff) + bCoeff := other.Coefficients + bLen := len(bCoeff) + product := make([]byte, aLen+bLen-1) + for i := 0; i < aLen; i++ { + ac := int(aCoeff[i]) + for j := 0; j < bLen; j++ { + bc := int(bCoeff[j]) + product[i+j] = byte(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc))) + } + } + return NewGFPoly(gp.gf, product) +} + +func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) { + quotient = gp.gf.Zero() + remainder = gp + fld := gp.gf + denomLeadTerm := other.GetCoefficient(other.Degree()) + inversDenomLeadTerm := fld.Invers(int(denomLeadTerm)) + for remainder.Degree() >= other.Degree() && !remainder.Zero() { + degreeDiff := remainder.Degree() - other.Degree() + scale := byte(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm)) + term := other.MultByMonominal(degreeDiff, scale) + itQuot := NewMonominalPoly(fld, degreeDiff, scale) + quotient = quotient.AddOrSubstract(itQuot) + remainder = remainder.AddOrSubstract(term) + } + return +} + +func NewMonominalPoly(field *GaloisField, degree int, coeff byte) *GFPoly { + if coeff == 0 { + return field.Zero() + } + result := make([]byte, degree+1) + result[0] = coeff + return NewGFPoly(field, result) +} + +func NewGFPoly(field *GaloisField, coefficients []byte) *GFPoly { + for len(coefficients) > 1 && coefficients[0] == 0 { + coefficients = coefficients[1:] + } + return &GFPoly{field, coefficients} +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/runeint.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/runeint.go new file mode 100644 index 00000000..d2e5e61e --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/runeint.go @@ -0,0 +1,19 @@ +package utils + +// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9 +// If the rune is outside of this range -1 is returned. +func RuneToInt(r rune) int { + if r >= '0' && r <= '9' { + return int(r - '0') + } + return -1 +} + +// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside +// of this range 'F' is returned! +func IntToRune(i int) rune { + if i >= 0 && i <= 9 { + return rune(i + '0') + } + return 'F' +} diff --git a/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/runeint_test.go b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/runeint_test.go new file mode 100644 index 00000000..f1fdbc5f --- /dev/null +++ b/server/Godeps/_workspace/src/github.com/boombuler/barcode/utils/runeint_test.go @@ -0,0 +1,24 @@ +package utils + +import "testing" + +func Test_RuneToIntIntToRune(t *testing.T) { + if IntToRune(0) != '0' { + t.Errorf("failed IntToRune(0) returned %d", string(IntToRune(0))) + } + if IntToRune(9) != '9' { + t.Errorf("failed IntToRune(9) returned %d", IntToRune(9)) + } + if IntToRune(10) != 'F' { + t.Errorf("failed IntToRune(10) returned %d", IntToRune(10)) + } + if RuneToInt('0') != 0 { + t.Error("failed RuneToInt('0') returned %d", RuneToInt(0)) + } + if RuneToInt('9') != 9 { + t.Error("failed RuneToInt('9') returned %d", RuneToInt(9)) + } + if RuneToInt('F') != -1 { + t.Error("failed RuneToInt('F') returned %d", RuneToInt('F')) + } +} diff --git a/server/Godeps/_workspace/src/github.com/facebookgo/clock/clock_test.go b/server/Godeps/_workspace/src/github.com/facebookgo/clock/clock_test.go index 452622e1..d8c56c32 100644 --- a/server/Godeps/_workspace/src/github.com/facebookgo/clock/clock_test.go +++ b/server/Godeps/_workspace/src/github.com/facebookgo/clock/clock_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/facebookgo/clock" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/clock" ) // Ensure that the clock's After channel sends at the correct time. diff --git a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/.travis.yml b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/.travis.yml index d29694f6..3a2548e2 100644 --- a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/.travis.yml +++ b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/.travis.yml @@ -1,15 +1,12 @@ language: go go: - - 1.3 - -matrix: - fast_finish: true + - 1.4 before_install: - - go get -v code.google.com/p/go.tools/cmd/vet + - go get -v golang.org/x/tools/cmd/vet + - go get -v golang.org/x/tools/cmd/cover - go get -v github.com/golang/lint/golint - - go get -v code.google.com/p/go.tools/cmd/cover install: - go install -race -v std @@ -20,4 +17,7 @@ script: - go vet ./... - $HOME/gopath/bin/golint . - go test -cpu=2 -race -v ./... - - go test -cpu=2 -covermode=atomic ./... + - go test -cpu=2 -covermode=atomic -coverprofile=coverage.txt ./ + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown.go b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown.go index 2ad031ea..16fc051c 100644 --- a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown.go +++ b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown.go @@ -14,8 +14,8 @@ import ( "syscall" "time" - "github.com/facebookgo/clock" - "github.com/facebookgo/stats" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/clock" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/stats" ) const ( @@ -100,7 +100,7 @@ func (h HTTP) Serve(s *http.Server, l net.Listener) Server { } // ListenAndServe returns a Server for the given http.Server. It is equivalent -// to ListendAndServe from the standard library, but returns immediately. +// to ListenAndServe from the standard library, but returns immediately. // Requests will be accepted in a background goroutine. If the http.Server has // a non-nil TLSConfig, a TLS enabled listener will be setup. func (h HTTP) ListenAndServe(s *http.Server) (Server, error) { diff --git a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_example/main.go b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_example/main.go index 9e3c0bff..7eaca258 100644 --- a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_example/main.go +++ b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_example/main.go @@ -7,7 +7,7 @@ import ( "os" "time" - "github.com/facebookgo/httpdown" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/httpdown" ) func handler(w http.ResponseWriter, r *http.Request) { diff --git a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_test.go b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_test.go index e582e22b..c0115a07 100644 --- a/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_test.go +++ b/server/Godeps/_workspace/src/github.com/facebookgo/httpdown/httpdown_test.go @@ -15,11 +15,11 @@ import ( "testing" "time" - "github.com/facebookgo/clock" "github.com/facebookgo/ensure" "github.com/facebookgo/freeport" - "github.com/facebookgo/httpdown" - "github.com/facebookgo/stats" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/clock" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/httpdown" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/stats" ) type onCloseListener struct { diff --git a/server/Godeps/_workspace/src/github.com/facebookgo/stats/stats_test.go b/server/Godeps/_workspace/src/github.com/facebookgo/stats/stats_test.go index b53df71b..07fa5ace 100644 --- a/server/Godeps/_workspace/src/github.com/facebookgo/stats/stats_test.go +++ b/server/Godeps/_workspace/src/github.com/facebookgo/stats/stats_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/facebookgo/ensure" - "github.com/facebookgo/stats" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/stats" ) // Ensure calling End works even when a BumpTimeHook isn't provided. diff --git a/server/common/config.go b/server/common/config.go index fc9c11ae..801d24f1 100644 --- a/server/common/config.go +++ b/server/common/config.go @@ -38,43 +38,39 @@ import ( "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/root-gg/logger" ) -var ( - plikVersion = "##VERSION##" -) - // Configuration object type Configuration struct { - LogLevel string - ListenAddress string - ListenPort int - MaxFileSize int64 + LogLevel string `json:"-"` + ListenAddress string `json:"-"` + ListenPort int `json:"-"` + MaxFileSize int64 `json:"maxFileSize"` - DefaultTTL int - MaxTTL int + DefaultTTL int `json:"defaultTTL"` + MaxTTL int `json:"maxTTL"` - SslEnabled bool - SslCert string - SslKey string + SslEnabled bool `json:"-"` + SslCert string `json:"-"` + SslKey string `json:"-"` - YubikeyEnabled bool - YubikeyAPIKey string - YubikeyAPISecret string - YubiAuth *yubigo.YubiAuth + YubikeyEnabled bool `json:"yubikeyEnabled"` + YubikeyAPIKey string `json:"-"` + YubikeyAPISecret string `json:"-"` + YubiAuth *yubigo.YubiAuth `json:"-"` - SourceIPHeader string - UploadWhitelist []string + SourceIPHeader string `json:"-"` + UploadWhitelist []string `json:"-"` - MetadataBackend string - MetadataBackendConfig map[string]interface{} + MetadataBackend string `json:"-"` + MetadataBackendConfig map[string]interface{} `json:"-"` - DataBackend string - DataBackendConfig map[string]interface{} + DataBackend string `json:"-"` + DataBackendConfig map[string]interface{} `json:"-"` - StreamMode bool - StreamBackendConfig map[string]interface{} + StreamMode bool `json:"streamMode"` + StreamBackendConfig map[string]interface{} `json:"-"` - ShortenBackend string - ShortenBackendConfig map[string]interface{} + ShortenBackend string `json:"shortenBackend"` + ShortenBackendConfig map[string]interface{} `json:"-"` } // Global var to store conf @@ -93,7 +89,7 @@ func NewConfiguration() (this *Configuration) { this.DataBackend = "file" this.MetadataBackend = "file" this.MaxFileSize = 10737418240 // 10GB - this.DefaultTTL = 2592000 // 30 days + this.DefaultTTL = 2592000 // 30 days this.MaxTTL = 0 this.SslEnabled = false this.SslCert = "" @@ -145,9 +141,3 @@ func LoadConfiguration(file string) { } } } - -// GetVersion return the hardcoded version -// before compilation -func GetVersion() string { - return plikVersion -} diff --git a/server/gen_build_info.sh b/server/gen_build_info.sh new file mode 100755 index 00000000..83d9d6c8 --- /dev/null +++ b/server/gen_build_info.sh @@ -0,0 +1,180 @@ +#!/bin/bash + +### +# The MIT License (MIT) +# +# Copyright (c) <2015> +# - Mathieu Bodjikian +# - Charles-Antoine Mathieu +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +### + +set -e + +# some variables +version=$1 +user=$(whoami) +host=$(hostname) +repo=$(pwd) +date=$(date "+%s") +isRelease=false +isMint=false + +# get git current revision +sh=`git rev-list --pretty=format:%h HEAD --max-count=1 | sed '1s/commit /full_rev=/;2s/^/short_rev=/'` +eval "$sh" # Sets the full_rev & short_rev variables. + +# get git version tag +tag=$(git show-ref --tags | egrep "refs/tags/$version$" | cut -d " " -f1) +if [[ $tag = $full_rev ]]; then + isRelease=true +fi + +# get repository status +is_mint_repo() { + git rev-parse --verify HEAD >/dev/null && + git update-index --refresh >/dev/null && + git diff-files --quiet && + git diff-index --cached --quiet HEAD +} +if is_mint_repo; then + isMint=true +fi + +# compute clients code +clients="" +clientList=$(find clients -name "plik*" 2> /dev/null | sort -n) +for client in $clientList ; do + folder=$(echo $client | cut -d "/" -f2) + binary=$(echo $client | cut -d "/" -f3) + os=$(echo $folder | cut -d "-" -f1) + arch=$(echo $folder | cut -d "-" -f2) + md5=$(md5sum $client | cut -d " " -f1) + + prettyOs="" + prettyArch="" + + case "$os" in + "darwin") prettyOs="MacOS" ;; + "linux") prettyOs="Linux" ;; + "windows") prettyOs="Windows" ;; + "openbsd") prettyOs="OpenBSD" ;; + "freebsd") prettyOs="FreeBSD" ;; + "bash") prettyOs="Bash (curl)" ;; + esac + + case "$arch" in + "386") prettyArch="32bit" ;; + "amd64") prettyArch="64bit" ;; + "arm") prettyArch="ARM" ;; + esac + + fullName="$prettyOs $prettyArch" + clientCode="&Client{Name: \"$fullName\", Md5: \"$md5\", Path: \"$client\", OS: \"$os\", ARCH: \"$arch\"}" + clients+=$'\t\t'"buildInfo.Clients = append(buildInfo.Clients, $clientCode)"$'\n' +done + + +cat > "server/common/version.go" < 0 { + v += fmt.Sprintf(" [%s]", strings.Join(flags, ",")) + } + + v += fmt.Sprintf(" at %s)", time.Unix(bi.Date, 0)) + + return v +} +EOF diff --git a/server/metadataBackend/file/file.go b/server/metadataBackend/file/file.go index e64d6d24..7cc7a765 100644 --- a/server/metadataBackend/file/file.go +++ b/server/metadataBackend/file/file.go @@ -325,7 +325,7 @@ func (fmb *MetadataBackend) GetUploadsToRemove(ctx *common.PlikContext) (ids []s } // If a TTL is set, test if expired or not - if upload.TTL != 0 && time.Now().Unix() > (upload.Creation+int64(upload.TTL)) { + if upload.TTL > 0 && time.Now().Unix() > (upload.Creation+int64(upload.TTL)) { ids = append(ids, upload.ID) } } diff --git a/server/metadataBackend/mongo/mongo.go b/server/metadataBackend/mongo/mongo.go index 391df6a7..f7609509 100644 --- a/server/metadataBackend/mongo/mongo.go +++ b/server/metadataBackend/mongo/mongo.go @@ -155,9 +155,9 @@ func (mmb *MetadataBackend) GetUploadsToRemove(ctx *common.PlikContext) (ids []s defer session.Close() collection := session.DB(mmb.config.Database).C(mmb.config.Collection) - // Look for uploads older than MaxTTL to schedule them for removal + // Look for expired uploads var uploads []*common.Upload - b := bson.M{"$where": strconv.Itoa(int(time.Now().Unix())) + " > mmb.uploadDate+mmb.ttl"} + b := bson.M{"$where": "this.ttl > 0 && " + strconv.Itoa(int(time.Now().Unix())) + " > this.uploadDate + this.ttl"} err = collection.Find(b).All(&uploads) if err != nil { diff --git a/server/plik.go b/server/plik.go index c1a2f710..cb0326f2 100644 --- a/server/plik.go +++ b/server/plik.go @@ -33,6 +33,7 @@ import ( "errors" "flag" "fmt" + "image/png" "io" "io/ioutil" "math/big" @@ -45,10 +46,13 @@ import ( "strings" "time" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode" + "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/boombuler/barcode/qr" "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/facebookgo/httpdown" "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/gorilla/mux" "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/root-gg/logger" "github.com/root-gg/plik/server/Godeps/_workspace/src/github.com/root-gg/utils" + "github.com/root-gg/plik/server/common" "github.com/root-gg/plik/server/dataBackend" "github.com/root-gg/plik/server/metadataBackend" @@ -67,12 +71,12 @@ func main() { var port = flag.Int("port", 0, "Overrides plik listen port") flag.Parse() if *version { - fmt.Printf("Plikd v%s\n", common.GetVersion()) + fmt.Printf("Plik server %s\n", common.GetBuildInfo()) os.Exit(0) } common.LoadConfiguration(*configFile) - log.Infof("Starting plikd server v" + common.GetVersion()) + log.Infof("Starting plikd server v" + common.GetBuildInfo().Version) // Overrides port if provided in command line if *port != 0 { @@ -92,6 +96,8 @@ func main() { // HTTP Api routes configuration r := mux.NewRouter() + r.HandleFunc("/config", getConfigurationHandler).Methods("GET") + r.HandleFunc("/version", getVersionHandler).Methods("GET") r.HandleFunc("/upload", createUploadHandler).Methods("POST") r.HandleFunc("/upload/{uploadID}", getUploadHandler).Methods("GET") r.HandleFunc("/file/{uploadID}", addFileHandler).Methods("POST") @@ -103,6 +109,7 @@ func main() { r.HandleFunc("/stream/{uploadID}/{fileID}/{filename}", removeFileHandler).Methods("DELETE") r.HandleFunc("/stream/{uploadID}/{fileID}/{filename}", getFileHandler).Methods("HEAD", "GET") r.HandleFunc("/stream/{uploadID}/{fileID}/{filename}/yubikey/{yubikey}", getFileHandler).Methods("GET") + r.HandleFunc("/qrcode", getQrCodeHandler).Methods("GET") r.PathPrefix("/clients/").Handler(http.StripPrefix("/clients/", http.FileServer(http.Dir("../clients")))) r.PathPrefix("/").Handler(http.FileServer(http.Dir("./public/"))) http.Handle("/", r) @@ -140,6 +147,50 @@ func main() { * HTTP HANDLERS */ +func getQrCodeHandler(resp http.ResponseWriter, req *http.Request) { + var err error + ctx := common.NewPlikContext("get qrcode handler", req) + defer ctx.Finalize(err) + + // Check that source IP address is valid and whitelisted + code, err := checkSourceIP(ctx, true) + if err != nil { + http.Error(resp, common.NewResult(err.Error(), nil).ToJSONString(), code) + return + } + + // Check params + urlParam := req.FormValue("url") + sizeParam := req.FormValue("size") + + // Parse int on size + sizeInt, err := strconv.Atoi(sizeParam) + if err != nil { + sizeInt = 250 + } + if sizeInt > 1000 { + http.Error(resp, common.NewResult("QRCode size must be lower than 1000", nil).ToJSONString(), 403) + return + } + + // Generate QRCode png from url + qrcode, err := qr.Encode(urlParam, qr.H, qr.Auto) + if err != nil { + http.Error(resp, common.NewResult(err.Error(), nil).ToJSONString(), 500) + return + } + + // Scale QRCode png size + qrcode, err = barcode.Scale(qrcode, sizeInt, sizeInt) + if err != nil { + http.Error(resp, common.NewResult(err.Error(), nil).ToJSONString(), 500) + return + } + + resp.Header().Add("Content-Type", "image/png") + png.Encode(resp, qrcode) +} + func createUploadHandler(resp http.ResponseWriter, req *http.Request) { var err error ctx := common.NewPlikContext("create upload handler", req) @@ -197,18 +248,18 @@ func createUploadHandler(resp http.ResponseWriter, req *http.Request) { case 0: upload.TTL = common.Config.DefaultTTL case -1: - if common.Config.MaxTTL != 0 { + if common.Config.MaxTTL != -1 { ctx.Warningf("Cannot set infinite ttl (maximum allowed is : %d)", common.Config.MaxTTL) http.Error(resp, common.NewResult(fmt.Sprintf("Cannot set infinite ttl (maximum allowed is : %d)", common.Config.MaxTTL), nil).ToJSONString(), 400) return } default: - if upload.TTL < 0 { + if upload.TTL <= 0 { ctx.Warningf("Invalid value for ttl : %d", upload.TTL) http.Error(resp, common.NewResult(fmt.Sprintf("Invalid value for ttl : %d", upload.TTL), nil).ToJSONString(), 400) return } - if common.Config.MaxTTL != 0 && upload.TTL > common.Config.MaxTTL { + if common.Config.MaxTTL > 0 && upload.TTL > common.Config.MaxTTL { ctx.Warningf("Cannot set ttl to %d (maximum allowed is : %d)", upload.TTL, common.Config.MaxTTL) http.Error(resp, common.NewResult(fmt.Sprintf("Cannot set ttl to %d (maximum allowed is : %d)", upload.TTL, common.Config.MaxTTL), nil).ToJSONString(), 400) return @@ -286,6 +337,13 @@ func createUploadHandler(resp http.ResponseWriter, req *http.Request) { // Create files for i, file := range upload.Files { + + // Check file name length + if len(file.Name) > 1024 { + http.Error(resp, common.NewResult("File name is too long. Maximum length is 1024 characters", nil).ToJSONString(), 401) + return + } + file.GenerateID() file.Status = "missing" delete(upload.Files, i) @@ -409,10 +467,10 @@ func getFileHandler(resp http.ResponseWriter, req *http.Request) { } // Test if upload is not expired - if upload.TTL != 0 { + if upload.TTL > 0 { if time.Now().Unix() >= (upload.Creation + int64(upload.TTL)) { ctx.Warningf("Upload is expired since %s", time.Since(time.Unix(upload.Creation, int64(0)).Add(time.Duration(upload.TTL)*time.Second)).String()) - redirect(req, resp, fmt.Errorf("Upload %s is expired", upload.ID), 404) + redirect(req, resp, fmt.Errorf("Upload %s has expired", upload.ID), 404) return } } @@ -629,6 +687,13 @@ func addFileHandler(resp http.ResponseWriter, req *http.Request) { } if part.FormName() == "file" { file = part + + // Check file name length + if len(part.FileName()) > 1024 { + http.Error(resp, common.NewResult("File name is too long. Maximum length is 1024 characters", nil).ToJSONString(), 401) + return + } + newFile.Name = part.FileName() break } @@ -838,6 +903,34 @@ func removeFileHandler(resp http.ResponseWriter, req *http.Request) { resp.Write(json) } +func getConfigurationHandler(resp http.ResponseWriter, req *http.Request) { + var err error + ctx := common.NewPlikContext("get configuration handler", req) + defer ctx.Finalize(err) + + // Print configuration in the json response. + var json []byte + if json, err = utils.ToJson(common.Config); err != nil { + ctx.Warningf("Unable to serialize response body : %s", err) + http.Error(resp, common.NewResult("Unable to serialize response body", nil).ToJSONString(), 500) + } + resp.Write(json) +} + +func getVersionHandler(resp http.ResponseWriter, req *http.Request) { + var err error + ctx := common.NewPlikContext("get version handler", req) + defer ctx.Finalize(err) + + // Print version and build informations in the json response. + var json []byte + if json, err = utils.ToJson(common.GetBuildInfo()); err != nil { + ctx.Warningf("Unable to serialize response body : %s", err) + http.Error(resp, common.NewResult("Unable to serialize response body", nil).ToJSONString(), 500) + } + resp.Write(json) +} + // //// Misc functions // @@ -932,7 +1025,6 @@ func UploadsCleaningRoutine() { ctx := common.RootContext().Fork("clean expired uploads") for { - // Sleep between 2 hours and 3 hours // This is a dirty trick to avoid frontends doing this at the same time r, _ := rand.Int(rand.Reader, big.NewInt(3600)) @@ -940,15 +1032,13 @@ func UploadsCleaningRoutine() { log.Infof("Will clean old uploads in %d seconds.", randomSleep) time.Sleep(time.Duration(randomSleep) * time.Second) - - // Get uploads that needs to be removed log.Infof("Cleaning expired uploads...") + // Get uploads that needs to be removed uploadIds, err := metadataBackend.GetMetaDataBackend().GetUploadsToRemove(ctx) if err != nil { - log.Warningf("Failed to get expired uploads : %s") + log.Warningf("Failed to get expired uploads : %s", err) } else { - // Remove them for _, uploadID := range uploadIds { ctx.SetUpload(uploadID) diff --git a/server/plikd.cfg b/server/plikd.cfg index 13bf2663..681d613f 100644 --- a/server/plikd.cfg +++ b/server/plikd.cfg @@ -13,7 +13,7 @@ ListenAddress = "0.0.0.0" MaxFileSize = 10737418240 # 10GB DefaultTTL = 2592000 # 30 days -MaxTTL = 2592000 # 0 => No limit +MaxTTL = 2592000 # -1 => No limit SslEnabled = false SslCert = "" # Path to your certificate file diff --git a/server/public/Gruntfile.js b/server/public/Gruntfile.js index 20fe0866..d27596de 100644 --- a/server/public/Gruntfile.js +++ b/server/public/Gruntfile.js @@ -33,8 +33,8 @@ module.exports = function(grunt) { "bower_components/jquery/dist/jquery.js", "bower_components/bootstrap/dist/js/bootstrap.js", "bower_components/angular/angular.js", - "bower_components/danialfarid-angular-file-upload/dist/ng-file-upload-shim.js", - "bower_components/danialfarid-angular-file-upload/dist/ng-file-upload.js", + "bower_components/ng-file-upload/ng-file-upload-shim.js", + "bower_components/ng-file-upload/ng-file-upload.js", "bower_components/angular-sanitize/angular-sanitize.min.js", "bower_components/angular-route/angular-route.js", "bower_components/angular-bootstrap/ui-bootstrap-tpls.js", @@ -52,7 +52,10 @@ module.exports = function(grunt) { css_vendors: { src: [ "bower_components/bootstrap/dist/css/bootstrap.css", - "bower_components/fontawesome/css/font-awesome.css" + "bower_components/bootstrap-flat/css/bootstrap-flat.css", + "bower_components/bootstrap-flat/css/bootstrap-flat-extras.css", + "bower_components/fontawesome/css/font-awesome.css", + "css/water_drop.css" ], dest: 'public/css/vendor.css' } @@ -63,8 +66,7 @@ module.exports = function(grunt) { expand: true, src: [ 'bower_components/bootstrap/fonts/*', - 'bower_components/fontawesome/fonts/fontawesome-webfont.woff', - 'bower_components/fontawesome/fonts/fontawesome-webfont.tff', + 'bower_components/fontawesome/fonts/*', ], dest: 'public/fonts/', flatten: true diff --git a/server/public/bower.json b/server/public/bower.json index 9e609c3d..73d502f6 100644 --- a/server/public/bower.json +++ b/server/public/bower.json @@ -16,22 +16,19 @@ ], "dependencies": { "angular": "~1.2.21", - "bootstrap": "~3.3.4", - "fontawesome": "~4.1.0", - "underscore": "~1.8.3", + "bootstrap": "~3.3.5", + "bootstrap-flat": "~3.3.4", "angular-bootstrap": "~0.11.0", - "filesize": "~2.0.3", - "angular-contenteditable": "~0.3.7", - "zeroclipboard": "~2.2.0", - "ng-clip": "~0.2.6", + "fontawesome": "~4.4.0", + "underscore": "~1.8.3", + "filesize": "~3.1.3", "showdown": "~0.3.4", - "danialfarid-angular-file-upload": "4.1.3", + "ng-file-upload": "~7.2.1", "angular-route": "~1.2.21", "angular-sanitize": "~1.2.21", "angular-markdown-directive": "~0.3.1" }, "resolutions": { - "zeroclipboard": "~2.2.0", "angular": "~1.2.20" } } diff --git a/server/public/css/style.css b/server/public/css/style.css index 50ce0932..87020e12 100644 --- a/server/public/css/style.css +++ b/server/public/css/style.css @@ -1,67 +1,270 @@ -.area{ - position: absolute; - top:15%; - bottom: 0; - left: 0; - right: 0; +/* +The MIT License (MIT) + +Copyright (c) <2015> +- Mathieu Bodjikian +- Charles-Antoine Mathieu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/* Fonts */ + +@font-face { + font-family: 'Courgette'; + src: url('../fonts/courgette-regular-webfont.eot'); + src: url('../fonts/courgette-regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/courgette-regular-webfont.woff2') format('woff2'), + url('../fonts/courgette-regular-webfont.woff') format('woff'), + url('../fonts/courgette-regular-webfont.ttf') format('truetype'), + url('../fonts/courgette-regular-webfont.svg#courgetteregular') format('svg'); + font-weight: normal; + font-style: normal; - margin: auto; } -.wallpaper{ +@font-face { + font-family: 'Open Sans'; + src: url('../fonts/opensans-regular-webfont.eot'); + src: url('../fonts/opensans-regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/opensans-regular-webfont.woff2') format('woff2'), + url('../fonts/opensans-regular-webfont.woff') format('woff'), + url('../fonts/opensans-regular-webfont.ttf') format('truetype'), + url('../fonts/opensans-regular-webfont.svg#open_sansregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* Global */ + +html { + height: 100%; +} + +body { + margin: 0; + padding: 0; + min-height: 100%; + + font-family: 'Open Sans', Helvetica, Arial, serif; + font-size: 15px; + font-weight: 400; + line-height: 1.5; + color: #666; + background: #2d6588 url(../img/background.jpg) no-repeat fixed center center; + background-size: cover; +} + +.col-centered{ + float: none; + margin: 0 auto; +} + +/* Background */ + +.wallpaper { position:fixed; left:0; top:0; + width: 1920px; + height: 1080px; + overflow: hidden; +} + +/* Header */ + +header { width: 100%; - height: 100%; + padding-top: 20px; + padding-bottom: 20px; + font-family: 'Courgette', sans-serif; } -.btn-file { +header h1 { + margin-top: 0; + font-size: 72px; + font-weight: normal; + line-height: 1; + color: #FFFFFF; + letter-spacing: -1px; +} + +header a:hover, header a:visited, header a:link, header a:active{ + color: #FFFFFF; + text-decoration: none; +} + +.header-right { + margin-right: 20px; +} + +.header-inner { position: relative; - overflow: hidden; + margin: 0 auto; } -.btn-file input[type=file] { - position: absolute; - top: 0; - right: 0; - min-width: 100%; + +/* View */ + +#view { + height: 100%; min-height: 100%; - font-size: 999px; - text-align: right; - filter: alpha(opacity=0); - opacity: 0; - outline: none; - background: white; - cursor: inherit; - display: block; -} - -.noselect { - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.table td,th { - text-align: center; + background: transparent; } -.table { - word-wrap: break-word; +/* extra small devices */ +@media all and (max-width: 768px){ + /* view margin */ + #view { + margin-left: 5px; + margin-right: 5px; + } } -.linkSummary { - word-wrap: break-word; +/* small devices */ +@media all and (min-width: 768px) and (max-width: 970px){ + /* view margin */ + #view { + margin-left: 15px; + margin-right: 15px; + } +} + +/* medium devices */ +@media all and (min-width: 970px) and (max-width: 1170px){ + /* view margin */ + #view { + margin-left: 15px; + margin-right: 15px; + } +} + +/* large devices */ +@media all and (min-width: 1170px){ + #view { + margin-left: 35px; + margin-right: 35px; + } } +/* Tile */ + +.tile { + background-color: rgba(255,255,255,0.7); + box-shadow: 2px 2px 5px 0 rgba(0,0,0,0.52); + margin-bottom: 5px; +} + +.tile > .panel-heading { + background-color : ghostwhite; +} + +/* Menu */ + +.menu { + padding: 10px; + font-size: 12px; + line-height: 1.3; +} + +/* Comments */ + .comments { + display: inline-block; word-wrap: break-word; + padding: 5px 5px 5px 5px; +} + +/* Drag and Drop */ + +#drop-zone { + padding-top:15px; + padding-bottom: 15px; + text-align:center; + font-size: 20px; + transition: 0.5s; +} + +.drop-text { + padding-bottom: 10px; +} + +.btn-file { + width: 200px; +} + +.drag-over { + opacity: 0.5; +} + +/* File list */ + +.row-padding { + padding-left: 15px; + padding-right: 15px; +} + +.file { + display: flex; + align-items: center; + min-height: 30px; + padding: 5px 5px 5px 5px; +} + +.file-flex { + display: flex; + align-items: center; +} + +.file-pencil-padding { + padding-right: 10px; +} + +.file-name { + display: inline-block; + word-break: break-all; +} + +.file-name-invalid { + color : red; } -.fileStatus { +.file-status { color : #ff2132; font-size: 18px; +} + +/* Link summary */ + +.link-summary { + padding: 15px; + word-break: break-all; +} + +/* QRCode */ + +.qrcode-header { + text-align: center; + word-break: break-all; +} + +/* Clients */ + +.client-max-width { + max-width:550px; } \ No newline at end of file diff --git a/server/public/css/water_drop.css b/server/public/css/water_drop.css new file mode 100644 index 00000000..d122beb6 --- /dev/null +++ b/server/public/css/water_drop.css @@ -0,0 +1,63 @@ +.pulse1 { + position: fixed; + width: 100px; + height: 100px; + top: 0; left: 0; bottom: 0; right: 0; + z-index: 1; + opacity: 0; + border: 3px solid rgba(255,255,255,.1); + + animation: pulsejg1 1s linear 1; + border-radius: 999px; + box-shadow: inset 0 0 15px 10px rgba(0, 0, 0, .6); + box-sizing: border-box; +} + +.pulse2 { + position: fixed; + width: 100px; + height: 100px; + top: 0; left: 0; bottom: 0; right: 0; + z-index: 2; + opacity: 0; + border: 1px solid rgba(255,255,255,0); + + animation: pulsejg2 1s linear 1; + border-radius: 999px; + box-shadow: inset 0 0 12px 5px rgba(255, 255, 255, .8); + box-sizing: border-box; +} + +@keyframes pulsejg1 { + 0% { + transform: scale(.5); + opacity: 0; + } + + 50% { + transform: scale(1); + opacity: .25; + } + + 100% { + transform: scale(1.5); + opacity: 0; + } +} + +@keyframes pulsejg2 { + 0% { + transform: scale(.4); + opacity: 0; + } + + 60% { + transform: scale(1.2); + opacity: .3; + } + + 100% { + transform: scale(1.3); + opacity: 0; + } +} \ No newline at end of file diff --git a/server/public/fonts/courgette-regular-webfont.eot b/server/public/fonts/courgette-regular-webfont.eot new file mode 100644 index 00000000..c34973de Binary files /dev/null and b/server/public/fonts/courgette-regular-webfont.eot differ diff --git a/server/public/fonts/courgette-regular-webfont.svg b/server/public/fonts/courgette-regular-webfont.svg new file mode 100644 index 00000000..e5be16c0 --- /dev/null +++ b/server/public/fonts/courgette-regular-webfont.svg @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/public/fonts/courgette-regular-webfont.ttf b/server/public/fonts/courgette-regular-webfont.ttf new file mode 100644 index 00000000..9801b8f7 Binary files /dev/null and b/server/public/fonts/courgette-regular-webfont.ttf differ diff --git a/server/public/fonts/courgette-regular-webfont.woff b/server/public/fonts/courgette-regular-webfont.woff new file mode 100644 index 00000000..aedf5a36 Binary files /dev/null and b/server/public/fonts/courgette-regular-webfont.woff differ diff --git a/server/public/fonts/courgette-regular-webfont.woff2 b/server/public/fonts/courgette-regular-webfont.woff2 new file mode 100644 index 00000000..1a0b1204 Binary files /dev/null and b/server/public/fonts/courgette-regular-webfont.woff2 differ diff --git a/server/public/fonts/opensans-regular-webfont.eot b/server/public/fonts/opensans-regular-webfont.eot new file mode 100644 index 00000000..56fb8bfc Binary files /dev/null and b/server/public/fonts/opensans-regular-webfont.eot differ diff --git a/server/public/fonts/opensans-regular-webfont.svg b/server/public/fonts/opensans-regular-webfont.svg new file mode 100644 index 00000000..3e5d0ac3 --- /dev/null +++ b/server/public/fonts/opensans-regular-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/public/fonts/opensans-regular-webfont.ttf b/server/public/fonts/opensans-regular-webfont.ttf new file mode 100644 index 00000000..e149cf21 Binary files /dev/null and b/server/public/fonts/opensans-regular-webfont.ttf differ diff --git a/server/public/fonts/opensans-regular-webfont.woff b/server/public/fonts/opensans-regular-webfont.woff new file mode 100644 index 00000000..ad940ec1 Binary files /dev/null and b/server/public/fonts/opensans-regular-webfont.woff differ diff --git a/server/public/fonts/opensans-regular-webfont.woff2 b/server/public/fonts/opensans-regular-webfont.woff2 new file mode 100644 index 00000000..a34eca83 Binary files /dev/null and b/server/public/fonts/opensans-regular-webfont.woff2 differ diff --git a/server/public/img/background.jpg b/server/public/img/background.jpg index 9a1992e3..00f07445 100644 Binary files a/server/public/img/background.jpg and b/server/public/img/background.jpg differ diff --git a/server/public/index.html b/server/public/index.html index d308b573..1fa5baef 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -26,24 +26,44 @@ + - .: Upload :. + Plik + - + + -
- -
-
+
+ +
+
+
+ +
+

Plik

+
+ + +
+
+
+ + +
@@ -54,5 +74,4 @@ - - + \ No newline at end of file diff --git a/server/public/js/app.js b/server/public/js/app.js index 64fe6705..54e09679 100644 --- a/server/public/js/app.js +++ b/server/public/js/app.js @@ -22,10 +22,69 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// Editable file name directive +angular.module('contentEditable', []). + directive('contenteditable', [ function() { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + scope: { + invalidClass: '@', // Bind invalid-class attr evaluated expr + validator: '&' // Bind parent scope value + }, + link: function(scope, element, attrs, ngModel) { + if (!ngModel) return; // do nothing if no ng-model + scope.validator = scope.validator(); // ??? + + // Update view from model + ngModel.$render = function() { + var string = ngModel.$viewValue; + validate(string); + element.text(string); + }; + + // Update model from view + function update() { + var string = element.text(); + validate(string); + ngModel.$setViewValue(string); + } + + // Validate input and update css class + function validate(string) { + if (scope.validator){ + if(scope.validator(string)){ + element.removeClass(scope.invalidClass); + } else { + element.addClass(scope.invalidClass); + } + } + } + + // Listen for change events to enable binding + element.on('blur keyup change', function() { + scope.$evalAsync(update); + }); + } + }; + }]); + // Modal dialog service angular.module('dialog', ['ui.bootstrap']). factory('$dialog', function ($rootScope, $modal) { + $rootScope.dialogs = []; + + // Register dialog + $rootScope.registerDialog = function($dialog){ + $rootScope.dialogs.push($dialog); + }; + + // Dismiss dialog + $rootScope.dismissDialog = function($dialog) { + $rootScope.dialogs = _.without($rootScope.dialogs, $dialog); + }; + var module = {}; // alert dialog @@ -34,7 +93,7 @@ angular.module('dialog', ['ui.bootstrap']). var options = { backdrop: true, backdropClick: true, - templateUrl: 'partials/alertDialogPartial.html', + templateUrl: 'partials/alert.html', controller: 'AlertDialogController', resolve: { args: function () { @@ -48,7 +107,6 @@ angular.module('dialog', ['ui.bootstrap']). }; // generic dialog - $rootScope.dialogs = []; module.openDialog = function (options) { if (!options) return false; @@ -142,11 +200,23 @@ angular.module('api', ['ngFileUpload']). return api.call(url, 'DELETE', {}, upload.uploadToken); }; + // Get server version + api.getVersion = function() { + var url = api.base + '/version'; + return api.call(url, 'GET', {}); + }; + + // Get server config + api.getConfig = function() { + var url = api.base + '/config'; + return api.call(url, 'GET', {}); + }; + return api; }); // Plik app bootstrap and global configuration -angular.module('plik', ['ngRoute', 'api', 'dialog', 'contenteditable', 'ngClipboard', 'ngSanitize', 'btford.markdown']) +angular.module('plik', ['ngRoute', 'api', 'dialog', 'contentEditable', 'btford.markdown']) .config(function ($routeProvider) { $routeProvider .when('/', {controller: MainCtrl, templateUrl: 'partials/main.html', reloadOnSearch: false}) @@ -156,9 +226,6 @@ angular.module('plik', ['ngRoute', 'api', 'dialog', 'contenteditable', 'ngClipbo .config(['$httpProvider', function ($httpProvider) { $httpProvider.defaults.headers.common['X-ClientApp'] = 'web_client'; }]) - .config(['ngClipProvider', function (ngClipProvider) { - ngClipProvider.setPath("bower_components/zeroclipboard/dist/ZeroClipboard.swf"); - }]) .filter('collapseClass', function () { return function (opened) { if (opened) return "fa fa-caret-down"; @@ -176,6 +243,27 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { $scope.yubikey = false; $scope.password = false; + // Get server config + $api.getConfig() + .then(function (config) { + $scope.config = config; + $scope.setDefaultTTL(); + }) + .then(null, function (error) { + $dialog.alert(error); + }); + + // File name checks + var fileNameMaxLength = 1024; + var invalidCharList = ['/','#','?','%','"']; + $scope.fileNameValidator = function(fileName) { + if(_.isUndefined(fileName)) return false; + if(fileName.length == 0 || fileName.length > fileNameMaxLength) return false; + return _.every(invalidCharList, function(char){ + return fileName.indexOf(char) == -1; + }); + }; + // Initialize main controller $scope.init = function () { // Display error from redirect if any @@ -191,7 +279,7 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { } } else { // Load current upload id - $scope.load($location.search().id) + $scope.load($location.search().id); $scope.upload.uploadToken = $location.search().uploadToken; } }; @@ -201,10 +289,10 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { if (!id) return; $scope.upload.id = id; $api.getUpload($scope.upload.id) - .then(function (metadatas) { - _.extend($scope.upload, metadatas); - $scope.files = _.map($scope.upload.files, function (metadata) { - return {metadata: metadata}; + .then(function (upload) { + _.extend($scope.upload, upload); + $scope.files = _.map($scope.upload.files, function (file) { + return {metadata: file}; }); }) .then(null, function (error) { @@ -219,21 +307,93 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { return reference.toString(); }; + // Detect shitty Apple devices + $scope.isAppleShit = function(){ + return navigator.userAgent.match(/iPhone/i) + || navigator.userAgent.match(/iPad/i) + || navigator.userAgent.match(/iPod/i); + }; + // Add a file to the upload list $scope.onFileSelect = function (files) { _.each(files, function (file) { - // remove already added files + // Check file size + if($scope.config.maxFileSize && file.size > $scope.config.maxFileSize){ + $dialog.alert({ + status: 0, + message: "File is too big : " + $scope.humanReadableSize(file.size), + value: "Maximum allowed size is : " + $scope.humanReadableSize($scope.config.maxFileSize) + }); + return; + } + + // Already added file names var names = _.pluck($scope.files, 'name'); - if (!_.contains(names, file.name)) { + + // iPhone/iPad/iPod fix + // Apple mobile devices does not populate file name + // well and tends to use something like image.jpg + // every time a new image is selected. + // If this appends an increment is added in the middle of + // the filename ( image.1.jpg ) + // As a result of this the same file can be uploaded twice. + if ($scope.isAppleShit() && _.contains(names, file.name)){ file.reference = nextRef(); - file.fileName = file.name; - file.fileSize = file.size; - file.fileType = file.type; - $scope.files.push(file); + + // Extract file name and extension and add increment + var sep = file.name.lastIndexOf('.'); + var name = sep ? file.name.substr(0,sep) : file.name; + var ext = file.name.substr(sep + 1); + name = name + '.' + file.reference + '.' + ext; + + // file.name is supposed to be read-only ... + Object.defineProperty(file,"name",{value: name, writable: true}); } + + // remove duplicate files + if (_.contains(names, file.name)) return; + + // Set reference to match file id in the response + if(!file.reference) file.reference = nextRef(); + + // Use correct json fields + file.fileName = file.name; + file.fileSize = file.size; + file.fileType = file.type; + + $scope.files.push(file); }); }; + // Kikoo style water drop effect + $scope.waterDrop = function(event){ + var body = $('body'); + + // Create div centered on mouse click event + var pulse1 = $(document.createElement('div')) + .css({ left : event.clientX - 50, top : event.clientY - 50 }) + .appendTo(body); + var pulse2 = $(document.createElement('div')) + .css({ left : event.clientX - 50, top : event.clientY - 50 }) + .appendTo(body); + + // Add animation class + pulse1.addClass("pulse1"); + pulse2.addClass("pulse2"); + + // Clean after animation + setTimeout(function(){ + pulse1.remove(); + pulse2.remove(); + },1100); + }; + + // Called when a file is dropped + $scope.onFileDrop = function(files,event){ + $scope.onFileSelect(files); + $scope.waterDrop(event); + }; + // Remove a file from the upload list $scope.removeFile = function (file) { $scope.files = _.reject($scope.files, function (f) { @@ -244,6 +404,8 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { // Create a new upload $scope.newUpload = function () { if (!$scope.files.length) return; + // Get TTL value + if(!$scope.checkTTL()) return; $scope.upload.ttl = $scope.getTTL(); // HTTP basic auth prompt dialog if ($scope.password && !($scope.upload.login && $scope.upload.password)) { @@ -251,13 +413,30 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { return; } // Yubikey prompt dialog - if ($scope.yubikey && !$scope.upload.yubikey) { + if ($scope.config.yubikeyEnabled && $scope.yubikey && !$scope.upload.yubikey) { $scope.getYubikey(); return; } // Create file to upload list $scope.upload.files = {}; - _.each($scope.files, function (file) { + var ko = _.find($scope.files, function (file) { + // Check file name length + if(file.fileName.length > fileNameMaxLength) { + $dialog.alert({ + status: 0, + message: "File name max length is " + fileNameMaxLength + " characters" + }); + return true; // break find loop + } + // Check invalid characters + if (!$scope.fileNameValidator(file.fileName)) { + $dialog.alert({ + status: 0, + message: "Invalid file name " + file.fileName + "\n", + value: "Forbidden characters are : " + invalidCharList.join(' ') + }); + return true; // break find loop + } // Sanitize file object $scope.upload.files[file.reference] = { fileName : file.fileName, @@ -266,6 +445,7 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { reference : file.reference }; }); + if(ko) return; $api.createUpload($scope.upload) .then(function (upload) { $scope.upload = upload; @@ -292,12 +472,12 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { if (!$scope.upload.id) return; _.each($scope.files, function (file) { if (!file.metadata.id) return; - // Progess bar callback - var cb = function (event) { - $scope.progress(file, event) + var progress = function (event) { + // Update progress bar callback + file.progress = parseInt(100.0 * event.loaded / event.total); }; file.metadata.status = "uploading"; - $api.uploadFile($scope.upload, file, cb, $scope.basicAuth) + $api.uploadFile($scope.upload, file, progress, $scope.basicAuth) .then(function (metadata) { file.metadata = metadata; }) @@ -321,16 +501,11 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { $route.reload(); } }) - .then(function (error) { + .then(null, function (error) { $dialog.alert(error); }); }; - // Upload progess bar callback - $scope.progress = function (file, event) { - file.progress = parseInt(100.0 * event.loaded / event.total); - }; - // Compute human readable size $scope.humanReadableSize = function (size) { if (_.isUndefined(size)) return; @@ -341,13 +516,63 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { $scope.getFileUrl = function (file, dl) { if (!file || !file.metadata) return; var mode = $scope.upload.stream ? "stream" : "file"; - var url = location.origin + '/' + mode + '/' + $scope.upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName + var url = location.origin + '/' + mode + '/' + $scope.upload.id + '/' + file.metadata.id + '/' + file.metadata.fileName; if (dl) { // Force file download url += "?dl=1"; } - return url + return url; + }; + + // Return QR Code image url + $scope.getQrCodeUrl = function (url, size) { + if (!url) return; + return location.origin + "/qrcode?url=" + encodeURIComponent(url) + "&size=" + size; + }; + + // Return QR Code image url for current upload + $scope.getQrCodeUploadUrl = function(size) { + return $scope.getQrCodeUrl(window.location.href,size); + }; + + // Return QR Code image url for file + $scope.getQrCodeFileUrl = function(file, size) { + return $scope.getQrCodeUrl($scope.getFileUrl($scope.qrcode, false),size); + }; + + // Display QR Code dialog for current upload + $scope.displayQRCodeUpload = function() { + var url = window.location.href; + var qrcode = $scope.getQrCodeUrl(url, 400); + $scope.displayQRCode(url,url,qrcode); + }; + + // Display QR Code dialog for file + $scope.displayQRCodeFile = function(file) { + var url = $scope.getFileUrl(file, false); + var qrcode = $scope.getQrCodeUrl(url, 400); + $scope.displayQRCode(file.metadata.fileName,url,qrcode); + }; + + // Display QRCode dialog + $scope.displayQRCode = function(title, url, qrcode) { + var opts = { + backdrop: true, + backdropClick: true, + templateUrl: 'partials/qrcode.html', + controller: 'QRCodeController', + resolve: { + args: function () { + return { + title: title, + url: url, + qrcode: qrcode + }; + } + } + }; + $dialog.openDialog(opts); }; // Basic auth credentials dialog @@ -370,7 +595,6 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { } } }; - $dialog.openDialog(opts); }; @@ -392,7 +616,6 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { } } }; - $dialog.openDialog(opts); }; @@ -414,11 +637,9 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { } } }; - $dialog.openDialog(opts); }; - $scope.ttlUnits = ["days", "hours", "minutes"]; $scope.ttlUnit = "days"; $scope.ttlValue = 30; @@ -440,51 +661,115 @@ function MainCtrl($scope, $dialog, $route, $location, $api) { } else if ($scope.ttlUnit == "days") { ttl = ttl * 86400; } + } else { + ttl = -1; } return ttl; }; + // Return TTL unit and value + $scope.getHumanReadableTTL = function (ttl) { + var value,unit; + if (ttl == -1){ + value = -1; + unit = "never" + } else if(ttl < 3600){ + value = Math.round(ttl / 60); + unit = "minutes" + } else if (ttl < 86400){ + value = Math.round(ttl / 3600); + unit = "hours" + } else if (ttl > 86400){ + value = Math.round(ttl / 86400); + unit = "days" + } else { + value = 0; + unit = "invalid"; + } + return [value,unit]; + }; + + // Check TTL value + $scope.checkTTL = function() { + var ok = true; + + // Fix never value + if ($scope.ttlUnit == 'never') { + $scope.ttlValue = -1; + } + + // Get TTL in seconds + var ttl = $scope.getTTL(); + + // Invalid negative value + if ($scope.ttlUnit != 'never' && ttl < 0) ok = false; + // Check against server side allowed maximum + if ($scope.config.maxTTL > 0 && ttl > $scope.config.maxTTL) ok = false; + + if (!ok) { + var maxTTL = $scope.getHumanReadableTTL($scope.config.maxTTL); + $dialog.alert({ + status: 0, + message: "Invalid expiration delay : " + $scope.ttlValue + " " + $scope.ttlUnit, + value: "Maximum expiration delay is : " + maxTTL[0] + " " + maxTTL[1] + }); + $scope.setDefaultTTL(); + } + + return ok; + }; + + // Set TTL value to server defaultTTL + $scope.setDefaultTTL = function(){ + if($scope.config.maxTTL == -1){ + // Never expiring upload is allowed + $scope.ttlUnits = ["days", "hours", "minutes", "never"]; + } + var ttl = $scope.getHumanReadableTTL($scope.config.defaultTTL); + $scope.ttlValue = ttl[0]; + $scope.ttlUnit = ttl[1]; + }; + // Return upload expiration date string $scope.getExpirationDate = function () { - var d = new Date(($scope.upload.ttl + $scope.upload.uploadDate) * 1000); - return d.toLocaleDateString() + " at " + d.toLocaleTimeString(); + if ($scope.upload.ttl == -1) { + return "never expire"; + } else { + var d = new Date(($scope.upload.ttl + $scope.upload.uploadDate) * 1000); + return "expire the " + d.toLocaleDateString() + " at " + d.toLocaleTimeString(); + } + }; - $scope.adminUrl = function () { + // Add upload token in url so one can add/remove files later + $scope.setAdminUrl = function () { $location.search('uploadToken', $scope.upload.uploadToken); }; + // Focus the given element by id + $scope.focus = function(id) { + angular.element('#'+id)[0].focus(); + }; + $scope.init(); } // Client download controller -function ClientListCtrl($scope, $location) { +function ClientListCtrl($scope, $api, $dialog) { $scope.clients = []; - $scope.addClient = function (name, arch, binary) { - if (!binary) binary = "plik"; - $scope.clients.push({name: name, url: location.origin + "/clients/" + arch + "/" + binary}); - }; - - // This list should not be hardcoded here - $scope.addClient("Linux 64bit", "linux-amd64"); - $scope.addClient("Linux 32bit", "linux-386"); - $scope.addClient("Linux ARM", "linux-arm"); - $scope.addClient("MacOS 64bit", "darwin-amd64"); - $scope.addClient("MacOS 32bit", "darwin-386"); - $scope.addClient("Freebsd 64bit", "freebsd-amd64"); - $scope.addClient("Freebsd 32bit", "freebsd-386"); - $scope.addClient("Freebsd ARM", "freebsd-arm"); - $scope.addClient("Openbsd 64bit", "openbsd-amd64"); - $scope.addClient("Openbsd 32bit", "openbsd-386"); - $scope.addClient("Windows 64bit", "windows-amd64", "plik.exe"); - $scope.addClient("Windows 32bit", "windows-386", "plik.exe"); - $scope.addClient("Bash (curl)", "bash", "plik.sh"); + $api.getVersion() + .then(function (buildInfo) { + $scope.clients = buildInfo.clients; + }) + .then(null, function (error) { + $dialog.alert(error); + }); } // Alert modal dialog controller function AlertDialogController($rootScope, $scope, $modalInstance, args) { - $rootScope.dialogs.push($scope); + $rootScope.registerDialog($scope); $scope.title = 'Success !'; if (args.data.status != 100) $scope.title = 'Oops !'; @@ -492,7 +777,7 @@ function AlertDialogController($rootScope, $scope, $modalInstance, args) { $scope.data = args.data; $scope.close = function (result) { - $rootScope.dialogs = _.without($rootScope.dialogs, $scope); + $rootScope.dismissDialog($scope); $modalInstance.close(result); if (args.callback) { args.callback(result); @@ -502,7 +787,7 @@ function AlertDialogController($rootScope, $scope, $modalInstance, args) { // HTTP basic auth credentials dialog controller function PasswordController($rootScope, $scope, $modalInstance, args) { - $rootScope.dialogs.push($scope); + $rootScope.registerDialog($scope); // Ugly but it works setTimeout(function () { @@ -510,8 +795,8 @@ function PasswordController($rootScope, $scope, $modalInstance, args) { }, 100); $scope.title = 'Please fill credentials !'; - $scope.login = "plik"; - $scope.password = ""; + $scope.login = 'plik'; + $scope.password = ''; $scope.close = function (login, password) { if (!(login.length > 0 && password.length > 0)) { @@ -524,14 +809,14 @@ function PasswordController($rootScope, $scope, $modalInstance, args) { }; $scope.dismiss = function () { - $rootScope.dialogs = _.without($rootScope.dialogs, $scope); + $rootScope.dismissDialog($scope); $modalInstance.close(); } } // Yubikey dialog controller function YubikeyController($rootScope, $scope, $modalInstance, args) { - $rootScope.dialogs.push($scope); + $rootScope.registerDialog($scope); // Ugly but it works setTimeout(function () { @@ -539,7 +824,7 @@ function YubikeyController($rootScope, $scope, $modalInstance, args) { }, 100); $scope.title = 'Please fill in a Yubikey OTP !'; - $scope.token = ""; + $scope.token = ''; $scope.check = function (token) { if (token.length == 44) { @@ -555,7 +840,19 @@ function YubikeyController($rootScope, $scope, $modalInstance, args) { }; $scope.dismiss = function () { - $rootScope.dialogs = _.without($rootScope.dialogs, $scope); + $rootScope.dismissDialog($scope); $modalInstance.close(); } +} + +// QRCode dialog controller +function QRCodeController($rootScope, $scope, $modalInstance, args) { + $rootScope.registerDialog($scope); + + $scope.args = args; + + $scope.close = function () { + $rootScope.dismissDialog($scope); + $modalInstance.close(); + }; } \ No newline at end of file diff --git a/server/public/package.json b/server/public/package.json index b4e5ca06..bcbee160 100644 --- a/server/public/package.json +++ b/server/public/package.json @@ -1,7 +1,15 @@ { "name": "plik-webapp", + "description": "This is the web frontend of plik upload system", + "repository": { + "type": "git", + "url": "git://github.com/root-gg/plik.git" + }, + "readme": "README.md", "devDependencies": { + "bower": "^1.5.2", "grunt": "^0.4.5", + "grunt-cli": "^0.1.13", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-concat": "^0.5.1", "grunt-contrib-copy": "^0.8.0", @@ -11,4 +19,4 @@ "grunt-ng-annotate": "^0.10.0", "load-grunt-tasks": "^1.0.0" } -} \ No newline at end of file +} diff --git a/server/public/partials/alertDialogPartial.html b/server/public/partials/alert.html similarity index 77% rename from server/public/partials/alertDialogPartial.html rename to server/public/partials/alert.html index 248d43a9..ad8422df 100644 --- a/server/public/partials/alertDialogPartial.html +++ b/server/public/partials/alert.html @@ -28,20 +28,9 @@

{{title}} ({{data.status}})