mirror of
https://github.com/go-shiori/shiori.git
synced 2025-12-11 06:26:03 +08:00
Merge branch 'master' into wip/tests
This commit is contained in:
commit
a747852728
15 changed files with 142 additions and 97 deletions
39
.travis.yml
Normal file
39
.travis.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.x
|
||||||
|
env: LATEST=true
|
||||||
|
- go: 1.9
|
||||||
|
- go: 1.8
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mitchellh/gox # go tool for cross compiling
|
||||||
|
- go get github.com/inconshreveable/mousetrap # needed for windows builds
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get github.com/RadhiFadlillah/shiori
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d .)
|
||||||
|
- go vet $(go list ./... | grep -v /vendor/)
|
||||||
|
- go test -v -race ./...
|
||||||
|
# only build binaries from the latest Go release.
|
||||||
|
- if [ "${LATEST}" = "true" ]; then gox -os="linux darwin windows" -arch="amd64" -output "shiori_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...; fi
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
provider: releases
|
||||||
|
skip_cleanup: true
|
||||||
|
api_key:
|
||||||
|
secure: $TOKEN
|
||||||
|
file:
|
||||||
|
- shiori_windows_amd64.exe
|
||||||
|
- shiori_darwin_amd64
|
||||||
|
- shiori_linux_amd64
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
condition: $LATEST = true
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
# Shiori
|
# Shiori
|
||||||
|
|
||||||
|
[](https://travis-ci.org/RadhiFadlillah/shiori)
|
||||||
|
[](https://goreportcard.com/report/github.com/radhifadlillah/shiori)
|
||||||
|
|
||||||
Shiori is a simple bookmarks manager written in Go language. Intended as a simple clone of [Pocket](https://getpocket.com//). You can use it as command line application or as web application. This application is distributed as a single binary, which means it can be installed and used easily.
|
Shiori is a simple bookmarks manager written in Go language. Intended as a simple clone of [Pocket](https://getpocket.com//). You can use it as command line application or as web application. This application is distributed as a single binary, which means it can be installed and used easily.
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -9,6 +12,7 @@ Shiori is a simple bookmarks manager written in Go language. Intended as a simpl
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
|
- [Advanced](#advanced)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
|
@ -57,6 +61,10 @@ Flags:
|
||||||
Use "shiori [command] --help" for more information about a command.
|
Use "shiori [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Advanced
|
||||||
|
|
||||||
|
By default, `shiori` will create database in the location where you run it. For example, if you run `shiori`. To set the database to a specific location, you can set the environment variable `ENV_SHIORI_DB` to your desired path.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
1. Save new bookmark with tags "nature" and "climate-change".
|
1. Save new bookmark with tags "nature" and "climate-change".
|
||||||
|
|
|
||||||
26
cmd/add.go
26
cmd/add.go
|
|
@ -2,6 +2,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
nurl "net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -59,6 +60,12 @@ func addBookmark(base model.Bookmark, offline bool) (book model.Bookmark, err er
|
||||||
// Prepare initial result
|
// Prepare initial result
|
||||||
book = base
|
book = base
|
||||||
|
|
||||||
|
// Clear UTM parameters from URL
|
||||||
|
book.URL, err = clearUTMParams(book.URL)
|
||||||
|
if err != nil {
|
||||||
|
return book, err
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch data from internet
|
// Fetch data from internet
|
||||||
if !offline {
|
if !offline {
|
||||||
article, err := readability.Parse(book.URL, 10*time.Second)
|
article, err := readability.Parse(book.URL, 10*time.Second)
|
||||||
|
|
@ -94,3 +101,22 @@ func addBookmark(base model.Bookmark, offline bool) (book model.Bookmark, err er
|
||||||
func normalizeSpace(str string) string {
|
func normalizeSpace(str string) string {
|
||||||
return strings.Join(strings.Fields(str), " ")
|
return strings.Join(strings.Fields(str), " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearUTMParams(uri string) (string, error) {
|
||||||
|
tempURL, err := nurl.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
newQuery := nurl.Values{}
|
||||||
|
for key, value := range tempURL.Query() {
|
||||||
|
if strings.HasPrefix(key, "utm_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newQuery[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
tempURL.RawQuery = newQuery.Encode()
|
||||||
|
return tempURL.String(), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -32,16 +31,9 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete bookmarks from database
|
// Delete bookmarks from database
|
||||||
oldIndices, newIndices, err := DB.DeleteBookmarks(args...)
|
err := DB.DeleteBookmarks(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Bookmarks has been deleted")
|
|
||||||
for i, oldIndex := range oldIndices {
|
|
||||||
newIndex := newIndices[i]
|
|
||||||
fmt.Printf("Index %d moved to %d\n", oldIndex, newIndex)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,15 +41,6 @@ func init() {
|
||||||
rootCmd.AddCommand(importCmd)
|
rootCmd.AddCommand(importCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTagName(s *goquery.Selection) string {
|
|
||||||
tags := []string{}
|
|
||||||
for _, nd := range s.Nodes {
|
|
||||||
tags = append(tags, nd.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(tags, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func importBookmarks(pth string, generateTag bool) error {
|
func importBookmarks(pth string, generateTag bool) error {
|
||||||
// Open file
|
// Open file
|
||||||
srcFile, err := os.Open(pth)
|
srcFile, err := os.Open(pth)
|
||||||
|
|
@ -74,10 +65,19 @@ func importBookmarks(pth string, generateTag bool) error {
|
||||||
// Get metadata
|
// Get metadata
|
||||||
title := a.Text()
|
title := a.Text()
|
||||||
url, _ := a.Attr("href")
|
url, _ := a.Attr("href")
|
||||||
|
strTags, _ := a.Attr("tags")
|
||||||
strModified, _ := a.Attr("last_modified")
|
strModified, _ := a.Attr("last_modified")
|
||||||
intModified, _ := strconv.ParseInt(strModified, 10, 64)
|
intModified, _ := strconv.ParseInt(strModified, 10, 64)
|
||||||
modified := time.Unix(intModified, 0)
|
modified := time.Unix(intModified, 0)
|
||||||
|
|
||||||
|
// Get bookmark tags
|
||||||
|
tags := []model.Tag{}
|
||||||
|
for _, strTag := range strings.Split(strTags, ",") {
|
||||||
|
if strTag != "" {
|
||||||
|
tags = append(tags, model.Tag{Name: strTag})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get bookmark excerpt
|
// Get bookmark excerpt
|
||||||
excerpt := ""
|
excerpt := ""
|
||||||
if dd := dt.Next(); dd.Is("dd") {
|
if dd := dt.Next(); dd.Is("dd") {
|
||||||
|
|
@ -85,6 +85,7 @@ func importBookmarks(pth string, generateTag bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get category name for this bookmark
|
// Get category name for this bookmark
|
||||||
|
// and add it as tags (if necessary)
|
||||||
category := ""
|
category := ""
|
||||||
if dtCategory := dl.Prev(); dtCategory.Is("h3") {
|
if dtCategory := dl.Prev(); dtCategory.Is("h3") {
|
||||||
category = dtCategory.Text()
|
category = dtCategory.Text()
|
||||||
|
|
@ -93,9 +94,8 @@ func importBookmarks(pth string, generateTag bool) error {
|
||||||
category = strings.Replace(category, " ", "-", -1)
|
category = strings.Replace(category, " ", "-", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := []model.Tag{}
|
|
||||||
if category != "" && generateTag {
|
if category != "" && generateTag {
|
||||||
tags = []model.Tag{{Name: category}}
|
tags = append(tags, model.Tag{Name: category})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add item to list
|
// Add item to list
|
||||||
|
|
|
||||||
20
cmd/open.go
20
cmd/open.go
|
|
@ -3,6 +3,7 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -67,7 +68,7 @@ func openBookmarks(args ...string) {
|
||||||
|
|
||||||
// Open in browser
|
// Open in browser
|
||||||
for _, book := range bookmarks {
|
for _, book := range bookmarks {
|
||||||
exec.Command("xdg-open", book.URL).Run()
|
err = openBrowser(book.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Printf("Failed to open %s: %v\n", book.URL, err)
|
cError.Printf("Failed to open %s: %v\n", book.URL, err)
|
||||||
}
|
}
|
||||||
|
|
@ -110,3 +111,20 @@ func openBookmarksCache(trimSpace bool, args ...string) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// openBrowser tries to open the URL in a browser,
|
||||||
|
// and returns whether it succeed in doing so.
|
||||||
|
func openBrowser(url string) error {
|
||||||
|
var args []string
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
args = []string{"open"}
|
||||||
|
case "windows":
|
||||||
|
args = []string{"cmd", "/c", "start"}
|
||||||
|
default:
|
||||||
|
args = []string{"xdg-open"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(args[0], append(args[1:], url)...)
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/RadhiFadlillah/shiori/model"
|
"github.com/RadhiFadlillah/shiori/model"
|
||||||
|
|
@ -26,7 +25,7 @@ var (
|
||||||
bookmarks, err := DB.GetBookmarks(false, args...)
|
bookmarks, err := DB.GetBookmarks(false, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(bookmarks) == 0 {
|
if len(bookmarks) == 0 {
|
||||||
|
|
@ -36,7 +35,7 @@ var (
|
||||||
cError.Println("No bookmarks saved yet")
|
cError.Println("No bookmarks saved yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print data
|
// Print data
|
||||||
|
|
@ -44,7 +43,7 @@ var (
|
||||||
bt, err := json.MarshalIndent(&bookmarks, "", " ")
|
bt, err := json.MarshalIndent(&bookmarks, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
fmt.Println(string(bt))
|
fmt.Println(string(bt))
|
||||||
} else if indexOnly {
|
} else if indexOnly {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/RadhiFadlillah/shiori/database"
|
"github.com/RadhiFadlillah/shiori/database"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -23,6 +22,5 @@ var (
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -34,12 +33,12 @@ var (
|
||||||
bookmarks, err := DB.SearchBookmarks(false, keyword, tags...)
|
bookmarks, err := DB.SearchBookmarks(false, keyword, tags...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(bookmarks) == 0 {
|
if len(bookmarks) == 0 {
|
||||||
cError.Println("No matching bookmarks found")
|
cError.Println("No matching bookmarks found")
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print data
|
// Print data
|
||||||
|
|
@ -47,7 +46,7 @@ var (
|
||||||
bt, err := json.MarshalIndent(&bookmarks, "", " ")
|
bt, err := json.MarshalIndent(&bookmarks, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
fmt.Println(string(bt))
|
fmt.Println(string(bt))
|
||||||
} else if indexOnly {
|
} else if indexOnly {
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ func apiDeleteBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Pa
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// Delete bookmarks
|
// Delete bookmarks
|
||||||
_, _, err = DB.DeleteBookmarks(request...)
|
err = DB.DeleteBookmarks(request...)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
fmt.Fprint(w, request)
|
fmt.Fprint(w, request)
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,13 @@ func init() {
|
||||||
|
|
||||||
func updateBookmarks(indices []string, url, title, excerpt string, tags []string, offline bool) ([]model.Bookmark, error) {
|
func updateBookmarks(indices []string, url, title, excerpt string, tags []string, offline bool) ([]model.Bookmark, error) {
|
||||||
mutex := sync.Mutex{}
|
mutex := sync.Mutex{}
|
||||||
|
|
||||||
|
// Clear UTM parameters from URL
|
||||||
|
url, err := clearUTMParams(url)
|
||||||
|
if err != nil {
|
||||||
|
return []model.Bookmark{}, err
|
||||||
|
}
|
||||||
|
|
||||||
// Read bookmarks from database
|
// Read bookmarks from database
|
||||||
bookmarks, err := DB.GetBookmarks(true, indices...)
|
bookmarks, err := DB.GetBookmarks(true, indices...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/RadhiFadlillah/shiori/model"
|
"github.com/RadhiFadlillah/shiori/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ type Database interface {
|
||||||
GetBookmarks(withContent bool, indices ...string) ([]model.Bookmark, error)
|
GetBookmarks(withContent bool, indices ...string) ([]model.Bookmark, error)
|
||||||
|
|
||||||
// DeleteBookmarks removes all record with matching indices from database.
|
// DeleteBookmarks removes all record with matching indices from database.
|
||||||
DeleteBookmarks(indices ...string) ([]int, []int, error)
|
DeleteBookmarks(indices ...string) error
|
||||||
|
|
||||||
// SearchBookmarks search bookmarks by the keyword or tags.
|
// SearchBookmarks search bookmarks by the keyword or tags.
|
||||||
SearchBookmarks(orderLatest bool, keyword string, tags ...string) ([]model.Bookmark, error)
|
SearchBookmarks(orderLatest bool, keyword string, tags ...string) ([]model.Bookmark, error)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ package database
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/RadhiFadlillah/shiori/model"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/RadhiFadlillah/shiori/model"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SQLiteDatabase is implementation of Database interface for connecting to SQLite3 database.
|
// SQLiteDatabase is implementation of Database interface for connecting to SQLite3 database.
|
||||||
|
|
@ -18,10 +18,10 @@ type SQLiteDatabase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenSQLiteDatabase creates and open connection to new SQLite3 database.
|
// OpenSQLiteDatabase creates and open connection to new SQLite3 database.
|
||||||
func OpenSQLiteDatabase(dbFile string) (*SQLiteDatabase, error) {
|
func OpenSQLiteDatabase(databasePath string) (*SQLiteDatabase, error) {
|
||||||
// Open database and start transaction
|
// Open database and start transaction
|
||||||
var err error
|
var err error
|
||||||
db := sqlx.MustConnect("sqlite3", dbFile)
|
db := sqlx.MustConnect("sqlite3", databasePath)
|
||||||
tx := db.MustBegin()
|
tx := db.MustBegin()
|
||||||
|
|
||||||
// Make sure to rollback if panic ever happened
|
// Make sure to rollback if panic ever happened
|
||||||
|
|
@ -262,7 +262,7 @@ func (db *SQLiteDatabase) GetBookmarks(withContent bool, indices ...string) ([]m
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBookmarks removes all record with matching indices from database.
|
// DeleteBookmarks removes all record with matching indices from database.
|
||||||
func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newIndices []int, err error) {
|
func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (err error) {
|
||||||
// Convert list of index to int
|
// Convert list of index to int
|
||||||
listIndex := []int{}
|
listIndex := []int{}
|
||||||
errInvalidIndex := fmt.Errorf("Index is not valid")
|
errInvalidIndex := fmt.Errorf("Index is not valid")
|
||||||
|
|
@ -271,13 +271,13 @@ func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newInd
|
||||||
if strings.Contains(strIndex, "-") {
|
if strings.Contains(strIndex, "-") {
|
||||||
parts := strings.Split(strIndex, "-")
|
parts := strings.Split(strIndex, "-")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return nil, nil, errInvalidIndex
|
return errInvalidIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
minIndex, errMin := strconv.Atoi(parts[0])
|
minIndex, errMin := strconv.Atoi(parts[0])
|
||||||
maxIndex, errMax := strconv.Atoi(parts[1])
|
maxIndex, errMax := strconv.Atoi(parts[1])
|
||||||
if errMin != nil || errMax != nil || minIndex < 1 || minIndex > maxIndex {
|
if errMin != nil || errMax != nil || minIndex < 1 || minIndex > maxIndex {
|
||||||
return nil, nil, errInvalidIndex
|
return errInvalidIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := minIndex; i <= maxIndex; i++ {
|
for i := minIndex; i <= maxIndex; i++ {
|
||||||
|
|
@ -286,16 +286,13 @@ func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newInd
|
||||||
} else {
|
} else {
|
||||||
index, err := strconv.Atoi(strIndex)
|
index, err := strconv.Atoi(strIndex)
|
||||||
if err != nil || index < 1 {
|
if err != nil || index < 1 {
|
||||||
return nil, nil, errInvalidIndex
|
return errInvalidIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
listIndex = append(listIndex, index)
|
listIndex = append(listIndex, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the index
|
|
||||||
sort.Ints(listIndex)
|
|
||||||
|
|
||||||
// Create args and where clause
|
// Create args and where clause
|
||||||
args := []interface{}{}
|
args := []interface{}{}
|
||||||
whereClause := " WHERE 1"
|
whereClause := " WHERE 1"
|
||||||
|
|
@ -314,7 +311,7 @@ func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newInd
|
||||||
// Begin transaction
|
// Begin transaction
|
||||||
tx, err := db.Beginx()
|
tx, err := db.Beginx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errInvalidIndex
|
return errInvalidIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure to rollback if panic ever happened
|
// Make sure to rollback if panic ever happened
|
||||||
|
|
@ -323,8 +320,6 @@ func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newInd
|
||||||
panicErr, _ := r.(error)
|
panicErr, _ := r.(error)
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
|
||||||
oldIndices = nil
|
|
||||||
newIndices = nil
|
|
||||||
err = panicErr
|
err = panicErr
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -337,56 +332,11 @@ func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (oldIndices, newInd
|
||||||
tx.MustExec("DELETE FROM bookmark_tag "+whereTagClause, args...)
|
tx.MustExec("DELETE FROM bookmark_tag "+whereTagClause, args...)
|
||||||
tx.MustExec("DELETE FROM bookmark_content "+whereContentClause, args...)
|
tx.MustExec("DELETE FROM bookmark_content "+whereContentClause, args...)
|
||||||
|
|
||||||
// Prepare statement for updating index
|
|
||||||
stmtGetMaxID, err := tx.Preparex(`SELECT IFNULL(MAX(id), 0) FROM bookmark`)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
stmtUpdateBookmark, err := tx.Preparex(`UPDATE bookmark SET id = ? WHERE id = ?`)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
stmtUpdateBookmarkTag, err := tx.Preparex(`UPDATE bookmark_tag SET bookmark_id = ? WHERE bookmark_id = ?`)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
stmtUpdateBookmarkContent, err := tx.Preparex(`UPDATE bookmark_content SET docid = ? WHERE docid = ?`)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
// Get list of removed indices
|
|
||||||
maxIndex := 0
|
|
||||||
err = stmtGetMaxID.Get(&maxIndex)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
removedIndices := []int{}
|
|
||||||
err = tx.Select(&removedIndices,
|
|
||||||
`WITH cnt(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cnt LIMIT ?)
|
|
||||||
SELECT x FROM cnt WHERE x NOT IN (SELECT id FROM bookmark)`,
|
|
||||||
maxIndex)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
// Fill removed indices
|
|
||||||
newIndices = []int{}
|
|
||||||
oldIndices = []int{}
|
|
||||||
for _, removedIndex := range removedIndices {
|
|
||||||
oldIndex := 0
|
|
||||||
err = stmtGetMaxID.Get(&oldIndex)
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
if oldIndex <= removedIndex {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
stmtUpdateBookmark.MustExec(removedIndex, oldIndex)
|
|
||||||
stmtUpdateBookmarkTag.MustExec(removedIndex, oldIndex)
|
|
||||||
stmtUpdateBookmarkContent.MustExec(removedIndex, oldIndex)
|
|
||||||
|
|
||||||
newIndices = append(newIndices, removedIndex)
|
|
||||||
oldIndices = append(oldIndices, oldIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit transaction
|
// Commit transaction
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
return oldIndices, newIndices, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchBookmarks search bookmarks by the keyword or tags.
|
// SearchBookmarks search bookmarks by the keyword or tags.
|
||||||
|
|
|
||||||
9
main.go
9
main.go
|
|
@ -2,13 +2,20 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/RadhiFadlillah/shiori/cmd"
|
"github.com/RadhiFadlillah/shiori/cmd"
|
||||||
db "github.com/RadhiFadlillah/shiori/database"
|
db "github.com/RadhiFadlillah/shiori/database"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
sqliteDB, err := db.OpenSQLiteDatabase("shiori.db")
|
databasePath := "shiori.db"
|
||||||
|
if value, found := os.LookupEnv("ENV_SHIORI_DB"); found {
|
||||||
|
databasePath = value
|
||||||
|
}
|
||||||
|
|
||||||
|
sqliteDB, err := db.OpenSQLiteDatabase(databasePath)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
cmd.DB = sqliteDB
|
cmd.DB = sqliteDB
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue