Skip to content

Commit fb476f8

Browse files
committed
Add Upload() function, update vendored dependencies
1 parent 4ff8afa commit fb476f8

File tree

10 files changed

+633
-19
lines changed

10 files changed

+633
-19
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,19 @@ A Go library for accessing and using SQLite databases stored remotely on DBHub.i
88
### What works now
99

1010
* Run read-only queries (eg SELECT statements) on databases, returning the results as JSON
11+
* Upload and download your databases
1112
* List the databases in your account
1213
* List the tables, views, and indexes present in a database
1314
* List the columns in a table, view or index, along with their details
1415
* List the branches, releases, tags, and commits for a database
1516
* Generate diffs between two databases, or database revisions
16-
* Download a complete database
1717
* Download the database metadata (size, branches, commit list, etc.)
18-
* Retrieve the web page URL of the database
18+
* Retrieve the web page URL of a database
1919

2020
### Still to do
2121

22-
* Tests for each function
2322
* Have the backend server correctly use the incoming branch, release, and tag information
24-
* Upload a complete database
23+
* Tests for each function
2524
* Investigate what would be needed for this to work through the Go SQL API
2625
* Anything else people suggest and seems like a good idea :smile:
2726

dbhub.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ package dbhub
44

55
import (
66
"encoding/base64"
7+
"encoding/json"
78
"fmt"
89
"io"
910
"net/url"
11+
"time"
1012

1113
com "github.com/sqlitebrowser/dbhub.io/common"
1214
)
@@ -142,6 +144,9 @@ func (c Connection) Download(dbOwner, dbName string, ident Identifier) (db io.Re
142144
// Fetch the database file
143145
queryUrl := c.Server + "/v1/download"
144146
db, err = sendRequest(queryUrl, data)
147+
if db != nil {
148+
defer db.Close()
149+
}
145150
if err != nil {
146151
return
147152
}
@@ -287,6 +292,72 @@ func (c Connection) Views(dbOwner, dbName string, ident Identifier) (views []str
287292
return
288293
}
289294

295+
// Upload uploads a new database, or a new revision of a database
296+
func (c Connection) Upload(dbName string, info UploadInformation, dbBytes *[]byte) (err error) {
297+
// Prepare the API parameters
298+
data := c.PrepareVals("", dbName, info.Ident)
299+
data.Del("dbowner") // The upload function always stores the database in the account of the API key user
300+
if info.CommitMsg != "" {
301+
data.Set("commitmsg", info.CommitMsg)
302+
}
303+
if info.SourceURL != "" {
304+
data.Set("sourceurl", info.SourceURL)
305+
}
306+
if !info.LastModified.IsZero() {
307+
data.Set("lastmodified", info.LastModified.Format(time.RFC3339))
308+
}
309+
if info.Licence != "" {
310+
data.Set("licence", info.Licence)
311+
}
312+
if info.Public != "" {
313+
data.Set("public", info.Public)
314+
}
315+
if info.Force {
316+
data.Set("force", "true")
317+
}
318+
if !info.CommitTimestamp.IsZero() {
319+
data.Set("committimestamp", info.CommitTimestamp.Format(time.RFC3339))
320+
}
321+
if info.AuthorName != "" {
322+
data.Set("authorname", info.AuthorName)
323+
}
324+
if info.AuthorEmail != "" {
325+
data.Set("authoremail", info.AuthorEmail)
326+
}
327+
if info.CommitterName != "" {
328+
data.Set("committername", info.CommitterName)
329+
}
330+
if info.CommitterEmail != "" {
331+
data.Set("committeremail", info.CommitterEmail)
332+
}
333+
if info.OtherParents != "" {
334+
data.Set("otherparents", info.OtherParents)
335+
}
336+
if info.ShaSum != "" {
337+
data.Set("dbshasum", info.ShaSum)
338+
}
339+
340+
// Upload the database
341+
var body io.ReadCloser
342+
queryUrl := c.Server + "/v1/upload"
343+
body, err = sendUpload(queryUrl, &data, dbBytes)
344+
if body != nil {
345+
defer body.Close()
346+
}
347+
if err != nil {
348+
if body != nil {
349+
// If there's useful error info in the returned JSON, return that as the error message
350+
var z JSONError
351+
err = json.NewDecoder(body).Decode(&z)
352+
if err != nil {
353+
return
354+
}
355+
err = fmt.Errorf("%s", z.Msg)
356+
}
357+
}
358+
return
359+
}
360+
290361
// Webpage returns the URL of the database file in the webUI. eg. for web browsers
291362
func (c Connection) Webpage(dbOwner, dbName string) (webPage com.WebpageResponseContainer, err error) {
292363
// Prepare the API parameters
@@ -296,4 +367,4 @@ func (c Connection) Webpage(dbOwner, dbName string) (webPage com.WebpageResponse
296367
queryUrl := c.Server + "/v1/webpage"
297368
err = sendRequestJSON(queryUrl, data, &webPage)
298369
return
299-
}
370+
}

examples/upload/example.db

12 KB
Binary file not shown.

examples/upload/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"log"
7+
8+
"github.com/sqlitebrowser/go-dbhub"
9+
)
10+
11+
func main() {
12+
// Create a new DBHub.io API object
13+
db, err := dbhub.New("YOUR_API_KEY_HERE")
14+
if err != nil {
15+
log.Fatal(err)
16+
}
17+
18+
// Read the database file into memory
19+
var myDB []byte
20+
myDB, err = ioutil.ReadFile("example.db")
21+
if err != nil {
22+
log.Fatal(err)
23+
}
24+
25+
// Prepare any information you want to include with the upload (eg a commit message, etc)
26+
info := dbhub.UploadInformation{
27+
CommitMsg: "An example upload",
28+
}
29+
30+
// Upload the database
31+
err = db.Upload("somedb.sqlite", info, &myDB)
32+
if err != nil {
33+
log.Fatal(err)
34+
}
35+
36+
// Display a success message
37+
fmt.Println("Database uploaded")
38+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ replace (
1010
github.com/Sirupsen/logrus v1.6.0 => github.com/sirupsen/logrus v1.6.0
1111
)
1212

13-
require github.com/sqlitebrowser/dbhub.io v0.0.12
13+
require github.com/sqlitebrowser/dbhub.io v0.0.13

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG
123123
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
124124
github.com/sqlitebrowser/blackfriday v9.0.0+incompatible h1:ddH/UyzasooYgGIblVU4R8DdmBuJ7QXLvSqX/0chZv4=
125125
github.com/sqlitebrowser/blackfriday v9.0.0+incompatible/go.mod h1:/zga9sqpWzcewuI83AO5JZwe9+6F9GgPDdqqdNNEL/0=
126-
github.com/sqlitebrowser/dbhub.io v0.0.12 h1:G5qd4qQGCVB8/Seh0e+cBvaYkpnoGnWQuxPEr4B09ZE=
127-
github.com/sqlitebrowser/dbhub.io v0.0.12/go.mod h1:GgZi8wkjdRGkkUVgYNuZp5i+Yps3jGt/jjRuLKRi6Po=
126+
github.com/sqlitebrowser/dbhub.io v0.0.13 h1:R29VyPD6LZmtsDicbK4ICnW4RnhiqN3c8+vvKS8RzHM=
127+
github.com/sqlitebrowser/dbhub.io v0.0.13/go.mod h1:GgZi8wkjdRGkkUVgYNuZp5i+Yps3jGt/jjRuLKRi6Po=
128128
github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47 h1:s0+Ea95n1LrsKh6rtclU/9Qb2/5ofvnfnR7gDDiFTw8=
129129
github.com/sqlitebrowser/github_flavored_markdown v0.0.0-20190120045821-b8cf8f054e47/go.mod h1:8vPIKi5FslxCXEgfQxrFtWfdclGy6VWAc9NA1ZTYCJg=
130130
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

http.go

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
package dbhub
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"io"
8+
"mime/multipart"
79
"net/http"
810
"net/url"
911
"strings"
1012
)
1113

1214
// sendRequestJSON sends a request to DBHub.io, formatting the returned result as JSON
1315
func sendRequestJSON(queryUrl string, data url.Values, returnStructure interface{}) (err error) {
14-
type JSONError struct {
15-
Msg string `json:"error"`
16-
}
17-
1816
// Send the request
1917
var body io.ReadCloser
2018
body, err = sendRequest(queryUrl, data)
19+
if body != nil {
20+
defer body.Close()
21+
}
2122
if err != nil {
2223
if body != nil {
23-
defer body.Close()
24-
2524
// If there's useful error info in the returned JSON, return that as the error message
2625
var z JSONError
2726
err = json.NewDecoder(body).Decode(&z)
@@ -32,9 +31,6 @@ func sendRequestJSON(queryUrl string, data url.Values, returnStructure interface
3231
}
3332
return
3433
}
35-
if body != nil {
36-
defer body.Close()
37-
}
3834

3935
// Unmarshall the JSON response into the structure provided by the caller
4036
err = json.NewDecoder(body).Decode(returnStructure)
@@ -61,7 +57,7 @@ func sendRequest(queryUrl string, data url.Values) (body io.ReadCloser, err erro
6157
return
6258
}
6359

64-
// Return the response body, even if an error occured. This lets us return useful error information provided as
60+
// Return the response body, even if an error occurred. This lets us return useful error information provided as
6561
// JSON in the body of the message
6662
body = resp.Body
6763

@@ -73,3 +69,69 @@ func sendRequest(queryUrl string, data url.Values) (body io.ReadCloser, err erro
7369
}
7470
return
7571
}
72+
73+
// sendUpload uploads a database to DBHub.io. It exists because the DBHub.io upload end point requires multi-part data
74+
func sendUpload(queryUrl string, data *url.Values, dbBytes *[]byte) (body io.ReadCloser, err error) {
75+
// Prepare the database file byte stream
76+
var buf bytes.Buffer
77+
w := multipart.NewWriter(&buf)
78+
dbName := data.Get("dbname")
79+
var wri io.Writer
80+
if dbName != "" {
81+
wri, err = w.CreateFormFile("file", dbName)
82+
} else {
83+
wri, err = w.CreateFormFile("file", "database.db")
84+
}
85+
if err != nil {
86+
return
87+
}
88+
_, err = wri.Write(*dbBytes)
89+
if err != nil {
90+
return
91+
}
92+
93+
// Add the headers
94+
for i, j := range *data {
95+
wri, err = w.CreateFormField(i)
96+
if err != nil {
97+
return
98+
}
99+
_, err = wri.Write([]byte(j[0]))
100+
if err != nil {
101+
return
102+
}
103+
}
104+
err = w.Close()
105+
if err != nil {
106+
return
107+
}
108+
109+
// Prepare the request
110+
var req *http.Request
111+
var resp *http.Response
112+
var client http.Client
113+
req, err = http.NewRequest(http.MethodPost, queryUrl, &buf)
114+
if err != nil {
115+
return
116+
}
117+
req.Header.Set("User-Agent", fmt.Sprintf("go-dbhub v%s", version))
118+
req.Header.Set("Content-Type", w.FormDataContentType())
119+
120+
// Upload the database
121+
resp, err = client.Do(req)
122+
if err != nil {
123+
return
124+
}
125+
126+
// Return the response body, even if an error occurred. This lets us return useful error information provided as
127+
// JSON in the body of the message
128+
body = resp.Body
129+
130+
// Basic error handling, based on the status code received from the server
131+
if resp.StatusCode != 201 {
132+
// The returned status code indicates something went wrong
133+
err = fmt.Errorf(resp.Status)
134+
return
135+
}
136+
return
137+
}

types.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package dbhub
22

3+
import "time"
4+
35
// Connection is a simple container holding the API key and address of the DBHub.io server
46
type Connection struct {
57
APIKey string `json:"api_key"`
@@ -14,6 +16,11 @@ type Identifier struct {
1416
Tag string `json:"tag"`
1517
}
1618

19+
// JSONError holds information about an error condition, in a useful JSON format
20+
type JSONError struct {
21+
Msg string `json:"error"`
22+
}
23+
1724
// MergeStrategy specifies the type of SQL statements included in the diff results.
1825
// The SQL statements can be used for merging databases and depending on whether and
1926
// how you want to merge you should choose your merge strategy.
@@ -41,3 +48,21 @@ type ResultRow struct {
4148
type Results struct {
4249
Rows []ResultRow
4350
}
51+
52+
// UploadInformation holds information used when uploading
53+
type UploadInformation struct {
54+
Ident Identifier `json:"identifier"`
55+
CommitMsg string `json:"commitmsg"`
56+
SourceURL string `json:"sourceurl"`
57+
LastModified time.Time `json:"lastmodified"`
58+
Licence string `json:"licence"`
59+
Public string `json:"public"`
60+
Force bool `json:"force"`
61+
CommitTimestamp time.Time `json:"committimestamp"`
62+
AuthorName string `json:"authorname"`
63+
AuthorEmail string `json:"authoremail"`
64+
CommitterName string `json:"committername"`
65+
CommitterEmail string `json:"committeremail"`
66+
OtherParents string `json:"otherparents"`
67+
ShaSum string `json:"dbshasum"`
68+
}

0 commit comments

Comments
 (0)