├── LICENSE
├── README.md
├── build
├── client.js.go
├── index.html.go
├── package.go
└── script.js.go
├── cmd
├── example
│ └── main.go
├── golang-nw-build
│ └── build.go
└── golang-nw-pkg
│ └── pkg.go
├── doc.go
├── nw.go
└── pkg
└── pkg.go
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | golang-nw
2 | =========
3 |
4 | Call a golang web application from node-webkit to get a native looking application.
5 |
6 | See [the documentation](http://godoc.org/github.com/lonnc/golang-nw) for usage instructions.
7 |
--------------------------------------------------------------------------------
/build/client.js.go:
--------------------------------------------------------------------------------
1 | package build
2 |
3 | const client = `
4 | "use strict";
5 |
6 | exports.createClient = function(args) {
7 | var events = require('events');
8 | var channel = new events.EventEmitter();
9 | var http = require('http');
10 | var server = http.createServer(function (request, response) {
11 | if (request.method!='POST') {
12 | response.writeHead(404);
13 | response.end('');
14 | return;
15 | }
16 |
17 | var body = '';
18 | request.on('data', function(chunk) { body += chunk.toString(); });
19 | request.on('end', function() {
20 | switch (request.url) {
21 | case '/redirect':
22 | channel.emit('redirect', body);
23 | response.writeHead(204);
24 | response.end('');
25 | break;
26 | case '/error':
27 | channel.emit('error', body);
28 | response.writeHead(204);
29 | response.end('');
30 | break;
31 | default:
32 | response.writeHead(404);
33 | response.end('');
34 | };
35 | });
36 | });
37 |
38 | server.listen(0, '127.0.0.1', 1, function() {
39 | var nodeWebkitAddr = 'http://127.0.0.1:'+server.address().port;
40 | console.log('Listening for golang-nw on '+nodeWebkitAddr);
41 | startClient(channel, nodeWebkitAddr, args);
42 | });
43 |
44 | return channel;
45 | };
46 |
47 | function logMessage(data, logger) {
48 | var lines = data.toString().split('\n');
49 | for (var i = 0; i < lines.length; i++) {
50 | if (lines[i]) {
51 | logger.call(console, lines[i]);
52 | }
53 | }
54 | }
55 |
56 | function startClient(channel, nodeWebkitAddr, args) {
57 | var path = require('path');
58 | var exe = '.'+path.sep+'{{ .Bin }}';
59 | console.log('Using client: ' + exe);
60 |
61 | // Make the exe executable
62 | var fs = require('fs');
63 | fs.chmodSync(exe, '755');
64 |
65 | // Now start the client process
66 | var childProcess = require('child_process');
67 |
68 | var env = process.env;
69 | env['{{ .EnvVar }}'] = nodeWebkitAddr;
70 | var p = childProcess.spawn(exe, args, {env: env});
71 |
72 | p.stdout.on('data', function(data) {
73 | logMessage(data, console.log);
74 | });
75 |
76 | p.stderr.on('data', function(data) {
77 | logMessage(data, console.error);
78 | });
79 |
80 | p.on('error', function(err) {
81 | console.error('child error: ' + err);
82 | channel.emit('error', err);
83 | });
84 |
85 | p.on('close', function(code) {
86 | console.log('child process closed with code ' + code);
87 | channel.emit('close', code);
88 | });
89 |
90 | p.on('exit', function(code) {
91 | console.log('child process exited with code ' + code);
92 | channel.emit('exit', code);
93 | });
94 |
95 | channel.kill = function() {
96 | p.kill();
97 | }
98 | };
99 | `
100 |
--------------------------------------------------------------------------------
/build/index.html.go:
--------------------------------------------------------------------------------
1 | package build
2 |
3 | const index = `
4 |
5 |
6 |
7 | {{ .Name }}
8 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | `
26 |
--------------------------------------------------------------------------------
/build/package.go:
--------------------------------------------------------------------------------
1 | package build
2 |
3 | import (
4 | "archive/zip"
5 | "encoding/json"
6 | "github.com/lonnc/golang-nw"
7 | "io"
8 | "io/ioutil"
9 | "os"
10 | "path"
11 | "path/filepath"
12 | "strings"
13 | "text/template"
14 | )
15 |
16 | type Package struct {
17 | Name string `json:"name"`
18 | Main string `json:"main"`
19 | Window Window `json:"window"`
20 | Bin string `json:"-"`
21 | EnvVar string `json:"-"`
22 | }
23 |
24 | type Window struct {
25 | Title string `json:"title,omitempty"`
26 | Toolbar bool `json:"toolbar"`
27 | Show bool `json:"show,omitempty"`
28 | Position string `json:"position,omitempty"`
29 | Width int `json:"width,omitempty"`
30 | Height int `json:"height,omitempty"`
31 | Fullscreen bool `json:"fullscreen"`
32 | Frame bool `json:"frame"`
33 | }
34 |
35 | type Templates struct {
36 | IndexHtml string
37 | ClientJs string
38 | ScriptJs string
39 | }
40 |
41 | var DefaultTemplates = Templates{IndexHtml: index, ClientJs: client, ScriptJs: script}
42 |
43 | // CreateNW creates a node-webkit .nw file
44 | func (p Package) CreateNW(zw *zip.Writer, templates Templates, myapp io.Reader, includes string) error {
45 | // Add in a couple of package defaults
46 | p.Main = "index.html"
47 | p.EnvVar = nw.EnvVar
48 |
49 | if w, err := zw.Create("package.json"); err != nil {
50 | return err
51 | } else {
52 | if _, err := p.writeJsonTo(w); err != nil {
53 | return err
54 | }
55 | }
56 |
57 | filenameTemplates := map[string]string{
58 | "index.html": templates.IndexHtml,
59 | "client.js": templates.ClientJs,
60 | "script.js": templates.ScriptJs}
61 | for filename, str := range filenameTemplates {
62 | if w, err := zw.Create(filename); err != nil {
63 | return err
64 | } else {
65 | if t, err := template.New(filename).Parse(str); err != nil {
66 | return err
67 | } else {
68 | if err := t.Execute(w, p); err != nil {
69 | return err
70 | }
71 | }
72 | }
73 | }
74 |
75 | if includes != "" {
76 | if err := copyIncludes(zw, includes); err != nil {
77 | return err
78 | }
79 | }
80 |
81 | binHeader := zip.FileHeader{Name: p.Bin}
82 | binHeader.SetMode(0755) // Make it executable
83 | if w, err := zw.CreateHeader(&binHeader); err != nil {
84 | return err
85 | } else {
86 | if _, err := io.Copy(w, myapp); err != nil {
87 | return err
88 | }
89 | }
90 |
91 | return nil
92 | }
93 |
94 | func (p Package) writeJsonTo(w io.Writer) (int64, error) {
95 | b, err := json.Marshal(p)
96 | if err != nil {
97 | return 0, err
98 | }
99 | n, err := w.Write(b)
100 | return int64(n), err
101 | }
102 |
103 | // Copy any files from the includes directory
104 | func copyIncludes(zw *zip.Writer, includes string) error {
105 | includes = path.Clean(includes)
106 | if !strings.HasSuffix(includes, "/") {
107 | includes += "/"
108 | }
109 | return filepath.Walk(includes, func(path string, info os.FileInfo, err error) error {
110 | if err != nil {
111 | return err
112 | }
113 | if info.IsDir() {
114 | return nil
115 | }
116 | path = filepath.ToSlash(path)
117 | if w, err := zw.Create(strings.TrimPrefix(path, includes)); err != nil {
118 | return err
119 | } else {
120 | b, err := ioutil.ReadFile(path)
121 | if err != nil {
122 | return err
123 | } else {
124 | _, err := w.Write(b)
125 | if err != nil {
126 | return err
127 | }
128 | }
129 | }
130 | return nil
131 | })
132 | }
133 |
--------------------------------------------------------------------------------
/build/script.js.go:
--------------------------------------------------------------------------------
1 | package build
2 |
3 | const script = `
4 | "use strict";
5 |
6 | var gui = require('nw.gui');
7 | var os = require('os');
8 | var win = gui.Window.get();
9 |
10 | win.on('loaded', function() {
11 | // Restore window location on startup.
12 | if (window.localStorage.width && window.localStorage.height) {
13 | win.resizeTo(parseInt(window.localStorage.width), parseInt(window.localStorage.height));
14 | win.moveTo(parseInt(window.localStorage.x), parseInt(window.localStorage.y));
15 | }
16 |
17 | // Ensure we are visible
18 | win.show();
19 |
20 | // Start client
21 | var client = require('./client.js');
22 |
23 | var msg = function(s) {
24 | var state = document.getElementById('state');
25 | state.appendChild(document.createTextNode(s + '\n'));
26 | };
27 |
28 | var clientProcess = client.createClient(gui.App.argv);
29 | clientProcess.
30 | on('error', function(err) {
31 | msg('Error: ' + err);
32 | }).
33 | on('redirect', function(url) {
34 | window.location.href = url;
35 | });
36 |
37 | // And kill client when we close the window
38 | win.on('close', function() {
39 | clientProcess.kill();
40 | });
41 | });
42 |
43 | // Save size on close.
44 | win.on('close', function() {
45 | window.localStorage.x = win.x;
46 | window.localStorage.y = win.y;
47 | window.localStorage.width = win.width;
48 | window.localStorage.height = win.height;
49 | this.close(true);
50 | });
51 |
52 | // Workaround for copy-paste working on Mac.
53 | if(os.platform() == "darwin") {
54 | var nativeMenuBar = new gui.Menu({ type: "menubar" });
55 | nativeMenuBar.createMacBuiltin("App Name");
56 | win.menu = nativeMenuBar;
57 | }
58 |
59 | `
60 |
--------------------------------------------------------------------------------
/cmd/example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/lonnc/golang-nw"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | // Setup our handler
11 | http.HandleFunc("/", hello)
12 |
13 | // Create a link back to node-webkit using the environment variable
14 | // populated by golang-nw's node-webkit code
15 | nodeWebkit, err := nw.New()
16 | if err != nil {
17 | panic(err)
18 | }
19 |
20 | // Pick a random localhost port, start listening for http requests using default handler
21 | // and send a message back to node-webkit to redirect
22 | if err := nodeWebkit.ListenAndServe(nil); err != nil {
23 | panic(err)
24 | }
25 | }
26 |
27 | func hello(w http.ResponseWriter, r *http.Request) {
28 | fmt.Fprintf(w, "Hello from golang.")
29 | }
30 |
--------------------------------------------------------------------------------
/cmd/golang-nw-build/build.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "archive/zip"
5 | "flag"
6 | "github.com/lonnc/golang-nw/build"
7 | "os"
8 | "path/filepath"
9 | )
10 |
11 | var (
12 | app = "myapp.exe"
13 | out = "myapp.nw"
14 | name = "My Application"
15 | includesDir = ""
16 | )
17 |
18 | func main() {
19 | flag.StringVar(&app, "app", app, "Application to be wrapped by node-webkit.")
20 | flag.StringVar(&name, "name", name, "Application name.")
21 | flag.StringVar(&out, "out", out, "Destination file for generated node-webkit .nw file.")
22 | flag.StringVar(&includesDir, "includesDir", includesDir, "Directory containing additional files to bundle with the .nw file")
23 | flag.Parse()
24 |
25 | if err := nwBuild(); err != nil {
26 | panic(err)
27 | }
28 | }
29 |
30 | func nwBuild() error {
31 | w, err := os.Create(out)
32 | if err != nil {
33 | return err
34 | }
35 | defer w.Close()
36 |
37 | zw := zip.NewWriter(w)
38 | defer zw.Close()
39 |
40 | r, err := os.Open(app)
41 | if err != nil {
42 | return err
43 | }
44 | defer r.Close()
45 |
46 | bin := filepath.Base(app)
47 | p := build.Package{Name: name, Bin: bin, Window: build.Window{Title: name}}
48 |
49 | if err := p.CreateNW(zw, build.DefaultTemplates, r, includesDir); err != nil {
50 | return err
51 | }
52 |
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/cmd/golang-nw-pkg/pkg.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "archive/zip"
5 | "flag"
6 | "fmt"
7 | "github.com/lonnc/golang-nw/build"
8 | "github.com/lonnc/golang-nw/pkg"
9 | "os"
10 | "path/filepath"
11 | "runtime"
12 | )
13 |
14 | var (
15 | app = "myapp"
16 | name = "My Application"
17 | bin = "myapp"
18 | binDir = "."
19 | cacheDir = "."
20 | nwVersion = "v0.11.6"
21 | nwOs = runtime.GOOS
22 | nwArch = runtime.GOARCH
23 | toolbar = true
24 | includesDir = ""
25 | fullscreen = false
26 | frame = true
27 | )
28 |
29 | func main() {
30 | if runtime.GOOS == "windows" {
31 | app = app + ".exe"
32 | bin = bin + ".exe"
33 | }
34 |
35 | flag.StringVar(&app, "app", app, "Web application to be wrapped by node-webkit.")
36 | flag.StringVar(&name, "name", name, "Application name.")
37 | flag.StringVar(&bin, "bin", bin, "Destination file for combined application and node-webkit .nw file (will be placed in binDir directory).")
38 | flag.StringVar(&binDir, "binDir", binDir, "Destination directory for bin and dependencies.")
39 | flag.StringVar(&cacheDir, "cacheDir", cacheDir, "Directory to cache node-webkit download.")
40 | flag.StringVar(&nwVersion, "version", nwVersion, "node-webkit version.")
41 | flag.StringVar(&nwOs, "os", nwOs, "Target os [linux|windows|darwin].")
42 | flag.StringVar(&nwArch, "arch", nwArch, "Target arch [386|amd64].")
43 | flag.BoolVar(&toolbar, "toolbar", toolbar, "Enable toolbar.")
44 | flag.StringVar(&includesDir, "includesDir", includesDir, "Directory containing additional files to bundle with the .nw file")
45 | flag.BoolVar(&fullscreen, "fullscreen", fullscreen, "Enable fullscreen mode.")
46 | flag.BoolVar(&frame, "frame", frame, "Set to false to make window frameless.")
47 | flag.Parse()
48 |
49 | p := pkg.New(nwVersion, nwOs, nwArch)
50 |
51 | if filepath.Base(bin) != bin {
52 | panic(fmt.Errorf("bin %q includes a path", bin))
53 | }
54 |
55 | nw := filepath.Join(binDir, bin+".nw")
56 | fmt.Printf("Building:\t %s\n", nw)
57 | if err := nwBuild(nw); err != nil {
58 | panic(err)
59 | }
60 |
61 | fmt.Printf("Downloading:\t %s\n", p.Url)
62 | // Ensure cache directory exists
63 | if err := os.MkdirAll(cacheDir, 0755); err != nil {
64 | panic(err)
65 | }
66 | nodeWebkitPath, err := nwDownload(p)
67 | if err != nil {
68 | panic(err)
69 | }
70 |
71 | out := filepath.Join(binDir, bin)
72 | fmt.Printf("Packaging:\t %s\n", out)
73 | // Ensure bin directory exists
74 | if err := os.MkdirAll(binDir, 0755); err != nil {
75 | panic(err)
76 | }
77 |
78 | if err := nwPkg(p, nodeWebkitPath, nw); err != nil {
79 | panic(err)
80 | }
81 | }
82 |
83 | func nwBuild(nw string) error {
84 | w, err := os.Create(nw)
85 | if err != nil {
86 | return err
87 | }
88 | defer w.Close()
89 |
90 | zw := zip.NewWriter(w)
91 | defer zw.Close()
92 |
93 | r, err := os.Open(app)
94 | if err != nil {
95 | return err
96 | }
97 | defer r.Close()
98 |
99 | bin := filepath.Base(app)
100 | p := build.Package{Name: name, Bin: bin, Window: build.Window{Title: name, Toolbar: toolbar, Fullscreen: fullscreen, Frame: frame}}
101 |
102 | if err := p.CreateNW(zw, build.DefaultTemplates, r, includesDir); err != nil {
103 | return err
104 | }
105 |
106 | return nil
107 | }
108 |
109 | func nwDownload(p pkg.Pkg) (string, error) {
110 | return p.Download(cacheDir)
111 | }
112 |
113 | func nwPkg(p pkg.Pkg, nodeWebkitPath string, nw string) error {
114 | r, err := os.Open(nw)
115 | if err != nil {
116 | return err
117 | }
118 | defer r.Close()
119 |
120 | if err := p.Package(nodeWebkitPath, r, bin, binDir); err != nil {
121 | return err
122 | }
123 |
124 | return nil
125 | }
126 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Call a golang web application from node-webkit to get a native looking application.
3 |
4 |
5 | Instructions
6 |
7 |
8 | Go get golang-nw:
9 |
10 | go get github.com/lonnc/golang-nw/cmd/golang-nw-pkg
11 |
12 | Create an app:
13 |
14 | See https://github.com/lonnc/golang-nw/blob/master/cmd/example/main.go
15 | package main
16 |
17 | import (
18 | "fmt"
19 | "github.com/lonnc/golang-nw"
20 | "net/http"
21 | )
22 |
23 | func main() {
24 | // Setup our handler
25 | http.HandleFunc("/", hello)
26 |
27 | // Create a link back to node-webkit using the environment variable
28 | // populated by golang-nw's node-webkit code
29 | nodeWebkit, err := nw.New()
30 | if err != nil {
31 | panic(err)
32 | }
33 |
34 | // Pick a random localhost port, start listening for http requests using default handler
35 | // and send a message back to node-webkit to redirect
36 | if err := nodeWebkit.ListenAndServe(nil); err != nil {
37 | panic(err)
38 | }
39 | }
40 |
41 | func hello(w http.ResponseWriter, r *http.Request) {
42 | fmt.Fprintf(w, "Hello from golang.")
43 | }
44 |
45 |
46 | Build your app:
47 |
48 | go install .\src\github.com\lonnc\golang-nw\cmd\example
49 |
50 |
51 | Wrap it in node-webkit:
52 |
53 | .\bin\golang-nw-pkg.exe -app=.\bin\example.exe -name="My Application" -bin="myapp.exe" -toolbar=false
54 |
55 | Building: myapp.exe.nw
56 | Downloading: https://s3.amazonaws.com/node-webkit/v0.8.4/node-webkit-v0.8.4-win-ia32.zip
57 | Packaging: myapp.exe
58 |
59 | You are now good to go:
60 |
61 | .\myapp.exe
62 |
63 | You may want to create your own build script so you can control window dimensions etc.
64 | See http://godoc.org/github.com/lonnc/golang-nw/build and
65 | https://github.com/lonnc/golang-nw/blob/master/cmd/golang-nw-pkg/pkg.go
66 |
67 | Command line options:
68 |
69 | $ ./bin/golang-nw-pkg -h
70 | Usage of ./bin/golang-nw-pkg:
71 | -app="myapp": Web application to be wrapped by node-webkit.
72 | -arch="amd64": Target arch [386|amd64].
73 | -bin="myapp": Destination file for combined application and node-webkit .nw file (will be placed in binDir directory).
74 | -binDir=".": Destination directory for bin and dependencies.
75 | -cacheDir=".": Directory to cache node-webkit download.
76 | -frame=true: Set to false to make window frameless.
77 | -fullscreen=false: Enable fullscreen mode.
78 | -includesDir="": Directory containing additional files to bundle with the .nw file
79 | -name="My Application": Application name.
80 | -os="linux": Target os [linux|windows|darwin].
81 | -toolbar=true: Enable toolbar.
82 | -version="v0.9.2": node-webkit version.
83 |
84 | Known issues:
85 |
86 | 1) libudev.so.0 - On ubuntu >=13.10 (and similar) libudev.so.0 has been removed.
87 | tl;dr:
88 |
89 | $ ./bin/golang-nw-pkg -app=./bin/example -name="My Application" -bin="myapp" -toolbar=false
90 | $ sed -i -e 's/udev\.so\.0/udev.so.1/g' myapp
91 |
92 | Node-webkit has various work arounds described at https://github.com/rogerwang/node-webkit/wiki/The-solution-of-lacking-libudev.so.0
93 |
94 | 2) Download of node-webkit appears to stall - It's a ~43MB download and can take longer than expected (it could do with some feedback).
95 |
96 | */
97 | package nw
98 |
--------------------------------------------------------------------------------
/nw.go:
--------------------------------------------------------------------------------
1 | package nw
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "net"
7 | "net/http"
8 | "os"
9 | "strings"
10 | )
11 |
12 | const EnvVar = "GOLANG-NW"
13 |
14 | var ErrMissingEnvVariable = errors.New("missing environment variable '" + EnvVar + "'")
15 |
16 | type NodeWebkit struct {
17 | Url string // URL to issue callback command to
18 | }
19 |
20 | func New() (NodeWebkit, error) {
21 | url := os.Getenv(EnvVar)
22 | if url == "" {
23 | return NodeWebkit{}, ErrMissingEnvVariable
24 | }
25 | if !strings.HasSuffix(url, "/") {
26 | url = url + "/"
27 | }
28 |
29 | return NodeWebkit{url}, nil
30 | }
31 |
32 | // ListenAndServe listens to a random port on localhost
33 | // and the issues a Redirect to node-webkit.
34 | // If handler is nil then the http.DefaultHandler will be used
35 | func (n NodeWebkit) ListenAndServe(handler http.Handler) error {
36 | httpAddr := "127.0.0.1:0"
37 | listener, err := net.Listen("tcp", httpAddr)
38 | // If we were not able to establish a socket send error back to node-webkit
39 | if err != nil {
40 | // Send error message to node-webkit
41 | return n.Error(err.Error())
42 | }
43 | defer listener.Close()
44 |
45 | errs := make(chan error)
46 | defer close(errs)
47 |
48 | // Issue redirect asynchronously so we can get on with the business of serving http requests
49 | go func() {
50 | if err := n.Redirect("http://" + listener.Addr().String() + "/"); err != nil {
51 | errs <- err
52 | }
53 | }()
54 |
55 | // Now start the normal http listener
56 | go func() {
57 | if err := http.Serve(listener, handler); err != nil {
58 | errs <- err
59 | }
60 | }()
61 |
62 | // Wait for an error from either serving or the redirect
63 | if err := <-errs; err != nil {
64 | // and forward it over to node-webkit
65 | return n.Error(err.Error())
66 | }
67 | return nil
68 | }
69 |
70 | // Redirect sends a redirect message to node-webkit
71 | func (n NodeWebkit) Redirect(url string) error {
72 | return n.send("redirect", url)
73 | }
74 |
75 | // Redirect sends an error message to node-webkit
76 | func (n NodeWebkit) Error(msg string) error {
77 | return n.send("error", msg)
78 | }
79 |
80 | func (n NodeWebkit) send(key string, value string) error {
81 | r, err := http.Post(n.Url+key, "text/plain", strings.NewReader(value))
82 | if err != nil {
83 | return err
84 | }
85 | if r.StatusCode < 200 || r.StatusCode > 299 {
86 | return fmt.Errorf("Unexpected status code: %d", r.StatusCode)
87 | }
88 | return nil
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/pkg.go:
--------------------------------------------------------------------------------
1 | package pkg
2 |
3 | import (
4 | "archive/tar"
5 | "archive/zip"
6 | "compress/gzip"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | "os"
12 | "path"
13 | "path/filepath"
14 | "strings"
15 | )
16 |
17 | type Pkg struct {
18 | Url string
19 | Bin string
20 | Dependencies []string
21 | }
22 |
23 | func New(version string, nwOs string, nwArch string) Pkg {
24 | pkgOs, ok := pkgOss[nwOs]
25 | if !ok {
26 | panic(fmt.Errorf("Unsupported os %q", nwOs))
27 | }
28 |
29 | var arch string
30 | switch nwArch {
31 | case "386":
32 | arch = "ia32"
33 | case "amd64":
34 | arch = "x64"
35 | default:
36 | panic(fmt.Errorf("Unsupported arch %q", nwArch))
37 | }
38 |
39 | url := fmt.Sprintf("http://dl.node-webkit.org/%s/node-webkit-%s-%s-%s%s", version, version, pkgOs.os, arch, pkgOs.ext)
40 |
41 | pkg := Pkg{
42 | Url: url,
43 | Bin: pkgOs.bin,
44 | Dependencies: pkgOs.deps,
45 | }
46 |
47 | return pkg
48 | }
49 |
50 | // Download retrieves the Url into the passed directory.
51 | // If destDir=="" then TempDir is used.
52 | // The file path is returned upon completion
53 | func (p Pkg) Download(destDir string) (string, error) {
54 | if destDir == "" {
55 | destDir = os.TempDir()
56 | }
57 |
58 | // Where do we want to download to
59 | out := filepath.Join(destDir, path.Base(p.Url))
60 |
61 | // See if we already have it
62 | if exists, err := isExists(out); err != nil {
63 | return out, err
64 | } else if exists {
65 | return out, nil
66 | }
67 |
68 | // Download into memory then write to disk after
69 | client := http.DefaultClient
70 | r, err := client.Get(p.Url)
71 | if err != nil {
72 | return "", err
73 | }
74 | defer r.Body.Close()
75 |
76 | if r.StatusCode != 200 {
77 | return "", fmt.Errorf("Failed to download %q got: %s", p.Url, r.Status)
78 | }
79 |
80 | if content, err := ioutil.ReadAll(r.Body); err != nil {
81 | return "", err
82 | } else {
83 | if err := ioutil.WriteFile(out, content, 0666); err != nil {
84 | return "", err
85 | }
86 | }
87 |
88 | return out, nil
89 | }
90 |
91 | // Package wraps populates destDir with the node-webkit depedencies and cat nw.exe [nw content] > binName
92 | func (p Pkg) Package(nodeWebkitPath string, nw io.Reader, binName string, destDir string) error {
93 | // Ensure destDir exists
94 | if err := os.MkdirAll(destDir, 0755); err != nil {
95 | return err
96 | }
97 |
98 | // Check we have a zip
99 | if nodeWebkitPathZip, err := ensureZip(nodeWebkitPath); err != nil {
100 | return err
101 | } else {
102 | nodeWebkitPath = nodeWebkitPathZip
103 | }
104 |
105 | // Extract dependencies from zip file
106 | zr, err := zip.OpenReader(nodeWebkitPath)
107 | if err != nil {
108 | return err
109 | }
110 | defer zr.Close()
111 |
112 | // Get list of files in the zip archive, excluding the preceding directory
113 | zipFiles := map[string]*zip.File{}
114 | for _, f := range zr.File {
115 | zipFiles[path.Base(f.Name)] = f
116 | }
117 |
118 | if p.Bin != "" {
119 | if bin, ok := zipFiles[p.Bin]; !ok {
120 | return fmt.Errorf("Failed to find %s in %s", p.Bin, nodeWebkitPath)
121 | } else {
122 | if err := p.copyBin(bin, nw, binName, destDir); err != nil {
123 | return err
124 | }
125 | }
126 | }
127 |
128 | if err := p.copyDependencies(zipFiles, destDir); err != nil {
129 | return err
130 | }
131 |
132 | return nil
133 | }
134 |
135 | func (p Pkg) copyBin(bin *zip.File, nw io.Reader, binName string, destDir string) error {
136 | r, err := bin.Open()
137 | if err != nil {
138 | return err
139 | }
140 | defer r.Close()
141 |
142 | filename := filepath.Join(destDir, binName)
143 | w, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
144 | if err != nil {
145 | return err
146 | }
147 | defer w.Close()
148 |
149 | // Copy nw binary
150 | _, err = io.Copy(w, r)
151 | if err != nil {
152 | return err
153 | }
154 |
155 | // Copy nw
156 | _, err = io.Copy(w, nw)
157 | if err != nil {
158 | return err
159 | }
160 |
161 | return nil
162 | }
163 |
164 | func (p Pkg) copyDependencies(zipFiles map[string]*zip.File, destDir string) error {
165 | // And extract the dependencies
166 | for _, dep := range p.Dependencies {
167 | filename := filepath.Join(destDir, dep)
168 |
169 | // Only copy over if it doesn't already exist
170 | if exists, err := isExists(filename); err != nil {
171 | return err
172 | } else if exists {
173 | continue
174 | }
175 |
176 | // And copy it over
177 | var r io.ReadCloser = nil
178 | if zipFile, ok := zipFiles[dep]; !ok {
179 | return fmt.Errorf("Failed to find %s", dep)
180 | } else {
181 | if f, err := zipFile.Open(); err != nil {
182 | return err
183 | } else {
184 | r = f
185 | }
186 | }
187 | defer r.Close()
188 |
189 | w, err := os.Create(filename)
190 | if err != nil {
191 | return err
192 | }
193 | defer w.Close()
194 |
195 | if _, err = io.Copy(w, r); err != nil {
196 | return err
197 | }
198 |
199 | }
200 |
201 | return nil
202 | }
203 |
204 | type pkgOs struct {
205 | os string
206 | bin string
207 | deps []string
208 | ext string
209 | }
210 |
211 | var windows = pkgOs{
212 | os: "win",
213 | bin: "nw.exe",
214 | deps: []string{"ffmpegsumo.dll", "icudtl.dat", "libEGL.dll", "libGLESv2.dll", "nw.pak"},
215 | ext: ".zip",
216 | }
217 |
218 | var linux = pkgOs{
219 | os: "linux",
220 | bin: "nw",
221 | deps: []string{"libffmpegsumo.so", "nw.pak", "icudtl.dat"},
222 | ext: ".tar.gz",
223 | }
224 |
225 | var darwin = pkgOs{
226 | os: "osx",
227 | bin: "",
228 | deps: []string{},
229 | ext: ".zip",
230 | }
231 |
232 | var pkgOss = map[string]pkgOs{
233 | "windows": windows,
234 | "linux": linux,
235 | "darwin": darwin,
236 | }
237 |
238 | func isExists(path string) (bool, error) {
239 | if _, err := os.Stat(path); err != nil {
240 | if os.IsNotExist(err) {
241 | return false, nil
242 | }
243 | return false, err
244 | }
245 | return true, nil
246 | }
247 |
248 | // Check whether this is a .zip and return if not create a zip file and return that
249 | func ensureZip(filename string) (string, error) {
250 | if strings.HasSuffix(filename, ".zip") {
251 | return filename, nil
252 | }
253 |
254 | if strings.HasSuffix(filename, ".tar.gz") {
255 | filenameZip := filename[:len(filename)-len(".tar.gz")] + ".zip"
256 | if exists, err := isExists(filenameZip); err != nil {
257 | return "", err
258 | } else {
259 | if !exists {
260 | if err := toZip(filename, filenameZip); err != nil {
261 | return "", err
262 | }
263 | }
264 | return filenameZip, err
265 | }
266 | }
267 |
268 | return "", fmt.Errorf("Do not know how to create a zip archive from %s", filename)
269 | }
270 |
271 | // convert a .tar.gz into a .zip
272 | func toZip(filenameTarGz string, filenameZip string) error {
273 | r, err := os.Open(filenameTarGz)
274 | if err != nil {
275 | return err
276 | }
277 | defer r.Close()
278 |
279 | gz, err := gzip.NewReader(r)
280 | if err != nil {
281 | return err
282 | }
283 |
284 | tgz := tar.NewReader(gz)
285 |
286 | filenameZipTmp := filenameZip + ".tmp"
287 | w, err := os.Create(filenameZipTmp)
288 | if err != nil {
289 | return err
290 | }
291 | defer func() {
292 | if w != nil {
293 | w.Close()
294 | }
295 | }()
296 |
297 | z := zip.NewWriter(w)
298 | defer func() {
299 | if z != nil {
300 | z.Close()
301 | }
302 | }()
303 |
304 | for {
305 | hdr, err := tgz.Next()
306 | if err == io.EOF {
307 | break
308 | }
309 | if err != nil {
310 | return err
311 | }
312 | f, err := z.Create(hdr.Name)
313 | if err != nil {
314 | return err
315 | }
316 | if _, err := io.Copy(f, tgz); err != nil {
317 | return err
318 | }
319 | }
320 |
321 | // Ok all done, lets close
322 | if err := z.Close(); err != nil {
323 | return err
324 | }
325 | if err := w.Close(); err != nil {
326 | return err
327 | }
328 | // And rename
329 | if err := os.Rename(filenameZipTmp, filenameZip); err != nil {
330 | return err
331 | }
332 | return nil
333 | }
334 |
--------------------------------------------------------------------------------