commit b951faa6b4771693f08b4002c771a508904d97a1 Author: "Vincent Ambo ext:(%22) Date: Wed Feb 22 22:03:31 2012 +0100 * initial checkin diff --git a/res/.DS_Store b/res/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/res/.DS_Store differ diff --git a/res/admin.css b/res/admin.css new file mode 100644 index 000000000..7a4d41897 --- /dev/null +++ b/res/admin.css @@ -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; +} \ No newline at end of file diff --git a/res/apple-touch-icon.png b/res/apple-touch-icon.png new file mode 100644 index 000000000..22ba058cd Binary files /dev/null and b/res/apple-touch-icon.png differ diff --git a/res/blogstyle.css b/res/blogstyle.css new file mode 100644 index 000000000..8c065c53c --- /dev/null +++ b/res/blogstyle.css @@ -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 +} \ No newline at end of file diff --git a/res/drama.wav b/res/drama.wav new file mode 100644 index 000000000..20d326c5f Binary files /dev/null and b/res/drama.wav differ diff --git a/res/favicon.ico b/res/favicon.ico new file mode 100644 index 000000000..2958dd3af Binary files /dev/null and b/res/favicon.ico differ diff --git a/res/loginBoxTop.png b/res/loginBoxTop.png new file mode 100644 index 000000000..8a0ee3ba8 Binary files /dev/null and b/res/loginBoxTop.png differ diff --git a/res/signin.gif b/res/signin.gif new file mode 100644 index 000000000..bbe282bae Binary files /dev/null and b/res/signin.gif differ diff --git a/res/twtbtn.png b/res/twtbtn.png new file mode 100644 index 000000000..3a54c73c4 Binary files /dev/null and b/res/twtbtn.png differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 000000000..db284a629 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/Blog.hs b/src/Blog.hs new file mode 100644 index 000000000..2a62bb768 --- /dev/null +++ b/src/Blog.hs @@ -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" + "." \ No newline at end of file diff --git a/src/Server.hs b/src/Server.hs new file mode 100644 index 000000000..aa41a2173 --- /dev/null +++ b/src/Server.hs @@ -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] diff --git a/tools/.DS_Store b/tools/.DS_Store new file mode 100644 index 000000000..714b6567a Binary files /dev/null and b/tools/.DS_Store differ diff --git a/tools/convertdb/.DS_Store b/tools/convertdb/.DS_Store new file mode 100644 index 000000000..5de3023c9 Binary files /dev/null and b/tools/convertdb/.DS_Store differ diff --git a/tools/convertdb/Makefile b/tools/convertdb/Makefile new file mode 100644 index 000000000..eba288ec1 --- /dev/null +++ b/tools/convertdb/Makefile @@ -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 diff --git a/tools/convertdb/convertdb.go b/tools/convertdb/convertdb.go new file mode 100644 index 000000000..adef31910 --- /dev/null +++ b/tools/convertdb/convertdb.go @@ -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= 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 +} diff --git a/tools/music/Makefile b/tools/music/Makefile new file mode 100644 index 000000000..488c7eb1b --- /dev/null +++ b/tools/music/Makefile @@ -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 \ No newline at end of file diff --git a/tools/music/gettitle b/tools/music/gettitle new file mode 100755 index 000000000..0bd4cc697 --- /dev/null +++ b/tools/music/gettitle @@ -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' diff --git a/tools/music/iTunes.8 b/tools/music/iTunes.8 new file mode 100644 index 000000000..ac5ce58ff Binary files /dev/null and b/tools/music/iTunes.8 differ diff --git a/tools/music/iTunes.go b/tools/music/iTunes.go new file mode 100644 index 000000000..5eb530f6b --- /dev/null +++ b/tools/music/iTunes.go @@ -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 +} \ No newline at end of file diff --git a/tools/music/music b/tools/music/music new file mode 100755 index 000000000..f91b62fe8 Binary files /dev/null and b/tools/music/music differ diff --git a/tools/music/start b/tools/music/start new file mode 100755 index 000000000..b9f1358e3 --- /dev/null +++ b/tools/music/start @@ -0,0 +1 @@ +./music -host "http://tazj.in" -key "4058ef41bbca252a7b7e675a61dbf935"