Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor changes #5

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 4 additions & 80 deletions anki.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"

"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -147,36 +146,7 @@ func (a *Apkg) Close() (e error) {
}

func (a *Apkg) Collection() (*Collection, error) {
var deletedDecks []ID
if rows, err := a.db.Query("SELECT oid FROM graves WHERE type=2"); err != nil {
return nil, err
} else {
for rows.Next() {
id := new(ID)
if err := rows.Scan(id); err != nil {
return nil, err
}
deletedDecks = append(deletedDecks, *id)
}
}
collection := &Collection{}
if err := a.db.Get(collection, "SELECT * FROM col"); err != nil {
return nil, err
}
for _, deck := range collection.Decks {
for _, deleted := range deletedDecks {
if deck.ID == deleted {
delete(collection.Decks, deck.ID)
continue
}
}
conf, ok := collection.DeckConfigs[deck.ConfigID]
if !ok {
return nil, fmt.Errorf("Deck %d references non-existent config %d", deck.ID, deck.ConfigID)
}
deck.Config = conf
}
return collection, nil
return a.db.Collection()
}

// Notes is a wrapper around sqlx.Rows, which means that any standard sqlx.Rows
Expand All @@ -190,14 +160,7 @@ type Notes struct {
// Notes returns a Notes struct representing all of the Note rows in the *.apkg
// package file.
func (a *Apkg) Notes() (*Notes, error) {
rows, err := a.db.Queryx(`
SELECT n.id, n.guid, n.mid, n.mod, n.usn, n.tags, n.flds, n.sfld,
CAST(n.csum AS text) AS csum -- Work-around for SQL.js trying to treat this as a float
FROM notes n
LEFT JOIN graves g ON g.oid=n.id AND g.type=1
ORDER BY id DESC
`)
return &Notes{rows}, err
return a.db.Notes()
}

// Note is a simple wrapper around sqlx's StructScan(), which returns a Note
Expand All @@ -219,30 +182,7 @@ type Cards struct {
// Cards returns a Cards struct represeting all of the non-deleted cards in the
// *.apkg package file.
func (a *Apkg) Cards() (*Cards, error) {
rows, err := a.db.Queryx(`
SELECT c.id, c.nid, c.did, c.ord, c.mod, c.usn, c.type, c.queue, c.reps, c.lapses, c.left, c.odid,
CAST(c.factor AS real)/1000 AS factor,
CASE c.queue
WHEN 0 THEN NULL
WHEN 1 THEN c.due
WHEN 2 THEN c.due*24*60*60+(SELECT crt FROM col)
END AS due,
CASE
WHEN c.ivl == 0 THEN NULL
WHEN c.ivl < 0 THEN -ivl
ELSE c.ivl*24*60*60
END AS ivl,
CASE c.queue
WHEN 0 THEN NULL
WHEN 1 THEN c.odue
WHEN 2 THEN c.odue*24*60*60+(SELECT crt FROM col)
END AS odue
FROM cards c
LEFT JOIN graves g ON g.oid=c.id AND g.type=0
WHERE g.oid IS NULL
ORDER BY id DESC
`)
return &Cards{rows}, err
return a.db.Cards()
}

func (c *Cards) Card() (*Card, error) {
Expand All @@ -259,23 +199,7 @@ type Reviews struct {
// non-deleted cards in the *.apkg package file, in reverse chronological
// order (newest first).
func (a *Apkg) Reviews() (*Reviews, error) {
rows, err := a.db.Queryx(`
SELECT r.id, r.cid, r.usn, r.ease, r.time, r.type,
CAST(r.factor AS real)/1000 AS factor,
CASE
WHEN r.ivl < 0 THEN -ivl
ELSE r.ivl*24*60*60
END AS ivl,
CASE
WHEN r.lastIvl < 0 THEN -ivl
ELSE r.lastIvl*24*60*60
END AS lastIvl
FROM revlog r
LEFT JOIN graves g ON g.oid=r.cid AND g.type=0
WHERE g.oid IS NULL
ORDER BY id DESc
`)
return &Reviews{rows}, err
return a.db.Reviews()
}

func (r *Reviews) Review() (*Review, error) {
Expand Down
4 changes: 3 additions & 1 deletion anki_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ func (t *Tags) Scan(src interface{}) error {
return nil
}

const FieldValuesDelimiter = "\x1f"

type FieldValues []string

// Scan implements the sql.Scanner interface for the FieldValues type.
Expand All @@ -353,7 +355,7 @@ func (fv *FieldValues) Scan(src interface{}) error {
default:
return errors.New("Incompatible type for FieldValues")
}
*fv = FieldValues(strings.Split(tmp, "\x1f"))
*fv = FieldValues(strings.Split(tmp, FieldValuesDelimiter))
return nil
}

Expand Down
101 changes: 101 additions & 0 deletions sqlite-std.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,97 @@ type DB struct {
tmpFile string
}

func (db *DB) Collection() (*Collection, error) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest putting these new *DB methods in a new file, sqlite.go, with no build constraints. By putting them here, they are not available for GopherJS builds (thus the Travis-CI failure).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok, thanks. I'll update my pull request later.

Copy link
Author

@tkrajina tkrajina Sep 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update, I found the situation again.

Deck 1446489859874 references non-existent config 0

Both the AnkiDroid and desktop app work without problems with this collection, so this isn't an error. Maybe just write a warning in stderr instead of returning an error here?

PS. Commented on a wrong code line, this was for the place where I commented the error when a deck isn't found by id.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that means that a default configuration is used in such a case?

Warning to sterr seems reasonable to me.

var deletedDecks []ID
if rows, err := db.Query("SELECT oid FROM graves WHERE type=2"); err != nil {
return nil, err
} else {
for rows.Next() {
id := new(ID)
if err := rows.Scan(id); err != nil {
return nil, err
}
deletedDecks = append(deletedDecks, *id)
}
}
collection := &Collection{}
if err := db.Get(collection, "SELECT * FROM col"); err != nil {
return nil, err
}
for _, deck := range collection.Decks {
for _, deleted := range deletedDecks {
if deck.ID == deleted {
delete(collection.Decks, deck.ID)
continue
}
}
conf, ok := collection.DeckConfigs[deck.ConfigID]
if !ok {
//return nil, fmt.Errorf("Deck %d references non-existent config %d", deck.ID, deck.ConfigID)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line got commented out during the code move. Was this intentional?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uops. Yes, you're right. The problem is that in one of my local Anki profiles I had a situation where there was no dek for that deck.ConfigID. I don't know how big a problem that incosistency is, but both the desktop Anki client and Ankidroid ignore this feature.

I tried now and it it's not the case any more. I suppose it's probably something left after I removed a deck and it was probably not cleaned immediately but later, after I run the "check database" desktop client.

I'm removing it for now, but if it happens again, I'll investigate more.

}
deck.Config = conf
}
return collection, nil
}

func (db *DB) Cards() (*Cards, error) {
rows, err := db.Queryx(`
SELECT c.id, c.nid, c.did, c.ord, c.mod, c.usn, c.type, c.queue, c.reps, c.lapses, c.left, c.odid,
CAST(c.factor AS real)/1000 AS factor,
CASE c.queue
WHEN 0 THEN NULL
WHEN 1 THEN c.due
WHEN 2 THEN c.due*24*60*60+(SELECT crt FROM col)
END AS due,
CASE
WHEN c.ivl == 0 THEN NULL
WHEN c.ivl < 0 THEN -ivl
ELSE c.ivl*24*60*60
END AS ivl,
CASE c.queue
WHEN 0 THEN NULL
WHEN 1 THEN c.odue
WHEN 2 THEN c.odue*24*60*60+(SELECT crt FROM col)
END AS odue
FROM cards c
LEFT JOIN graves g ON g.oid=c.id AND g.type=0
WHERE g.oid IS NULL
ORDER BY id DESC
`)
return &Cards{rows}, err
}

func (db *DB) Notes() (*Notes, error) {
rows, err := db.Queryx(`
SELECT n.id, n.guid, n.mid, n.mod, n.usn, n.tags, n.flds, n.sfld,
CAST(n.csum AS text) AS csum -- Work-around for SQL.js trying to treat this as a float
FROM notes n
LEFT JOIN graves g ON g.oid=n.id AND g.type=1
ORDER BY id DESC
`)
return &Notes{rows}, err
}

func (db *DB) Reviews() (*Reviews, error) {
rows, err := db.Queryx(`
SELECT r.id, r.cid, r.usn, r.ease, r.time, r.type,
CAST(r.factor AS real)/1000 AS factor,
CASE
WHEN r.ivl < 0 THEN -ivl
ELSE r.ivl*24*60*60
END AS ivl,
CASE
WHEN r.lastIvl < 0 THEN -ivl
ELSE r.lastIvl*24*60*60
END AS lastIvl
FROM revlog r
LEFT JOIN graves g ON g.oid=r.cid AND g.type=0
WHERE g.oid IS NULL
ORDER BY id DESc
`)
return &Reviews{rows}, err
}

func (db *DB) Close() (e error) {
if db.tmpFile != "" {
if err := os.Remove(db.tmpFile); err != nil {
Expand All @@ -35,6 +126,16 @@ func (db *DB) Close() (e error) {
return
}

func OpenOriginalDB(filename string) (db *DB, e error) {
db = &DB{}
sqldb, err := sqlx.Connect("sqlite3", filename)
if err != nil {
return db, err
}
db.DB = sqldb
return db, nil
}

func OpenDB(src io.Reader) (db *DB, e error) {
db = &DB{}
dbFile, err := dumpToTemp(src)
Expand Down