1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package pouchdb
16
17 import (
18 "context"
19 "encoding/json"
20 "errors"
21 "fmt"
22 "net/http"
23 "net/url"
24 "strings"
25 "sync"
26
27 kivik "github.com/go-kivik/kivik/v4"
28 "github.com/go-kivik/kivik/v4/driver"
29 internal "github.com/go-kivik/kivik/v4/int/errors"
30 "github.com/go-kivik/kivik/v4/pouchdb/bindings"
31 )
32
33 type pouchDriver struct{}
34
35 var _ driver.Driver = &pouchDriver{}
36
37 func init() {
38 kivik.Register("pouch", &pouchDriver{})
39 }
40
41
42
43 func (d *pouchDriver) NewClient(dsn string, options driver.Options) (driver.Client, error) {
44 var u *url.URL
45 var user *url.Userinfo
46 if dsn != "" {
47 var err error
48 u, err = url.Parse(dsn)
49 if err != nil {
50 return nil, fmt.Errorf("Invalid DSN URL '%s' provided: %s", dsn, err)
51 }
52 user = u.User
53 u.User = nil
54 }
55 pouch := bindings.GlobalPouchDB()
56 client := &client{
57 dsn: u,
58 pouch: pouch,
59 opts: make(map[string]Options),
60 }
61 if user != nil {
62 pass, _ := user.Password()
63 client.setAuth(user.Username(), pass)
64 }
65 options.Apply(client)
66 return client, nil
67 }
68
69 func (c *client) setAuth(username, password string) {
70 c.opts["authenticator"] = Options{
71 "auth": map[string]interface{}{
72 "username": username,
73 "password": password,
74 },
75 }
76 }
77
78 type client struct {
79 dsn *url.URL
80 opts map[string]Options
81 pouch *bindings.PouchDB
82
83
84 replications []*replication
85 replicationsMU sync.RWMutex
86 }
87
88 var _ driver.Client = &client{}
89
90
91
92 func (c *client) AllDBs(ctx context.Context, _ driver.Options) ([]string, error) {
93 if c.dsn == nil {
94 return c.pouch.AllDBs(ctx)
95 }
96 return nil, errors.New("AllDBs() not implemented for remote PouchDB databases")
97 }
98
99 func (c *client) Version(context.Context) (*driver.Version, error) {
100 ver := c.pouch.Version()
101 return &driver.Version{
102 Version: ver,
103 Vendor: "PouchDB",
104 RawResponse: json.RawMessage(fmt.Sprintf(`{"version":"%s","vendor":{"name":"PouchDB"}}`, ver)),
105 }, nil
106 }
107
108 func (c *client) dbURL(db string) string {
109 if c.dsn == nil {
110
111 return db
112 }
113 myURL := *c.dsn
114 myURL.Path += strings.TrimLeft(db, "/")
115 return myURL.String()
116 }
117
118
119 type Options map[string]interface{}
120
121 func (c *client) options(options ...Options) Options {
122 o := Options{}
123 for _, defOpts := range c.opts {
124 for k, v := range defOpts {
125 o[k] = v
126 }
127 }
128 for _, opts := range options {
129 for k, v := range opts {
130 o[k] = v
131 }
132 }
133 return o
134 }
135
136 func (c *client) isRemote() bool {
137 return c.dsn != nil
138 }
139
140
141
142
143 func (c *client) DBExists(ctx context.Context, dbName string, options driver.Options) (bool, error) {
144 pouchOpts := map[string]interface{}{"skip_setup": true}
145 options.Apply(pouchOpts)
146 _, err := c.pouch.New(c.dbURL(dbName), pouchOpts).Info(ctx)
147 if err == nil {
148 return true, nil
149 }
150 if kivik.HTTPStatus(err) == http.StatusNotFound {
151 return false, nil
152 }
153 return false, err
154 }
155
156 func (c *client) CreateDB(ctx context.Context, dbName string, options driver.Options) error {
157 if c.isRemote() {
158 if exists, _ := c.DBExists(ctx, dbName, options); exists {
159 return &internal.Error{Status: http.StatusPreconditionFailed, Message: "database exists"}
160 }
161 }
162 pouchOpts := map[string]interface{}{}
163 options.Apply(pouchOpts)
164 _, err := c.pouch.New(c.dbURL(dbName), pouchOpts).Info(ctx)
165 return err
166 }
167
168 func (c *client) DestroyDB(ctx context.Context, dbName string, options driver.Options) error {
169 exists, err := c.DBExists(ctx, dbName, options)
170 if err != nil {
171 return err
172 }
173 if !exists {
174
175 return &internal.Error{Status: http.StatusNotFound, Message: "database does not exist"}
176 }
177 pouchOpts := map[string]interface{}{}
178 options.Apply(pouchOpts)
179 return c.pouch.New(c.dbURL(dbName), pouchOpts).Destroy(ctx, nil)
180 }
181
182 func (c *client) DB(dbName string, options driver.Options) (driver.DB, error) {
183 pouchOpts := map[string]interface{}{}
184 options.Apply(pouchOpts)
185 return &db{
186
187
188 db: c.pouch.New(c.dbURL(dbName), pouchOpts),
189 client: c,
190 }, nil
191 }
192
View as plain text