Source

ukiyo / config.go

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/mattn/go-sqlite3"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
)

type Xdg struct {
	DATA_HOME   string
	CONFIG_HOME string
	Initialized bool
}

type Site struct {
	Name     string
	Url      string
	Priority int
	Updated  int64
}

type Config struct {
	DownloadPath string
	SiteOrder    []string
	path         string
}

type Series struct {
	Name    string
	Url     string
	Site    string
	Key     string
	Updated int64
}

//"create table chapters (series text, site text, url text primary key, number text)",

type Chapter struct {
	Series  string
	Site    string
	Name    string
	Url     string
	Number  string
	Numberf float64
}

var xdg Xdg
var config Config

var DEFAULT_DOWNLOAD_PATH = expandpath("~/Downloads")

// return whether a path exists
func exists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	fmt.Printf("%s\n", err)
	return false
}

// return a path with posix tilde and ENV expansion
func expandpath(path string) string {
	if path[0] == '~' {
		sep := strings.Index(path, string(os.PathSeparator))
		if sep < 0 {
			sep = len(path)
		}
		var err error
		var u *user.User
		username := path[1:sep]
		if len(username) == 0 {
			u, err = user.Current()
		} else {
			u, err = user.Lookup(username)
		}
		if err == nil {
			path = filepath.Join(u.HomeDir, path[sep:])
		}
	}
	path = os.ExpandEnv(path)
	abs, err := filepath.Abs(path)
	if err != nil {
		return path
	}
	return abs
}

func getenv(key, default_ string) string {
	value := os.Getenv(key)
	if len(value) == 0 {
		value = default_
	}
	return value
}

// initialize platform dependent data/config paths
func (x *Xdg) init() {
	if x.Initialized {
		return
	}
	switch runtime.GOOS {
	case "linux":
		x.DATA_HOME = getenv("XDG_DATA_HOME", expandpath("~/.local/share"))
		x.CONFIG_HOME = getenv("XDG_CONFIG_HOME", expandpath("~/.config"))
	case "osx":
		x.DATA_HOME = expandpath("~/Library/Application Support/")
		x.CONFIG_HOME = expandpath("~/Library/Preferences/")
	}

	x.Initialized = true
}

func (c *Config) Open() *sql.DB {
	db, err := sql.Open("sqlite3", c.path)
	if err != nil {
		panic(fmt.Sprintf("%q", err))
	}
	return db
}

func (c *Config) getval(key string) (string, error) {
	var value string
	db := c.Open()
	defer db.Close()
	row := db.QueryRow("select value from config where key = ?", key)
	err := row.Scan(&value)
	return value, err
}

func (c *Config) init() {
	var err error
	if !xdg.Initialized {
		xdg.init()
	}
	configPath := filepath.Join(xdg.CONFIG_HOME, "ukiyo/")
	os.MkdirAll(configPath, 0755)

	c.path = filepath.Join(configPath, "config.db")

	if !exists(c.path) {
		c.initDb()
	}

	c.DownloadPath, err = c.getval("DownloadPath")
	if err != nil {
		fmt.Errorf("Could not read key 'DownloadPath' from config: %q\n", err)
	}
}

func (c *Config) initDb() {
	tables := []string{
		"create table config (key text primary key, value text)",
		"create table watchlist (key text primary key, name text, chapter text)",
		"create table sites (name text primary key, url text, priority integer, updated integer default 0)",
		"create table series (name text, key text, url text primary key, site text, updated integer default 0)",
		"create table chapters (name text, number text, url text primary key, series text, site text)",
	}
	db := c.Open()
	defer db.Close()
	// start a transaction;  sqlite is slow as hell without them
	tx, err := db.Begin()
	if err != nil {
		fmt.Printf("Unable to open transaction on config db: %s\n", err)
		return
	}

	// create tables
	for _, t := range tables {
		_, err := tx.Exec(t)
		if err != nil {
			panic(fmt.Sprintf("table panic: %q: %s\n", err, t))
		}
	}

	_, err = tx.Exec("insert into config (key, value) values (?, ?)",
		"DownloadPath", DEFAULT_DOWNLOAD_PATH)
	if err != nil {
		panic(fmt.Sprintf("panic: %q\n", err))
	}

	addSite := "insert into sites (name, url, priority) values (?, ?, ?)"
	tx.Exec(addSite, "manga-access", "http://www.manga-access.com", 1)
	tx.Exec(addSite, "mangahere", "http://www.mangahere.com", 2)
	tx.Exec(addSite, "mangareader", "http://www.mangareader.net", 3)
	tx.Exec(addSite, "mangafox", "http://www.mangafox.me", 4)

	tx.Commit()
}

func (c *Config) SetDownloadPath(path string) error {
	db := c.Open()
	defer db.Close()

	_, err := db.Exec("update config set value=? where key=?", path, "DownloadPath")
	return err
}

func (c *Config) AddSite(name, url string, priority int) error {
	db := c.Open()
	defer db.Close()

	_, err := db.Exec("insert into sites (name, url, priority) values (?, ?, ?)",
		name, url, priority)
	return err
}

func (c *Config) RemoveSite(name string) error {
	db := c.Open()
	defer db.Close()
	if len(name) == 0 {
		return fmt.Errorf("Error: name of site to delete must be provided.")
	}
	_, err := db.Exec("DELETE FROM sites WHERE name=?", name)
	return err
}

func (c *Config) SetSitePriority(name string, priority int) {

}

// Convenient interface for fetching a list of series objects from the db
func QuerySeries(db *sql.DB, query string, args ...interface{}) []*Series {
	series := make([]*Series, 0)
	rows, err := db.Query(query, args...)
	if err != nil {
		fmt.Printf("Error occured fetching series: %s\n", err)
		return series
	}
	for rows.Next() {
		s := new(Series)
		err = rows.Scan(&s.Name, &s.Key, &s.Url, &s.Site, &s.Updated)
		if err != nil {
			fmt.Printf("Error occured scanning series\n")
		} else {
			series = append(series, s)
		}
	}
	return series
}

func init() {
	config.init()
}