Skip to content

Instantly share code, notes, and snippets.

@LouisStAmour
Created July 25, 2013 21:13
Show Gist options
  • Save LouisStAmour/6083826 to your computer and use it in GitHub Desktop.
Save LouisStAmour/6083826 to your computer and use it in GitHub Desktop.
package main
import "database/sql"
import "encoding/xml"
import "flag"
import "html"
import "html/template"
import "net/http"
import "net/url"
import "bytes"
import "fmt"
import "strconv"
import "os"
import "regexp"
import "strings"
import "io/ioutil"
import _ "github.com/Go-SQL-Driver/MySQL"
import "image"
import "image/color"
import "image/draw"
import _ "image/png"
import "image/jpeg"
import _ "image/gif"
import "github.com/nfnt/resize"
var (
username = flag.String("username", "root", "MySQL username")
password = flag.String("password", "p4ssw0rd", "MySQL password")
location = flag.String("location", "unix(/var/run/mysqld/mysqld.sock)", "MySQL host and/or port")
database = flag.String("database", "my_app", "MySQL database")
)
const (
HEIGHT = 120
)
type Page struct {
TemplateName string
DatabasePostfix string
FilenamePrefix string
NewTitleLists []*NewTitleList
StaffPicksTopics []*StaffPicksTopic
}
type NewTitleList struct {
Id int
Title string
Link string
SearchResult SearchResult
WidthOfCovers int
Index int
}
type Bib struct {
Format string
Title string
SubTitle string
Link string
AuthorLink template.HTML
Authors []string `xml:"Authors>String"`
ISBNs []string `xml:"ISBNs>String"`
UPCs []string `xml:"UPCs>String"`
EANs []string `xml:"EANs>String"`
BcId string
Cover interface{}
CoverResized interface{}
CoverOffset int
}
type Cover struct {
URL string
Width int
Height int
Format string
Image image.Image
}
type StaffPicksTopic struct {
Name string
Index int
HideButtons bool
Lists []*StaffPicksList
}
type StaffPicksList struct {
Id int
BcId string
Title string
TitleNil bool
Topic string
Link string
Description string
DescNil bool
AuthorName string
AuthorNil bool
AuthorLink template.HTML
Index int
UserList UserList
}
type UserList struct {
Name string
Description string
User struct {
BcId string
DisplayName string
}
ListItems struct {
PageSize int
Page int
TotalCount int
TotalPages int
Bibs []Bib `xml:"BibListItem>Bib"`
}
}
type SearchResult struct {
PageSize int
Page int
TotalCount int
TotalPages int
Bibs []Bib `xml:"Bib"`
}
func parseLinkFromDatabase(link string) (string, error) {
u, err := url.ParseRequestURI(link)
if err != nil {
return "", err
}
v := u.Query()
v.Set("display_quantity", strconv.Itoa(25))
v.Set("page", strconv.Itoa(1))
u.RawQuery = v.Encode()
u.Path += ".xml"
return u.String(), nil
}
func handleError(err error) {
if err != nil {
panic(err.Error())
}
}
func imageHasDifferentColors(img image.Image) bool {
r, g, b, a := img.At(0, 0).RGBA()
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
cR, cG, cB, cA := img.At(x, y).RGBA()
if cR != r || cG != g || cB != b || cA != a {
return true
}
}
}
return false
}
func lookupCover(number string, isbn bool, size string) (*Cover, error) {
cover := new(Cover)
if isbn {
cover.URL = "http://www.syndetics.com/index.aspx?isbn=" + template.URLQueryEscaper(number) + "/" + template.URLQueryEscaper(size) + "C.JPG&client=XXX&type=xw12&oclc=&upc="
} else {
cover.URL = "http://www.syndetics.com/index.aspx?isbn=/" + template.URLQueryEscaper(size) + "C.JPG&client=XXX&type=xw12&oclc=&upc=" + template.URLQueryEscaper(number)
}
resp, err := http.Get(cover.URL)
if err != nil {
if strings.Contains(err.Error(), "unexpected EOF") {
resp, err = http.Get(cover.URL)
if err != nil {
return cover, err
}
} else {
return cover, err
}
}
defer resp.Body.Close()
cover.Image, cover.Format, err = image.Decode(resp.Body)
if err != nil {
return cover, err
}
if imageHasDifferentColors(cover.Image) {
cover.Width = cover.Image.Bounds().Size().X
cover.Height = cover.Image.Bounds().Size().Y
} else {
cover.Width = 0
cover.Height = 0
}
return cover, nil
}
func excerptFromString(s string, count int) string {
r := regexp.MustCompile("\\s*\\S+")
m := r.FindAllString(s, count)
if m != nil && len(m) == count {
return strings.Join(m, " ") + " . . ."
}
return s
}
func (p *Page) getStaffPicks(db *sql.DB) {
page := *p
rows, err := db.Query("select f.id, t.title AS topic, f.title AS feed, f.link, f.description, f.author_name, f.author_link from bc_staff_picks" + page.DatabasePostfix + "_feeds f, bc_staff_picks" + page.DatabasePostfix + "_topics t WHERE f.topic_id = t.id ORDER BY t.weight, f.weight")
handleError(err)
defer rows.Close()
var (
authorLink string
authorName interface{}
title interface{}
description interface{}
topic *StaffPicksTopic
i int
j int
)
for rows.Next() {
list := new(StaffPicksList)
var topicName string
err = rows.Scan(&list.Id, &topicName, &title, &list.Link, &description, &authorName, &authorLink)
handleError(err)
if topic == nil || topicName != topic.Name {
j = 0
topic = new(StaffPicksTopic)
topic.Name = topicName
topic.Index = i + 1
i++
page.StaffPicksTopics = append(page.StaffPicksTopics, topic)
}
if title == nil {
list.TitleNil = true
} else {
list.Title = string(title.([]byte))
}
if description == nil {
list.DescNil = true
} else {
list.Description = string(description.([]byte))
}
list.Description = excerptFromString(list.Description, 25)
if authorName == nil {
list.AuthorNil = true
} else {
list.AuthorName = string(authorName.([]byte))
}
list.AuthorLink = template.HTML(authorLink)
list.Index = j + 1
topic.HideButtons = list.Index < 2
j++
topic.Lists = append(topic.Lists, list)
}
err = rows.Err()
handleError(err)
*p = page
}
func (p *Page) getNewTitles(db *sql.DB) {
page := *p
rows, err := db.Query("SELECT `id`, `title`, `link` FROM bc_new_titles" + page.DatabasePostfix + " ORDER BY weight ASC")
handleError(err)
defer rows.Close()
var (
i int
)
for rows.Next() {
list := new(NewTitleList)
err = rows.Scan(&list.Id, &list.Title, &list.Link)
handleError(err)
list.Index = i + 1
i++
page.NewTitleLists = append(page.NewTitleLists, list)
}
err = rows.Err()
handleError(err)
*p = page
}
func (bib *Bib) LookupCover(width uint, height uint) {
sizes := [3]string{"M", "S", "L"} // Large as last resort
for _, size := range sizes {
for _, ean := range bib.EANs {
if bib.Cover != nil {
break
}
cover, err := lookupCover(ean, true, size)
if err != nil {
if err.Error() != "image: unknown format" {
handleError(err)
}
} else {
if cover.Width > 1 && cover.Height > 1 {
bib.Cover = *cover
}
}
}
for _, isbn := range bib.ISBNs {
if bib.Cover != nil {
break
}
cover, err := lookupCover(isbn, true, size)
if err != nil {
if err.Error() != "image: unknown format" {
handleError(err)
}
} else {
if cover.Width > 1 && cover.Height > 1 {
bib.Cover = *cover
}
}
}
for _, upc := range bib.UPCs {
if bib.Cover != nil {
break
}
cover, err := lookupCover(upc, false, size)
if err != nil {
if err.Error() != "image: unknown format" {
handleError(err)
}
} else {
if cover.Width > 1 && cover.Height > 1 {
bib.Cover = *cover
}
}
}
}
if bib.Cover == nil {
} else {
c := new(Cover)
c.Image = resize.Resize(width, height, bib.Cover.(Cover).Image, resize.Bicubic)
c.Width = c.Image.Bounds().Size().X
c.Height = c.Image.Bounds().Size().Y
bib.CoverResized = *c
}
}
var lastDigitsRegexp = regexp.MustCompile("/(\\d+)[^/]*$")
const (
SP_WIDTH = 35
SP_HEIGHT = 52
SP_PADDING = 5
SP_GRID_SIZE = 3
)
var doubleQuotedReplacer = strings.NewReplacer("&amp;amp;", "&amp;", "&amp;lt;", "&lt;", "&amp;gt;", "&gt;", "&amp;apos;", "&apos;", "&amp;quot;", "&quot;")
func (page *Page) processStaffPicks() {
for t, topics := range page.StaffPicksTopics {
for _, list := range topics.Lists {
submatches := lastDigitsRegexp.FindStringSubmatch(list.Link)
if submatches == nil || len(submatches) < 1 || submatches[1] == "" {
fmt.Println("ListID", list.Id, "has unparsable link", list.Link)
} else {
list.BcId = submatches[1]
resp, err := http.Get("http://example.org/api/UserList/" + list.BcId)
handleError(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
handleError(err)
decoder := xml.NewDecoder(strings.NewReader(doubleQuotedReplacer.Replace(string(body))))
decoder.Strict = false
var ul UserList
decoder.Decode(&ul)
if list.TitleNil {
list.Title = ul.Name
} else {
list.Title = html.UnescapeString(list.Title)
}
if list.DescNil {
list.Description = ul.Description
} else {
list.Description = html.UnescapeString(list.Description)
}
if list.AuthorNil {
list.AuthorName = ul.User.DisplayName
} else {
list.AuthorName = html.UnescapeString(list.AuthorName)
}
list.AuthorLink = template.HTML("<a href=\"http://example.org/collection/show/" + ul.User.BcId + "/library\">" + list.AuthorName + "</a>")
fmt.Println(list.Title)
list.UserList = ul
}
}
for i, list := range topics.Lists {
var coversFound int
for j, bib := range list.UserList.ListItems.Bibs {
if coversFound < SP_GRID_SIZE*SP_GRID_SIZE {
bib.LookupCover(SP_WIDTH, SP_HEIGHT)
if bib.Cover != nil {
coversFound++
} else {
fmt.Println("No cover found for", bib.BcId)
}
}
// Save our progress
page.StaffPicksTopics[t].Lists[i].UserList.ListItems.Bibs[j] = bib
}
}
for _, list := range topics.Lists {
m := image.NewRGBA(image.Rect(0, 0, 115, 166))
white := color.RGBA{255, 255, 255, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{white}, image.ZP, draw.Src)
var c int
for _, bib := range list.UserList.ListItems.Bibs {
if c < SP_GRID_SIZE*SP_GRID_SIZE && bib.CoverResized != nil {
cover := bib.CoverResized.(Cover)
dp := image.Point{c%SP_GRID_SIZE*SP_WIDTH + c%SP_GRID_SIZE*SP_PADDING,
c/SP_GRID_SIZE*SP_HEIGHT + c/SP_GRID_SIZE*SP_PADDING}
r := image.Rectangle{dp, dp.Add(cover.Image.Bounds().Size())}
draw.Draw(m, r, cover.Image, cover.Image.Bounds().Min, draw.Over)
c++
}
}
saveImage(m, page.FilenamePrefix+"staff_picks_covers_"+strconv.Itoa(list.Id))
}
}
}
func saveImage(m *image.RGBA, filename string) {
img, err := os.Create(filename + ".jpg")
defer img.Close()
handleError(err)
jpeg.Encode(img, m, &jpeg.Options{95})
}
func (page *Page) processNewTitles() {
for _, list := range page.NewTitleLists {
url, err := parseLinkFromDatabase(list.Link)
handleError(err)
resp, err := http.Get(url)
handleError(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
handleError(err)
decoder := xml.NewDecoder(strings.NewReader(doubleQuotedReplacer.Replace(string(body))))
decoder.Strict = false
var sr SearchResult
decoder.Decode(&sr)
list.SearchResult = sr
}
for i, list := range page.NewTitleLists {
for j, bib := range list.SearchResult.Bibs {
bib.Link = "http://example.org/item/show/" + bib.BcId
if len(bib.Authors) > 0 {
bib.AuthorLink = template.HTML("<a href=\"http://example.org/search?q=" + html.EscapeString(template.URLQueryEscaper(bib.Authors[0])) + "&t=author\">" + template.HTMLEscaper(bib.Authors[0]) + "</a>")
}
bib.LookupCover(0, HEIGHT)
if bib.CoverResized != nil {
bib.CoverOffset = list.WidthOfCovers
list.WidthOfCovers += bib.CoverResized.(Cover).Width
}
// Save our progress
page.NewTitleLists[i].SearchResult.Bibs[j] = bib
}
}
for _, list := range page.NewTitleLists {
m := image.NewRGBA(image.Rect(0, 0, list.WidthOfCovers, HEIGHT))
white := color.RGBA{255, 255, 255, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{white}, image.ZP, draw.Src)
var c int
for _, bib := range list.SearchResult.Bibs {
if bib.CoverResized != nil {
cover := bib.CoverResized.(Cover)
dp := image.Point{c, 0}
r := image.Rectangle{dp, dp.Add(cover.Image.Bounds().Size())}
draw.Draw(m, r, cover.Image, cover.Image.Bounds().Min, draw.Over)
c += cover.Width
}
}
saveImage(m, page.FilenamePrefix+"new_titles_covers_"+strconv.Itoa(list.Id))
}
}
func main() {
flag.Parse()
db, err := sql.Open("mysql", *username+":"+*password+"@"+*location+"/"+*database+"?charset=utf8")
handleError(err)
defer db.Close()
pages := [3]*Page{
&Page{TemplateName: "teens", DatabasePostfix: "_teens", FilenamePrefix: "teens_"},
&Page{TemplateName: "kids", DatabasePostfix: "_kids", FilenamePrefix: "kids_"},
&Page{TemplateName: "index", DatabasePostfix: "", FilenamePrefix: ""},
}
t := template.Must(template.ParseFiles(
"summary_carousel.template.html",
"carousel.template.html"))
q, err := db.Prepare("INSERT INTO go_blocks (id, data) values(?, ?) on duplicate key update data=values(data)")
handleError(err)
for _, page := range pages {
page.getStaffPicks(db)
page.getNewTitles(db)
page.processStaffPicks()
page.processNewTitles()
var (
staffPicksHTML bytes.Buffer
newTitlesHTML bytes.Buffer
)
// out, err := os.Create(page.TemplateName + ".html")
// defer out.Close()
// handleError(err)
err = t.ExecuteTemplate(&staffPicksHTML, "summary_carousel", page)
handleError(err)
tmpString := staffPicksHTML.String()
_, err = q.Exec("staff_picks"+page.DatabasePostfix, tmpString)
handleError(err)
err = t.ExecuteTemplate(&newTitlesHTML, "carousel", page)
handleError(err)
tmpString = newTitlesHTML.String()
_, err = q.Exec("new_titles"+page.DatabasePostfix, tmpString)
handleError(err)
}
}
{{define "index"}}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr" class="js"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Homepage</title>
</head>
<body class="not-front logged-in page-kids-1 node-type-page-1 page-kids-home section-kids two-sidebars">
<h2 class="title">Booklists</h2>
<div class="content">
{{template "summary_carousel" .}}
</div>
</div> <!-- /.block --><div id="block-bc_new_titles_teens-0" class="block block-bc_new_titles_teens last region-odd odd region-count-3 count-7">
<h2 class="title">New Titles</h2>
<div class="content">
{{template "carousel" .}}
</div>
</div> <!-- /.block --></div> <!-- /.region -->
</body></html>
{{end}}
{{define "kids"}}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr" class="js"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Kids Homepage</title>
</head>
<body class="not-front logged-in page-kids-1 node-type-page-1 page-kids-home section-kids two-sidebars">
<h2 class="title">Booklists</h2>
<div class="content">
{{template "summary_carousel" .}}
</div>
</div> <!-- /.block --><div id="block-bc_new_titles_teens-0" class="block block-bc_new_titles_teens last region-odd odd region-count-3 count-7">
<h2 class="title">New Titles</h2>
<div class="content">
{{template "carousel" .}}
</div>
</div> <!-- /.block --></div> <!-- /.region -->
</body></html>
{{end}}
{{define "teens"}}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr" class="js"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Teens Homepage</title>
</head>
<body class="not-front logged-in page-kids-1 node-type-page-1 page-kids-home section-kids two-sidebars">
<h2 class="title">Booklists</h2>
<div class="content">
{{template "summary_carousel" .}}
</div>
</div> <!-- /.block --><div id="block-bc_new_titles_teens-0" class="block block-bc_new_titles_teens last region-odd odd region-count-3 count-7">
<h2 class="title">New Titles</h2>
<div class="content">
{{template "carousel" .}}
</div>
</div> <!-- /.block --></div> <!-- /.region -->
</body></html>
{{end}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment