2017-07-08 23:13:36 +00:00
package yatwiki3
import (
2017-07-09 00:13:43 +00:00
"bytes"
"compress/flate"
2017-07-08 23:13:36 +00:00
"crypto/md5"
"database/sql"
"encoding/hex"
"fmt"
2017-07-09 00:13:43 +00:00
"io"
2017-07-08 23:13:36 +00:00
"net/http"
"time"
_ "github.com/mattn/go-sqlite3"
)
type WikiDB struct {
db * sql . DB
}
func NewWikiDB ( dbFilePath string ) ( * WikiDB , error ) {
db , err := sql . Open ( "sqlite3" , dbFilePath )
if err != nil {
return nil , err
}
wdb := WikiDB { db : db }
err = wdb . assertSchema ( )
if err != nil {
return nil , fmt . Errorf ( "assertSchema: %s" , err . Error ( ) )
}
return & wdb , nil
}
func ( this * WikiDB ) assertSchema ( ) error {
_ , err := this . db . Exec ( `
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY ,
article INTEGER ,
modified INTEGER ,
body BLOB ,
author TEXT
) ; ` )
if err != nil {
return err
}
_ , err = this . db . Exec ( `
CREATE TABLE IF NOT EXISTS titles (
id INTEGER PRIMARY KEY ,
title TEXT
) ; ` )
if err != nil {
return err
}
_ , err = this . db . Exec ( `
CREATE INDEX IF NOT EXISTS articles_modified_index ON articles ( modified )
` )
if err != nil {
return err
}
_ , err = this . db . Exec ( `
CREATE INDEX IF NOT EXISTS articles_title_index ON articles ( article )
` )
if err != nil {
return err
}
return nil
}
type Article struct {
ID int
TitleID int
Modified int64
Body [ ] byte
Author string
2017-07-09 06:05:03 +00:00
Title string
2017-07-08 23:13:36 +00:00
}
func ( this * Article ) FillModifiedTimestamp ( ) {
this . Modified = time . Now ( ) . Unix ( )
}
func ( this * Article ) FillAuthor ( r * http . Request ) {
userAgentHash := md5 . Sum ( [ ] byte ( r . UserAgent ( ) ) )
this . Author = r . RemoteAddr + "-" + hex . EncodeToString ( userAgentHash [ : ] ) [ : 6 ]
}
func ( this * WikiDB ) GetArticleById ( articleId int ) ( * Article , error ) {
row := this . db . QueryRow ( ` SELECT articles.* FROM articles WHERE id = ? ` , articleId )
return this . parseArticle ( row )
}
2017-07-09 06:05:03 +00:00
func ( this * WikiDB ) GetRevision ( revId int ) ( * Article , error ) {
2017-07-09 01:18:18 +00:00
row := this . db . QueryRow ( ` SELECT articles.*, titles.title FROM articles JOIN titles ON articles.article=titles.id WHERE articles.id = ? ` , revId )
return this . parseArticleWithTitle ( row )
}
2017-07-08 23:13:36 +00:00
func ( this * WikiDB ) GetLatestVersion ( title string ) ( * Article , error ) {
row := this . db . QueryRow ( ` SELECT articles.* FROM articles WHERE article = (SELECT id FROM titles WHERE title = ?) ORDER BY modified DESC LIMIT 1 ` , title )
return this . parseArticle ( row )
}
2017-07-09 06:05:03 +00:00
func ( this * WikiDB ) GetRevisionHistory ( title string ) ( [ ] Article , error ) {
rows , err := this . db . Query ( ` SELECT articles.id, articles.modified, articles.author FROM articles WHERE article = (SELECT id FROM titles WHERE title = ?) ORDER BY modified DESC ` , title )
if err != nil {
return nil , err
}
defer rows . Close ( )
ret := make ( [ ] Article , 0 )
for rows . Next ( ) {
a := Article { }
err := rows . Scan ( & a . ID , & a . Modified , & a . Author )
if err != nil {
return nil , err
}
ret = append ( ret , a )
}
return ret , nil
}
func ( this * WikiDB ) GetRecentChanges ( offset int , limit int ) ( [ ] Article , error ) {
rows , err := this . db . Query (
` SELECT articles.id, articles.modified, articles.author, titles.title FROM articles JOIN titles ON articles.article=titles.id ORDER BY modified DESC ` +
fmt . Sprintf ( ` LIMIT %d OFFSET %d ` , limit , offset ) ,
)
if err != nil {
return nil , err
}
defer rows . Close ( )
ret := make ( [ ] Article , 0 , limit )
for rows . Next ( ) {
a := Article { }
err := rows . Scan ( & a . ID , & a . Modified , & a . Author , & a . Title )
if err != nil {
return nil , err
}
ret = append ( ret , a )
}
return ret , nil
}
2017-07-09 01:00:26 +00:00
func ( this * WikiDB ) TotalRevisions ( ) ( int64 , error ) {
row := this . db . QueryRow ( ` SELECT COUNT(*) c FROM articles ` )
var ret int64
err := row . Scan ( & ret )
if err != nil {
return 0 , err
}
return ret , nil
}
2017-07-08 23:13:36 +00:00
func ( this * WikiDB ) ListTitles ( ) ( [ ] string , error ) {
rows , err := this . db . Query ( ` SELECT title FROM titles ORDER BY title ASC ` )
if err != nil {
return nil , err
}
defer rows . Close ( )
2017-07-09 06:05:03 +00:00
2017-07-08 23:13:36 +00:00
ret := make ( [ ] string , 0 )
for rows . Next ( ) {
var title string
err = rows . Scan ( & title )
if err != nil {
return nil , err
}
ret = append ( ret , title )
}
return ret , nil
}
2017-07-09 00:13:43 +00:00
func ( this * WikiDB ) gzinflate ( gzBody [ ] byte ) ( [ ] byte , error ) {
gzBodyReader := bytes . NewReader ( gzBody )
gzReader := flate . NewReader ( gzBodyReader )
defer gzReader . Close ( )
buffer := bytes . Buffer { }
_ , err := io . Copy ( & buffer , gzReader )
if err != nil {
return nil , err
}
return buffer . Bytes ( ) , nil
}
2017-07-08 23:13:36 +00:00
func ( this * WikiDB ) parseArticle ( row * sql . Row ) ( * Article , error ) {
a := Article { }
2017-07-09 00:13:43 +00:00
var gzBody [ ] byte
err := row . Scan ( & a . ID , & a . TitleID , & a . Modified , & gzBody , & a . Author )
if err != nil {
return nil , err
}
decompressed , err := this . gzinflate ( gzBody )
2017-07-08 23:13:36 +00:00
if err != nil {
return nil , err
}
2017-07-09 00:13:43 +00:00
a . Body = decompressed
2017-07-08 23:13:36 +00:00
return & a , nil
}
2017-07-09 06:05:03 +00:00
func ( this * WikiDB ) parseArticleWithTitle ( row * sql . Row ) ( * Article , error ) {
a := Article { }
2017-07-09 00:13:43 +00:00
var gzBody [ ] byte
err := row . Scan ( & a . ID , & a . TitleID , & a . Modified , & gzBody , & a . Author , & a . Title )
2017-07-08 23:13:36 +00:00
if err != nil {
return nil , err
}
2017-07-09 00:13:43 +00:00
decompressed , err := this . gzinflate ( gzBody )
if err != nil {
return nil , err
}
a . Body = decompressed
2017-07-08 23:13:36 +00:00
return & a , nil
}
func ( this * WikiDB ) Close ( ) {
this . db . Close ( )
}