github.com/status-im/status-go@v1.1.0/services/browsers/database.go (about) 1 package browsers 2 3 import ( 4 "context" 5 "database/sql" 6 7 "github.com/mat/besticon/besticon" 8 9 "github.com/ethereum/go-ethereum/log" 10 ) 11 12 // Database sql wrapper for operations with browser objects. 13 type Database struct { 14 db *sql.DB 15 } 16 17 // Close closes database. 18 func (db Database) Close() error { 19 return db.db.Close() 20 } 21 22 func NewDB(db *sql.DB) *Database { 23 return &Database{db: db} 24 } 25 26 type BookmarksType string 27 28 type Bookmark struct { 29 URL string `json:"url"` 30 Name string `json:"name"` 31 ImageURL string `json:"imageUrl"` 32 Removed bool `json:"removed"` 33 Clock uint64 `json:"-"` //used to sync 34 DeletedAt uint64 `json:"deletedAt,omitempty"` 35 } 36 type Browser struct { 37 ID string `json:"browser-id"` 38 Name string `json:"name"` 39 Timestamp uint64 `json:"timestamp"` 40 Dapp bool `json:"dapp?"` 41 HistoryIndex int `json:"history-index"` 42 History []string `json:"history,omitempty"` 43 } 44 45 func (db *Database) GetBookmarks() ([]*Bookmark, error) { 46 rows, err := db.db.Query(`SELECT url, name, image_url, removed, deleted_at FROM bookmarks`) 47 if err != nil { 48 return nil, err 49 } 50 defer rows.Close() 51 52 var rst []*Bookmark 53 for rows.Next() { 54 bookmark := &Bookmark{} 55 err := rows.Scan(&bookmark.URL, &bookmark.Name, &bookmark.ImageURL, &bookmark.Removed, &bookmark.DeletedAt) 56 if err != nil { 57 return nil, err 58 } 59 60 rst = append(rst, bookmark) 61 } 62 63 return rst, nil 64 } 65 66 func (db *Database) StoreBookmark(bookmark Bookmark) (Bookmark, error) { 67 insert, err := db.db.Prepare("INSERT OR REPLACE INTO bookmarks (url, name, image_url, removed, clock, deleted_at) VALUES (?, ?, ?, ?, ?, ?)") 68 69 if err != nil { 70 return bookmark, err 71 } 72 73 // Get the right icon 74 finder := besticon.IconFinder{} 75 icons, iconError := finder.FetchIcons(bookmark.URL) 76 77 if iconError == nil && len(icons) > 0 { 78 icon := finder.IconInSizeRange(besticon.SizeRange{Min: 48, Perfect: 48, Max: 100}) 79 if icon != nil { 80 bookmark.ImageURL = icon.URL 81 } else { 82 bookmark.ImageURL = icons[0].URL 83 } 84 } else { 85 log.Error("error getting the bookmark icon", "iconError", iconError) 86 } 87 88 _, err = insert.Exec(bookmark.URL, bookmark.Name, bookmark.ImageURL, bookmark.Removed, bookmark.Clock, bookmark.DeletedAt) 89 return bookmark, err 90 } 91 92 func (db *Database) StoreBookmarkWithoutFetchIcon(bookmark *Bookmark, tx *sql.Tx) (err error) { 93 if tx == nil { 94 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 95 if err != nil { 96 return err 97 } 98 defer func() { 99 if err == nil { 100 err = tx.Commit() 101 return 102 } 103 // don't shadow original error 104 _ = tx.Rollback() 105 }() 106 } 107 108 insert, err := tx.Prepare("INSERT OR REPLACE INTO bookmarks (url, name, image_url, removed, clock, deleted_at) VALUES (?, ?, ?, ?, ?, ?)") 109 110 if err != nil { 111 return err 112 } 113 114 defer insert.Close() 115 116 _, err = insert.Exec(bookmark.URL, bookmark.Name, bookmark.ImageURL, bookmark.Removed, bookmark.Clock, bookmark.DeletedAt) 117 return err 118 } 119 120 func (db *Database) StoreSyncBookmarks(bookmarks []*Bookmark) ([]*Bookmark, error) { 121 tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{}) 122 if err != nil { 123 return nil, err 124 } 125 126 defer func() { 127 if err == nil { 128 err = tx.Commit() 129 return 130 } 131 // don't shadow original error 132 _ = tx.Rollback() 133 }() 134 135 var storedBookmarks []*Bookmark 136 for _, bookmark := range bookmarks { 137 shouldSync, err := db.shouldSyncBookmark(bookmark, tx) 138 if err != nil { 139 return storedBookmarks, err 140 } 141 if shouldSync { 142 err := db.StoreBookmarkWithoutFetchIcon(bookmark, tx) 143 if err != nil { 144 return storedBookmarks, err 145 } 146 storedBookmarks = append(storedBookmarks, bookmark) 147 } 148 } 149 return storedBookmarks, nil 150 } 151 152 func (db *Database) shouldSyncBookmark(bookmark *Bookmark, tx *sql.Tx) (shouldSync bool, err error) { 153 if tx == nil { 154 tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{}) 155 if err != nil { 156 return false, err 157 } 158 defer func() { 159 if err == nil { 160 err = tx.Commit() 161 return 162 } 163 // don't shadow original error 164 _ = tx.Rollback() 165 }() 166 } 167 qr := tx.QueryRow(`SELECT 1 FROM bookmarks WHERE url = ? AND clock > ?`, bookmark.URL, bookmark.Clock) 168 var result int 169 err = qr.Scan(&result) 170 switch err { 171 case sql.ErrNoRows: 172 // Query does not match, therefore synced_at value is not older than the new clock value or id was not found 173 return true, nil 174 case nil: 175 // Error is nil, therefore query matched and synced_at is older than the new clock 176 return false, nil 177 default: 178 // Error is not nil and is not sql.ErrNoRows, therefore pass out the error 179 return false, err 180 } 181 } 182 183 func (db *Database) UpdateBookmark(originalURL string, bookmark Bookmark) error { 184 insert, err := db.db.Prepare("UPDATE bookmarks SET url = ?, name = ?, image_url = ?, removed = ?, clock = ?, deleted_at = ? WHERE url = ?") 185 if err != nil { 186 return err 187 } 188 _, err = insert.Exec(bookmark.URL, bookmark.Name, bookmark.ImageURL, bookmark.Removed, bookmark.Clock, bookmark.DeletedAt, originalURL) 189 return err 190 } 191 192 func (db *Database) DeleteBookmark(url string) error { 193 _, err := db.db.Exec(`DELETE FROM bookmarks WHERE url = ?`, url) 194 return err 195 } 196 197 func (db *Database) RemoveBookmark(url string) error { 198 _, err := db.db.Exec(`UPDATE bookmarks SET removed = 1 WHERE url = ?`, url) 199 return err 200 }