* initial checkin
This commit is contained in:
commit
b951faa6b4
23 changed files with 910 additions and 0 deletions
BIN
res/.DS_Store
vendored
Normal file
BIN
res/.DS_Store
vendored
Normal file
Binary file not shown.
49
res/admin.css
Normal file
49
res/admin.css
Normal file
|
@ -0,0 +1,49 @@
|
|||
@charset "UTF-8";
|
||||
/* CSS Document */
|
||||
|
||||
|
||||
body {
|
||||
padding-top: 20px;
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
background-image: linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -o-linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.66, rgb(245,245,245)),
|
||||
color-stop(0.83, rgb(239,239,239))
|
||||
);
|
||||
background-repeat: no-repeat;
|
||||
background-color: rgb(245,245,245);
|
||||
}
|
||||
|
||||
.loginBox {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.loginBoxTop {
|
||||
width: 380px;
|
||||
height: 28px;
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
padding-left: 20px;
|
||||
padding-top: 11px;
|
||||
background: url(/res/loginBoxTop.png);
|
||||
}
|
||||
|
||||
.loginBoxMiddle {
|
||||
background-color: #F3F3F3;
|
||||
border-top: 0px hidden;
|
||||
border:1px solid #D2D2D2;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
;
|
||||
font-size:12px;
|
||||
height:auto;
|
||||
min-height:200px;
|
||||
padding-left:20px;
|
||||
width:378px;
|
||||
}
|
BIN
res/apple-touch-icon.png
Normal file
BIN
res/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
99
res/blogstyle.css
Normal file
99
res/blogstyle.css
Normal file
|
@ -0,0 +1,99 @@
|
|||
@charset "UTF-8";
|
||||
/* CSS Document */
|
||||
|
||||
@font-face {
|
||||
font-family: 'PT Sans';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('PT Sans'), local('PTSans-Regular'), url('http://themes.googleusercontent.com/static/fonts/ptsans/v2/7YGmE4Ls5b94ct65u07hVQLUuEpTyoUstqEm5AMlJo4.woff') format('woff');
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 20px;
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
background-image: linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -o-linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -moz-linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -ms-linear-gradient(bottom, rgb(245,245,245) 66%, rgb(239,239,239) 83%);
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.66, rgb(245,245,245)),
|
||||
color-stop(0.83, rgb(239,239,239))
|
||||
);
|
||||
}
|
||||
|
||||
.mainshell {
|
||||
width: 98%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.gradBox {
|
||||
width: 98%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.myclear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.centerbox {
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.innerBox {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.innerBoxTop {
|
||||
height: 28px;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
padding-left: 20px;
|
||||
padding-top: 11px;
|
||||
border: 1px solid #b6b6b6;
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
-moz-border-radius-topleft: 6px;
|
||||
-moz-border-radius-topright: 6px;
|
||||
-webkit-border-top-left-radius: 6px;
|
||||
-webkit-border-top-right-radius: 6px;
|
||||
border-bottom: 1px solid #dcdcdc;
|
||||
background-image: linear-gradient(bottom, rgb(246,246,246) 31%, rgb(234,234,234) 83%);
|
||||
background-image: -o-linear-gradient(bottom, rgb(246,246,246) 31%, rgb(234,234,234) 83%);
|
||||
background-image: -moz-linear-gradient(bottom, rgb(246,246,246) 31%, rgb(234,234,234) 83%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(246,246,246) 31%, rgb(234,234,234) 83%);
|
||||
background-image: -ms-linear-gradient(bottom, rgb(246,246,246) 31%, rgb(234,234,234) 83%);
|
||||
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.31, rgb(246,246,246)),
|
||||
color-stop(0.83, rgb(234,234,234))
|
||||
);
|
||||
}
|
||||
|
||||
.innerBoxMiddle {
|
||||
border: 1px solid #b6b6b6;
|
||||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
-moz-border-radius-bottomleft: 6px;
|
||||
-moz-border-radius-bottomright: 6px;
|
||||
-webkit-border-bottom-left-radius: 6px;
|
||||
-webkit-border-bottom-right-radius: 6px;
|
||||
border-top: 0px hidden;
|
||||
background-color: #FFFFFF;
|
||||
min-height: 200px;
|
||||
height: auto;
|
||||
padding-top: 21px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.innerBoxComments {
|
||||
padding-left: 20px
|
||||
}
|
BIN
res/drama.wav
Normal file
BIN
res/drama.wav
Normal file
Binary file not shown.
BIN
res/favicon.ico
Normal file
BIN
res/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
res/loginBoxTop.png
Normal file
BIN
res/loginBoxTop.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 606 B |
BIN
res/signin.gif
Normal file
BIN
res/signin.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
res/twtbtn.png
Normal file
BIN
res/twtbtn.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
33
src/Blog.hs
Normal file
33
src/Blog.hs
Normal file
|
@ -0,0 +1,33 @@
|
|||
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
|
||||
module Blog where
|
||||
|
||||
import Text.Blaze (toValue, preEscapedString)
|
||||
import Text.Blaze.Html5 (Html, (!), a, form, input, p, toHtml, label)
|
||||
import Text.Blaze.Html5.Attributes (action, enctype, href, name, size, type_, value)
|
||||
import qualified Text.Blaze.Html5 as H
|
||||
import qualified Text.Blaze.Html5.Attributes as A
|
||||
|
||||
blogTemplate :: String -> String -> String -> Html
|
||||
blogTemplate t h o = H.docTypeHtml $ do
|
||||
H.head $ do
|
||||
H.title $ (toHtml t)
|
||||
H.link ! A.rel "alternate" ! A.type_ "application/rss+xml" ! A.title "RSS-Feed" ! A.href "/rss"
|
||||
H.link ! A.rel "stylesheet" ! A.type_ "text/css" ! A.href "/res/blogstyle.css" ! A.media "all"
|
||||
H.meta ! A.httpEquiv "content-type" ! A.content "text/html;charset=UTF-8"
|
||||
{- H.style ! A.type_ "text/css" ! A.title "iOS iMessage" ! A.media "screen and (max-device-width: 1024px)" $ "#cosx{display:none;} #cios{display:block;}" -}
|
||||
H.body $ do
|
||||
H.div ! A.class_ "mainshell" $ H.div ! A.class_ "gradBox" $ H.div ! A.class_ "header" $ do
|
||||
H.a ! A.href "/" ! A.style "text-decoration:none;color:black;font-size:x-large;font-weight:bold;" $
|
||||
(toHtml t)
|
||||
H.br
|
||||
H.span ! A.id "cosx" ! A.style "display:block;" $ H.b $ contactInfo "imessage:tazjin@me.com"
|
||||
H.span ! A.id "cios" ! A.style "display:none;" $ H.b $ contactInfo "sms:tazjin@me.com"
|
||||
where
|
||||
contactInfo (imu :: String) = do
|
||||
toHtml h
|
||||
H.a ! A.href "mailto:hej@tazj.in" $ "Mail"
|
||||
", "
|
||||
H.a ! A.href "http://twitter.com/#!/tazjin" ! A.target "_blank" $ "Twitter"
|
||||
toHtml o
|
||||
H.a ! A.href (toValue imu) ! A.target "_blank" $ "iMessage"
|
||||
"."
|
98
src/Server.hs
Normal file
98
src/Server.hs
Normal file
|
@ -0,0 +1,98 @@
|
|||
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, DeriveDataTypeable #-}
|
||||
|
||||
module Main where
|
||||
|
||||
import Control.Monad (msum, mzero)
|
||||
import Data.Data (Data, Typeable)
|
||||
import Data.Monoid (mempty)
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import Data.Text hiding (map, length, zip, head)
|
||||
import Data.Time
|
||||
import Database.CouchDB
|
||||
import Happstack.Server
|
||||
import Text.Blaze (toValue, preEscapedString)
|
||||
import Text.Blaze.Html5 (Html, (!), a, form, input, p, toHtml, label)
|
||||
import Text.Blaze.Html5.Attributes (action, enctype, href, name, size, type_, value)
|
||||
import qualified Text.Blaze.Html5 as H
|
||||
import qualified Text.Blaze.Html5.Attributes as A
|
||||
import Text.JSON.Generic
|
||||
|
||||
import Blog
|
||||
|
||||
tmpPolicy :: BodyPolicy
|
||||
tmpPolicy = (defaultBodyPolicy "./tmp/" 0 1000 1000)
|
||||
|
||||
data BlogLang = EN | DE
|
||||
|
||||
data Comment = Comment{
|
||||
cauthor :: String,
|
||||
ctext :: String,
|
||||
cdate :: Integer
|
||||
} deriving (Show, Data, Typeable)
|
||||
|
||||
data Entry = Entry{
|
||||
_id :: String,
|
||||
year :: Int,
|
||||
month :: Int,
|
||||
day :: Int,
|
||||
lang :: String,
|
||||
title :: String,
|
||||
author :: String,
|
||||
text :: String,
|
||||
mtext :: String,
|
||||
comments :: [Comment]
|
||||
} deriving (Show, Data, Typeable)
|
||||
|
||||
instance Show BlogLang where
|
||||
show EN = "en"
|
||||
show DE = "de"
|
||||
|
||||
--TazBlog version
|
||||
version = ("2.2b" :: String)
|
||||
|
||||
main :: IO()
|
||||
main = do
|
||||
putStrLn ("TazBlog " ++ version ++ " in Haskell starting")
|
||||
simpleHTTP nullConf tazBlog
|
||||
|
||||
tazBlog :: ServerPart Response
|
||||
tazBlog = do
|
||||
msum [ dir "en" $ blogHandler EN
|
||||
, dir "de" $ blogHandler DE
|
||||
, do nullDir;
|
||||
ok $ showIndex DE
|
||||
, do dir " " $ nullDir;
|
||||
seeOther ("https://plus.google.com/115916629925754851590" :: String) (toResponse ())
|
||||
, dir "res" $ serveDirectory DisableBrowsing [] "../res"
|
||||
, serveDirectory DisableBrowsing [] "../res"
|
||||
]
|
||||
|
||||
blogHandler :: BlogLang -> ServerPart Response
|
||||
blogHandler lang =
|
||||
msum [ path $ \(year :: Int) -> path $ \(month :: Int) -> path $
|
||||
\(day :: Int) -> path $ \(id_ :: String) -> showEntry lang year month day id_
|
||||
]
|
||||
|
||||
showEntry :: BlogLang -> Int -> Int -> Int -> String -> ServerPart Response
|
||||
showEntry EN y m d i = undefined
|
||||
showEntry DE y m d i = undefined
|
||||
|
||||
showIndex :: BlogLang -> Response
|
||||
showIndex lang = toResponse $ renderBlogHeader lang
|
||||
|
||||
renderBlogHeader :: BlogLang -> Html
|
||||
renderBlogHeader DE = blogTemplate "Tazjins Blog" "Wer mich kontaktieren will: " " oder "
|
||||
renderBlogHeader EN = blogTemplate "Tazjin's Blog" "Get in touch with me: " " or "
|
||||
|
||||
-- http://tazj.in/2012/02/10.155234
|
||||
|
||||
-- CouchDB View Setup
|
||||
latestDEView = "function(doc){ if(doc.lang == \"de\"){ emit([doc.year, doc.month, doc.day, doc.id_], doc); } }"
|
||||
latestENView = "function(doc){ if(doc.lang == \"en\"){ emit([doc.year, doc.month, doc.day, doc.id_]], doc); } }"
|
||||
|
||||
latestDE = ViewMap "latestDE" latestDEView
|
||||
latestEN = ViewMap "latestEN" latestENView
|
||||
|
||||
setupBlogViews :: IO () -- taking *reservations* DB name as parameter because we'll have multiple stores
|
||||
setupBlogViews = runCouchDB' $
|
||||
newView "tazblog" "entries" [latestDE, latestEN]
|
BIN
tools/.DS_Store
vendored
Normal file
BIN
tools/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
tools/convertdb/.DS_Store
vendored
Normal file
BIN
tools/convertdb/.DS_Store
vendored
Normal file
Binary file not shown.
13
tools/convertdb/Makefile
Normal file
13
tools/convertdb/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
all: convertdb
|
||||
|
||||
convertdb: couch.8 convertdb.8
|
||||
8l -o convertdb convertdb.8
|
||||
|
||||
convertdb.8: convertdb.go
|
||||
8g convertdb.go
|
||||
|
||||
couch.8: couch.go
|
||||
8g couch.go
|
||||
|
||||
clean:
|
||||
rm -rf *.8 convertdb
|
121
tools/convertdb/convertdb.go
Normal file
121
tools/convertdb/convertdb.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"json"
|
||||
"./couch"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
//old
|
||||
type OldComment struct {
|
||||
Author string
|
||||
Text string
|
||||
Date string
|
||||
}
|
||||
|
||||
type OldEntry struct {
|
||||
Id string
|
||||
Title string
|
||||
Author string
|
||||
Text string
|
||||
Mtext string
|
||||
Comments []OldComment
|
||||
}
|
||||
|
||||
//new
|
||||
type Comment struct {
|
||||
Author string `json:"cauthor"`
|
||||
Text string `json:"ctext"`
|
||||
Date int64 `json:"cdate"`
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
Id string `json:"_id"`
|
||||
Year int `json:"year"`
|
||||
Month int `json:"month"`
|
||||
Day int
|
||||
Lang string `json:"lang"`
|
||||
Title string `json:"title"`
|
||||
Author string `json:"author"`
|
||||
Text string `json:"text"`
|
||||
Mtext string `json:"mtext"`
|
||||
Comments []Comment `json:"comments"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
getAllByYear("2011", 8, 12)
|
||||
getAllByYear("2012", 1, 2)
|
||||
}
|
||||
|
||||
func getAllByYear(year string, minm, maxm int){
|
||||
db, _ := couch.NewDatabase("127.0.0.1", "5984", "tazblog")
|
||||
for i:=minm;i<=maxm;i++{
|
||||
dirList, err := ioutil.ReadDir(fmt.Sprintf("data/%s/%02d/", year, i))
|
||||
if err != nil {
|
||||
fmt.Println(err.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
for d:=len(dirList)-1; d>-1; d--{
|
||||
content, cErr := ioutil.ReadFile(fmt.Sprintf("data/%s/%02d/%s", year, i, dirList[d].Name))
|
||||
if cErr != nil {
|
||||
fmt.Println(cErr)
|
||||
os.Exit(1)
|
||||
}
|
||||
var oEntry OldEntry
|
||||
jErr := json.Unmarshal(content, &oEntry)
|
||||
if jErr != nil {
|
||||
fmt.Println(jErr.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
nEntry := convertEntry(oEntry, fmt.Sprintf("data/%s/%02d/%s", year, i, dirList[d].Name))
|
||||
eId, _, err := db.Insert(nEntry)
|
||||
if err != nil {
|
||||
fmt.Println(err.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Inserted " + eId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func convertEntry(oEntry OldEntry, p string) Entry{
|
||||
var nEntry Entry
|
||||
nComments := make([]Comment, len(oEntry.Comments))
|
||||
for i:=0;i<len(oEntry.Comments);i++{
|
||||
nComments[i].Author = oEntry.Comments[i].Author
|
||||
nComments[i].Text = oEntry.Comments[i].Text
|
||||
nComments[i].Date = parseDumbTime(oEntry.Comments[i].Date)
|
||||
}
|
||||
|
||||
nEntry.Id = oEntry.Id[3:]
|
||||
nEntry.Year, _ = strconv.Atoi(p[5:9])
|
||||
nEntry.Month, _ = strconv.Atoi(p[10:12])
|
||||
nEntry.Day, _ = strconv.Atoi(p[13:15])
|
||||
nEntry.Title = oEntry.Title
|
||||
nEntry.Author = oEntry.Author
|
||||
nEntry.Mtext = oEntry.Mtext
|
||||
nEntry.Text = oEntry.Text
|
||||
nEntry.Comments = nComments
|
||||
nEntry.Lang = "de"
|
||||
|
||||
return nEntry
|
||||
}
|
||||
|
||||
//http://tazj.in/2012/02/10.155234
|
||||
func parseEntryTime(year, month, day int, ids string) string {
|
||||
x := fmt.Sprintf()
|
||||
}
|
||||
|
||||
func parseDumbTime(Year, Month, Day int, ) int64 {
|
||||
x, err := time.Parse("[Am 02.01.2006 um 15:04 Uhr]", ct)
|
||||
if err != nil {
|
||||
fmt.Println(err.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return x.Seconds()
|
||||
}
|
403
tools/convertdb/couch.go
Normal file
403
tools/convertdb/couch.go
Normal file
|
@ -0,0 +1,403 @@
|
|||
// -*- tab-width: 4 -*-
|
||||
package couch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"json"
|
||||
"http"
|
||||
"net"
|
||||
"io/ioutil"
|
||||
"url"
|
||||
)
|
||||
|
||||
var def_hdrs = map[string][]string{}
|
||||
|
||||
type buffer struct {
|
||||
b *bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *buffer) Read(out []byte) (int, os.Error) {
|
||||
return b.b.Read(out)
|
||||
}
|
||||
|
||||
func (b *buffer) Close() os.Error { return nil }
|
||||
|
||||
// Converts given URL to string containing the body of the response.
|
||||
func url_to_buf(u string) []byte {
|
||||
if r, err := http.Get(u); err == nil {
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return make([]byte, 0)
|
||||
}
|
||||
|
||||
type IdAndRev struct {
|
||||
Id string `json:"_id"`
|
||||
Rev string `json:"_rev"`
|
||||
}
|
||||
|
||||
// Sends a query to CouchDB and parses the response back.
|
||||
// method: the name of the HTTP method (POST, PUT,...)
|
||||
// url: the URL to interact with
|
||||
// headers: additional headers to pass to the request
|
||||
// in: body of the request
|
||||
// out: a structure to fill in with the returned JSON document
|
||||
func (p Database) interact(method, u string, headers map[string][]string, in []byte, out interface{}) (int, os.Error) {
|
||||
fullHeaders := map[string][]string{}
|
||||
for k, v := range headers {
|
||||
fullHeaders[k] = v
|
||||
}
|
||||
bodyLength := 0
|
||||
if in != nil {
|
||||
bodyLength = len(in)
|
||||
fullHeaders["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
req := http.Request{
|
||||
Method: method,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Close: true,
|
||||
ContentLength: int64(bodyLength),
|
||||
Header: fullHeaders,
|
||||
}
|
||||
req.TransferEncoding = []string{"chunked"}
|
||||
req.URL, _ = url.Parse(u)
|
||||
if in != nil {
|
||||
req.Body = &buffer{bytes.NewBuffer(in)}
|
||||
}
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", p.Host, p.Port))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
http_conn := http.NewClientConn(conn, nil)
|
||||
defer http_conn.Close()
|
||||
if err := http_conn.Write(&req); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r, err := http_conn.Read(&req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.StatusCode < 200 || r.StatusCode >= 300 {
|
||||
b := []byte{}
|
||||
r.Body.Read(b)
|
||||
return r.StatusCode, os.NewError("server said: " + r.Status)
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err = decoder.Decode(out); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.Body.Close()
|
||||
return r.StatusCode, nil
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
Host string
|
||||
Port string
|
||||
Name string
|
||||
}
|
||||
|
||||
func (p Database) BaseURL() string {
|
||||
return fmt.Sprintf("http://%s:%s", p.Host, p.Port)
|
||||
}
|
||||
|
||||
func (p Database) DBURL() string {
|
||||
return fmt.Sprintf("%s/%s", p.BaseURL(), p.Name)
|
||||
}
|
||||
|
||||
// Test whether CouchDB is running (ignores Database.Name)
|
||||
func (p Database) Running() bool {
|
||||
u := fmt.Sprintf("%s/%s", p.BaseURL(), "_all_dbs")
|
||||
s := url_to_buf(u)
|
||||
if len(s) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type database_info struct {
|
||||
Db_name string
|
||||
// other stuff too, ignore for now
|
||||
}
|
||||
|
||||
// Test whether specified database exists in specified CouchDB instance
|
||||
func (p Database) Exists() bool {
|
||||
di := new(database_info)
|
||||
if err := json.Unmarshal(url_to_buf(p.DBURL()), di); err != nil {
|
||||
return false
|
||||
}
|
||||
if di.Db_name != p.Name {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p Database) create_database() os.Error {
|
||||
ir := response{}
|
||||
if _, err := p.interact("PUT", p.DBURL(), def_hdrs, nil, &ir); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ir.Ok {
|
||||
return os.NewError("Create database operation returned not-OK")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deletes the given database and all documents
|
||||
func (p Database) DeleteDatabase() os.Error {
|
||||
ir := response{}
|
||||
if _, err := p.interact("DELETE", p.DBURL(), def_hdrs, nil, &ir); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ir.Ok {
|
||||
return os.NewError("Delete database operation returned not-OK")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDatabase(host, port, name string) (Database, os.Error) {
|
||||
db := Database{host, port, name}
|
||||
if !db.Running() {
|
||||
return db, os.NewError("CouchDB not running")
|
||||
}
|
||||
if !db.Exists() {
|
||||
if err := db.create_database(); err != nil {
|
||||
return db, err
|
||||
}
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// Strip _id and _rev from d, returning them separately if they exist
|
||||
func clean_JSON(d interface{}) (json_buf []byte, id, rev string, err os.Error) {
|
||||
json_buf, err = json.Marshal(d)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
err = json.Unmarshal(json_buf, &m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
id_rev := new(IdAndRev)
|
||||
err = json.Unmarshal(json_buf, &id_rev)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if _, ok := m["_id"]; ok {
|
||||
id = id_rev.Id
|
||||
m["_id"] = nil, false
|
||||
}
|
||||
if _, ok := m["_rev"]; ok {
|
||||
rev = id_rev.Rev
|
||||
m["_rev"] = nil, false
|
||||
}
|
||||
json_buf, err = json.Marshal(m)
|
||||
return
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Ok bool
|
||||
Id string
|
||||
Rev string
|
||||
Error string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// Inserts document to CouchDB, returning id and rev on success.
|
||||
// Document may specify both "_id" and "_rev" fields (will overwrite existing)
|
||||
// or just "_id" (will use that id, but not overwrite existing)
|
||||
// or neither (will use autogenerated id)
|
||||
func (p Database) Insert(d interface{}) (string, string, os.Error) {
|
||||
json_buf, id, rev, err := clean_JSON(d)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if id != "" && rev != "" {
|
||||
new_rev, err2 := p.Edit(d)
|
||||
return id, new_rev, err2
|
||||
} else if id != "" {
|
||||
return p.insert_with(json_buf, id)
|
||||
} else if id == "" {
|
||||
return p.insert(json_buf)
|
||||
}
|
||||
return "", "", os.NewError("invalid Document")
|
||||
}
|
||||
|
||||
// Private implementation of simple autogenerated-id insert
|
||||
func (p Database) insert(json_buf []byte) (string, string, os.Error) {
|
||||
ir := response{}
|
||||
if _, err := p.interact("POST", p.DBURL(), def_hdrs, json_buf, &ir); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if !ir.Ok {
|
||||
return "", "", os.NewError(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
|
||||
}
|
||||
return ir.Id, ir.Rev, nil
|
||||
}
|
||||
|
||||
// Inserts the given document (shouldn't contain "_id" or "_rev" tagged fields)
|
||||
// using the passed 'id' as the _id. Will fail if the id already exists.
|
||||
func (p Database) InsertWith(d interface{}, id string) (string, string, os.Error) {
|
||||
json_buf, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return p.insert_with(json_buf, id)
|
||||
}
|
||||
|
||||
// Private implementation of insert with given id
|
||||
func (p Database) insert_with(json_buf []byte, id string) (string, string, os.Error) {
|
||||
u := fmt.Sprintf("%s/%s", p.DBURL(), url.QueryEscape(id))
|
||||
ir := response{}
|
||||
if _, err := p.interact("PUT", u, def_hdrs, json_buf, &ir); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if !ir.Ok {
|
||||
return "", "", os.NewError(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
|
||||
}
|
||||
return ir.Id, ir.Rev, nil
|
||||
}
|
||||
|
||||
// Edits the given document, returning the new revision.
|
||||
// d must contain "_id" and "_rev" tagged fields.
|
||||
func (p Database) Edit(d interface{}) (string, os.Error) {
|
||||
json_buf, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
id_rev := new(IdAndRev)
|
||||
err = json.Unmarshal(json_buf, id_rev)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if id_rev.Id == "" {
|
||||
return "", os.NewError("Id not specified in interface")
|
||||
}
|
||||
if id_rev.Rev == "" {
|
||||
return "", os.NewError("Rev not specified in interface (try InsertWith)")
|
||||
}
|
||||
u := fmt.Sprintf("%s/%s", p.DBURL(), url.QueryEscape(id_rev.Id))
|
||||
ir := response{}
|
||||
if _, err = p.interact("PUT", u, def_hdrs, json_buf, &ir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ir.Rev, nil
|
||||
}
|
||||
|
||||
// Edits the given document, returning the new revision.
|
||||
// d should not contain "_id" or "_rev" tagged fields. If it does, they will
|
||||
// be overwritten with the passed values.
|
||||
func (p Database) EditWith(d interface{}, id, rev string) (string, os.Error) {
|
||||
if id == "" || rev == "" {
|
||||
return "", os.NewError("EditWith: must specify both id and rev")
|
||||
}
|
||||
json_buf, err := json.Marshal(d)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
err = json.Unmarshal(json_buf, &m)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m["_id"] = id
|
||||
m["_rev"] = rev
|
||||
return p.Edit(m)
|
||||
}
|
||||
|
||||
// Unmarshals the document matching id to the given interface, returning rev.
|
||||
func (p Database) Retrieve(id string, d interface{}) (string, os.Error) {
|
||||
if id == "" {
|
||||
return "", os.NewError("no id specified")
|
||||
}
|
||||
json_buf := url_to_buf(fmt.Sprintf("%s/%s", p.DBURL(), id))
|
||||
id_rev := new(IdAndRev)
|
||||
if err := json.Unmarshal(json_buf, &id_rev); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if id_rev.Id != id {
|
||||
return "", os.NewError("invalid id specified")
|
||||
}
|
||||
return id_rev.Rev, json.Unmarshal(json_buf, d)
|
||||
}
|
||||
|
||||
// Deletes document given by id and rev.
|
||||
func (p Database) Delete(id, rev string) os.Error {
|
||||
headers := map[string][]string{
|
||||
"If-Match": []string{rev},
|
||||
}
|
||||
u := fmt.Sprintf("%s/%s", p.DBURL(), id)
|
||||
ir := response{}
|
||||
if _, err := p.interact("DELETE", u, headers, nil, &ir); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ir.Ok {
|
||||
return os.NewError(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
Id *string
|
||||
}
|
||||
|
||||
type keyed_view_response struct {
|
||||
Total_rows uint64
|
||||
Offset uint64
|
||||
Rows []Row
|
||||
}
|
||||
|
||||
// Return array of document ids as returned by the given view/options combo.
|
||||
// view should be eg. "_design/my_foo/_view/my_bar"
|
||||
// options should be eg. { "limit": 10, "key": "baz" }
|
||||
func (p Database) QueryIds(view string, options map[string]interface{}) ([]string, os.Error) {
|
||||
kvr := new(keyed_view_response)
|
||||
|
||||
if err := p.Query(view, options, kvr); err != nil {
|
||||
fmt.Println("Query error: " + err.String())
|
||||
return make([]string, 0), err
|
||||
}
|
||||
ids := make([]string, len(kvr.Rows))
|
||||
i := 0
|
||||
for _, row := range kvr.Rows {
|
||||
if row.Id != nil {
|
||||
ids[i] = *row.Id
|
||||
i++
|
||||
}
|
||||
}
|
||||
return ids[:i], nil
|
||||
}
|
||||
|
||||
func (p Database) Query(view string, options map[string]interface{}, results interface{}) os.Error {
|
||||
if view == "" {
|
||||
return os.NewError("empty view")
|
||||
}
|
||||
|
||||
var parameters string
|
||||
for k, v := range options {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
parameters += fmt.Sprintf(`%s=%s&`, k, url.QueryEscape(t))
|
||||
case int:
|
||||
parameters += fmt.Sprintf(`%s=%d&`, k, t)
|
||||
case bool:
|
||||
parameters += fmt.Sprintf(`%s=%v&`, k, t)
|
||||
default:
|
||||
// TODO more types are supported
|
||||
panic(fmt.Sprintf("unsupported value-type %T in Query", t))
|
||||
}
|
||||
}
|
||||
full_url := fmt.Sprintf("%s/%s?%s", p.DBURL(), view, parameters)
|
||||
json_buf := url_to_buf(full_url)
|
||||
|
||||
if err := json.Unmarshal(json_buf, results); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
10
tools/music/Makefile
Normal file
10
tools/music/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
all: music
|
||||
|
||||
music: iTunes.8
|
||||
8l -o music iTunes.8
|
||||
|
||||
iTunes.8: iTunes.go
|
||||
8g iTunes.go
|
||||
|
||||
clean:
|
||||
rm -rf *.8 music
|
4
tools/music/gettitle
Executable file
4
tools/music/gettitle
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
osascript -e 'tell application "iTunes" to get name of current track'
|
||||
osascript -e 'tell application "iTunes" to get artist of current track'
|
BIN
tools/music/iTunes.8
Normal file
BIN
tools/music/iTunes.8
Normal file
Binary file not shown.
79
tools/music/iTunes.go
Normal file
79
tools/music/iTunes.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
/* This program is free software. It comes without any warranty, to
|
||||
* the extent permitted by applicable law. You can redistribute it
|
||||
* and/or modify it under the terms of the Do What The Fuck You Want
|
||||
* To Do Public License, Version 3, as published by Vincent Ambo. See
|
||||
* included COPYING file for more details. */
|
||||
|
||||
package main
|
||||
|
||||
import( "fmt"
|
||||
"exec"
|
||||
"strings"
|
||||
"http"
|
||||
"url"
|
||||
"flag"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var authkey, host, c_artist, c_title string
|
||||
|
||||
func init(){
|
||||
flag.StringVar(&authkey, "key", "none", "http auth key")
|
||||
flag.StringVar(&host, "host", "http://localhost:8080", "host")
|
||||
}
|
||||
|
||||
func main(){
|
||||
flag.Parse()
|
||||
fmt.Println("Music updater launching. Update occurs once per minute.")
|
||||
go updaterThread()
|
||||
|
||||
var cc string
|
||||
for {
|
||||
fmt.Println("Type \"exit\" to quit")
|
||||
fmt.Scanf("%s", &cc)
|
||||
switch(cc) {
|
||||
case "exit":
|
||||
os.Exit(1)
|
||||
default:
|
||||
fmt.Println("Type \"exit\" to quit")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updaterThread(){
|
||||
rValues := make(url.Values)
|
||||
rValues.Add("artist", "")
|
||||
rValues.Add("title", "")
|
||||
rValues.Add("key", authkey)
|
||||
|
||||
for {
|
||||
title, artist := getTrack()
|
||||
if (title != c_title) || (artist != c_artist) {
|
||||
fmt.Println("Updating to: " + title + " - " + artist)
|
||||
c_artist = artist; c_title = title
|
||||
rValues.Set("artist", artist)
|
||||
rValues.Set("title", title)
|
||||
_, err := http.PostForm(fmt.Sprint(host + "/setsong"), rValues)
|
||||
if err != nil {
|
||||
fmt.Println(err.String())
|
||||
}
|
||||
}
|
||||
time.Sleep(60000000000)
|
||||
}
|
||||
}
|
||||
|
||||
func getTrack() (title, artist string){
|
||||
a, err := exec.Command("./gettitle").Output()
|
||||
if err != nil {
|
||||
fmt.Println("err: " + err.String())
|
||||
title = ""
|
||||
artist = ""
|
||||
} else {
|
||||
trackInfo := strings.Split(string(a), "\n")
|
||||
title = trackInfo[0]
|
||||
artist = trackInfo[1]
|
||||
}
|
||||
return
|
||||
}
|
BIN
tools/music/music
Executable file
BIN
tools/music/music
Executable file
Binary file not shown.
1
tools/music/start
Executable file
1
tools/music/start
Executable file
|
@ -0,0 +1 @@
|
|||
./music -host "http://tazj.in" -key "4058ef41bbca252a7b7e675a61dbf935"
|
Loading…
Reference in a new issue