amuse.git

commit 892b84aec721411390f331df4d03996446350a15

Author: Adam <git@apiote.tk>

show TV queue

 datastructure/item.go | 9 ++-
 datastructure/list.go | 6 ++
 datastructure/tvqueue.go | 58 +++++++++++++++++++++++
 datastructure/watchlist.go | 8 +++
 db/db.go | 61 ++++++++++++++++++++++++
 front/capnproto.go | 4 +
 front/html.go | 6 ++
 front/renderer.go | 1 
 libamuse/common.go | 11 ++++
 libamuse/tvqueue.go | 55 ++++++++++++++++++++++
 libamuse/watchlist.go | 16 +-----
 router.go | 7 ++
 templates/about.html | 1 
 templates/book.html | 1 
 templates/bookserie.html | 1 
 templates/experiences.html | 1 
 templates/film.html | 1 
 templates/index.html | 1 
 templates/person.html | 1 
 templates/search.html | 1 
 templates/tvqueue.html | 99 ++++++++++++++++++++++++++++++++++++++++
 templates/tvserie.html | 1 
 templates/watchlist.html | 3 
 tmdb/genres.go | 20 ++++++-
 tmdb/serie.go | 2 


diff --git a/datastructure/item.go b/datastructure/item.go
index 77383d4f284743907f1f204535254f9c2430a99f..44ca31ef527796280cfee1e3f29e38916e646202 100644
--- a/datastructure/item.go
+++ b/datastructure/item.go
@@ -18,8 +18,9 @@ 	Collection int
 	Part       int
 }
 
-func (i ItemInfo) IsUnreleased() bool {
-	return i.Status != "Released"
+func (i ItemInfo) IsUnreleased(itemType ItemType) bool {
+	return (itemType == ItemTypeFilm && i.Status != "Released") ||
+		(itemType == ItemTypeTvserie && i.Status != "Returning Series" && i.Status != "Ended")
 }
 
 func (i ItemInfo) GetGenres(genres map[int]string) string {
@@ -28,7 +29,9 @@ 	genreNames := []string{}
 	for _, genreId := range genreIds {
 		if genreId != "" {
 			genreIdInt, _ := strconv.ParseInt(genreId, 10, 64)
-			genreNames = append(genreNames, genres[int(genreIdInt)])
+			if genres[int(genreIdInt)] != "" {
+				genreNames = append(genreNames, genres[int(genreIdInt)])
+			}
 		}
 	}
 	return strings.Join(genreNames, ", ")




diff --git a/datastructure/list.go b/datastructure/list.go
new file mode 100644
index 0000000000000000000000000000000000000000..4ba072a0e226c2456e3450178332a51ac7fe4ea3
--- /dev/null
+++ b/datastructure/list.go
@@ -0,0 +1,6 @@
+package datastructure
+
+type List interface {
+	SetGenres(map[int]string)
+	GetType() ItemType
+}




diff --git a/datastructure/tvqueue.go b/datastructure/tvqueue.go
new file mode 100644
index 0000000000000000000000000000000000000000..33104c0163bb6bf9ced05533192cb3e6f9583e9d
--- /dev/null
+++ b/datastructure/tvqueue.go
@@ -0,0 +1,58 @@
+package datastructure
+
+import (
+	"strconv"
+)
+
+type TvQueueEntry struct {
+	ItemInfo
+	Id              string
+	HasPrevious     bool
+	WatchedEpisodes int
+}
+
+func (e TvQueueEntry) GetYears() string {
+	if e.YearStart == 0 {
+		return ""
+	} else if e.Status == "Ended" {
+		if e.YearEnd == e.YearStart {
+			return strconv.FormatInt(int64(e.YearStart), 10)
+		} else {
+			return strconv.FormatInt(int64(e.YearStart), 10) + "–" + strconv.FormatInt(int64(e.YearEnd), 10)
+		}
+	} else {
+		return strconv.FormatInt(int64(e.YearStart), 10) + "–"
+	}
+}
+
+type TvQueue struct {
+	List   []TvQueueEntry
+	Page   int
+	Pages  int
+	Genres map[int]string
+	Query  string
+}
+
+func (q *TvQueue) SetGenres(m map[int]string) {
+	q.Genres = m
+}
+
+func (q *TvQueue) GetType() ItemType {
+	return ItemTypeTvserie
+}
+
+func (q TvQueue) NextPage() int {
+	if q.Page < q.Pages {
+		return q.Page + 1
+	} else {
+		return q.Page
+	}
+}
+
+func (q TvQueue) PrevPage() int {
+	if q.Page > 1 {
+		return q.Page - 1
+	} else {
+		return q.Page
+	}
+}




diff --git a/datastructure/watchlist.go b/datastructure/watchlist.go
index 8fbfeaae6aab4a43c6eb35456f0721a4ee467810..ac12a25f399487ca3f68bf76969a97d219812715 100644
--- a/datastructure/watchlist.go
+++ b/datastructure/watchlist.go
@@ -14,6 +14,14 @@ 	Genres map[int]string
 	Query  string
 }
 
+func (w *Watchlist) SetGenres(m map[int]string) {
+	w.Genres = m
+}
+
+func (w *Watchlist) GetType() ItemType {
+	return ItemTypeFilm
+}
+
 func (w Watchlist) NextPage() int {
 	if w.Page < w.Pages {
 		return w.Page + 1




diff --git a/db/db.go b/db/db.go
index 1cfb126da492da2624b241c5a2835af7a10bf373..b9cfb57eb676971a4498e8242cd3991625a90107 100644
--- a/db/db.go
+++ b/db/db.go
@@ -585,6 +585,67 @@
 	return watchlist, nil
 }
 
+func GetTvQueue(username, filter string, page int) (datastructure.TvQueue, error) {
+	tvQueue := datastructure.TvQueue{}
+	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "DB open err\n")
+		return tvQueue, err
+	}
+	defer db.Close()
+
+	if page <= 0 {
+		page = 1
+	}
+
+	var pages float64
+	row := db.QueryRow(`select count(*) from wantlist where item_type = 'tvserie' and username = ?`, username)
+	err = row.Scan(&pages)
+	if err != nil {
+		return tvQueue, err
+	}
+	tvQueue.Pages = int(math.Ceil(pages / 18))
+
+	offset := (page - 1) * 18
+
+	//todo filter, order by
+
+	var whereClause string
+	if filter != "" {
+		whereClause = "and c1.title like '%" + filter + "%'"
+	}
+
+	rows, err := db.Query(`select item_id, episodes_watched, cover, status, based_on, genres, title, year_start, year_end, substr(e.id, 1, pos-1) as series_id from wantlist w left join (select count(time) as episodes_watched, item_id as id, instr(item_id, '/') as pos from experiences where item_type = 'tvserie' and time != '0001-01-01 00:00:00+00:00' group by substr(id, 1, pos-1)) e on item_id = series_id natural join item_cache c where item_type = 'tvserie' and username = ? `+whereClause+` order by title limit ?,18`, username, offset)
+
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Select err: %v\n", err)
+		return tvQueue, err
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var (
+			entry            datastructure.TvQueueEntry
+			episodes_watched *int
+			series_id        *int
+		)
+		err := rows.Scan(&entry.Id, &episodes_watched, &entry.Cover, &entry.Status, &entry.BasedOn, &entry.Genres, &entry.Title, &entry.YearStart, &entry.YearEnd, &series_id)
+		if err != nil {
+			fmt.Println("Scan error")
+			return datastructure.TvQueue{}, err
+		}
+		if episodes_watched == nil {
+			entry.WatchedEpisodes = 0
+		} else {
+			entry.WatchedEpisodes = *episodes_watched
+		}
+
+		tvQueue.List = append(tvQueue.List, entry)
+	}
+
+	return tvQueue, nil
+}
+
 func GetUserExperiences(username, filter string, page int) (datastructure.Experiences, error) {
 	experiences := datastructure.Experiences{}
 	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")




diff --git a/front/capnproto.go b/front/capnproto.go
index b2656f82d2d1f393f25a434b94740ce0a079814e..d9459f01145f2671e0533b71a7297dde5baf414a 100644
--- a/front/capnproto.go
+++ b/front/capnproto.go
@@ -59,6 +59,10 @@ func (CapnprotoRenderer) RenderWatchlist(watchlist datastructure.Watchlist, languages []language.Tag) string {
 	return TODO("implement CapnprotoRenderer.RenderWatchlist").(string)
 }
 
+func (CapnprotoRenderer) RenderTvQueue(watchlist datastructure.TvQueue, languages []language.Tag) string {
+	return TODO("implement CapnprotoRenderer.RenderTvQueue").(string)
+}
+
 func (CapnprotoRenderer) RenderExperiences(experiences datastructure.Experiences, languages []language.Tag) string {
 	return TODO("implement CapnprotoRenderer.RenderExperiences").(string)
 }




diff --git a/front/html.go b/front/html.go
index 3e96480f9dd37dba02c8c689e0f56c57233968ea..032616129d9def0d580a21cca672f9dbdc0b3191 100644
--- a/front/html.go
+++ b/front/html.go
@@ -144,6 +144,12 @@ 	data.State.User = r.user
 	return render(languages, data, "watchlist")
 }
 
+func (r HtmlRenderer) RenderTvQueue(tvqueue datastructure.TvQueue, languages []language.Tag) string {
+	data := RenderData{Data: tvqueue}
+	data.State.User = r.user
+	return render(languages, data, "tvqueue")
+}
+
 func (r HtmlRenderer) RenderExperiences(experiences datastructure.Experiences, languages []language.Tag) string {
 	data := RenderData{Data: experiences}
 	data.State.User = r.user




diff --git a/front/renderer.go b/front/renderer.go
index bb4b743bd5b287c25f4e50b395fe47ed12ed76f8..01a3bac20fd7100d29e9badbc41f0e74dd6d54af 100644
--- a/front/renderer.go
+++ b/front/renderer.go
@@ -29,6 +29,7 @@ 	RenderAbout([]language.Tag) string
 	RenderErrorPage(int, []language.Tag) string
 	RenderLogin([]language.Tag, error, string) string
 	RenderWatchlist(datastructure.Watchlist, []language.Tag) string
+	RenderTvQueue(datastructure.TvQueue, []language.Tag) string
 	RenderExperiences(datastructure.Experiences, []language.Tag) string
 }
 




diff --git a/libamuse/common.go b/libamuse/common.go
index ddd1dde94735b8d8668e09291b90603e38d5a8f7..031c57de1d09eb0d685f27d0d11a8171afa04acf 100644
--- a/libamuse/common.go
+++ b/libamuse/common.go
@@ -5,6 +5,7 @@ 	"notabug.org/apiote/amuse/accounts"
 	"notabug.org/apiote/amuse/front"
 	"notabug.org/apiote/amuse/tmdb"
 	"notabug.org/apiote/amuse/wikidata"
+	"notabug.org/apiote/amuse/datastructure"
 	"notabug.org/apiote/amuse/db"
 
 	"database/sql"
@@ -90,6 +91,16 @@ 	show := result.result.(tmdb.Show)
 	languages := result.languages
 	book, err := wikidata.GetBookByTmdb(data.id, languages[0].String())
 	show.AddBasedOn(book)
+	return gott.Tuple(args), err
+}
+
+func getGenres(args ...interface{}) (interface{}, error) {
+	result := args[1].(*Result)
+	list := result.result.(datastructure.List)
+	genres, err := tmdb.GetGenres(result.languages[0].String(), list.GetType())
+	list.SetGenres(genres)
+	result.result = list
+	
 	return gott.Tuple(args), err
 }
 




diff --git a/libamuse/tvqueue.go b/libamuse/tvqueue.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd0e5913f37efbddac4bdc3eea4ba35e5fec0856
--- /dev/null
+++ b/libamuse/tvqueue.go
@@ -0,0 +1,55 @@
+package libamuse
+
+import (
+	"notabug.org/apiote/amuse/accounts"
+	"notabug.org/apiote/amuse/datastructure"
+	"notabug.org/apiote/amuse/db"
+
+	"notabug.org/apiote/gott"
+)
+
+func getTvQueue(args ...interface{}) (interface{}, error) {
+	request := args[0].(*RequestData)
+	result := args[1].(*Result)
+	page := args[2].(int)
+	tvQueue, err := db.GetTvQueue(result.user.Username, request.id, page)
+	result.result = &tvQueue
+
+	return gott.Tuple(args), err
+}
+
+func renderTvQueue(args ...interface{}) interface{} {
+	request := args[0].(*RequestData)
+	result := args[1].(*Result)
+	page := args[2].(int)
+	tvQueue := result.result.(*datastructure.TvQueue)
+	tvQueue.Page = page
+	tvQueue.Query = request.id
+	result.page = result.renderer.RenderTvQueue(*tvQueue, result.languages)
+
+	return gott.Tuple(args)
+}
+
+func ShowTvQueue(username string, auth accounts.Authentication, languages, mimetype, filter string, page int) (string, error) {
+	auth.Necessary = true
+	if page <= 0 {
+		page = 1
+	}
+	request := &RequestData{id: filter, language: languages, mimetype: mimetype, auth: auth, username: username}
+	r, err := gott.
+		NewResult(gott.Tuple{request, &Result{}, page}).
+		Bind(parseLanguage).
+		Bind(verifyToken).
+		Bind(verifyUser).
+		Bind(getTvQueue).
+		Bind(getGenres).
+		Bind(createRenderer).
+		Map(renderTvQueue).
+		Finish()
+
+	if err != nil {
+		return "", err
+	} else {
+		return r.(gott.Tuple)[1].(*Result).page, nil
+	}
+}




diff --git a/libamuse/watchlist.go b/libamuse/watchlist.go
index cd8e9bc9d161e4b80d7958c77a66c0ac0fd9ca52..31eb870d150a2cc732c856e61e1e07b3b507a64e 100644
--- a/libamuse/watchlist.go
+++ b/libamuse/watchlist.go
@@ -2,7 +2,6 @@ package libamuse
 
 import (
 	"notabug.org/apiote/amuse/accounts"
-	"notabug.org/apiote/amuse/tmdb"
 	"notabug.org/apiote/amuse/db"
 	"notabug.org/apiote/amuse/datastructure"
 
@@ -14,7 +13,7 @@ 	request := args[0].(*RequestData)
 	result := args[1].(*Result)
 	page := args[2].(int)
 	watchlist, err := db.GetWatchlist(result.user.Username, request.id, page)
-	result.result = watchlist
+	result.result = &watchlist
 
 	return gott.Tuple(args), err
 }
@@ -23,21 +22,12 @@ func renderWatchlist(args ...interface{}) interface{} {
 	request := args[0].(*RequestData)
 	result := args[1].(*Result)
 	page := args[2].(int)
-	watchlist := result.result.(datastructure.Watchlist)
+	watchlist := result.result.(*datastructure.Watchlist)
 	watchlist.Page = page
 	watchlist.Query = request.id
-	result.page = result.renderer.RenderWatchlist(watchlist, result.languages)
+	result.page = result.renderer.RenderWatchlist(*watchlist, result.languages)
 
 	return gott.Tuple(args)
-}
-
-func getGenres(args ...interface{}) (interface{}, error) {
-	result := args[1].(*Result)
-	watchlist := result.result.(datastructure.Watchlist)
-	genres, err := tmdb.GetGenres(result.languages[0].String())
-	watchlist.Genres = genres
-	result.result = watchlist
-	return gott.Tuple(args), err
 }
 
 func ShowWatchlist(username string, auth accounts.Authentication, languages, mimetype, filter string, page int) (string, error) {




diff --git a/router.go b/router.go
index d395cf8677d7a01d89895e384a4860e7766f7a65..dbac71c674fb4f2da21e735fbd8666940a9e4ed8 100644
--- a/router.go
+++ b/router.go
@@ -301,7 +301,12 @@ }
 
 func userTvQueue(w http.ResponseWriter, r *http.Request, username string, auth accounts.Authentication, acceptLanguages string, mimetype string) {
 	if r.Method == "" || r.Method == "GET" {
-		// todo
+		var page int
+		r.ParseForm()
+		filter := r.Form.Get("filter")
+		fmt.Sscanf(r.Form.Get("page"), "%d", &page)
+		tvQueue, err := libamuse.ShowTvQueue(username, auth, acceptLanguages, mimetype, filter, page)
+		render(tvQueue, err, w, acceptLanguages, mimetype)
 	} else if r.Method == "POST" {
 		r.ParseForm()
 		itemId := r.PostForm.Get("itemId")




diff --git a/templates/about.html b/templates/about.html
index 6a09a2715f9cac8037ee89813c3d170432954f90..77a7f843ff30b430a39b9ea700c37b5fe89c7e49 100644
--- a/templates/about.html
+++ b/templates/about.html
@@ -27,6 +27,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/book.html b/templates/book.html
index 3d78c812a5ca4d79bf1912e0d4f83ea42133c568..ef7e73ae1536248e5bc0426972541c847a49c42a 100644
--- a/templates/book.html
+++ b/templates/book.html
@@ -30,6 +30,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg-primary align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/bookserie.html b/templates/bookserie.html
index 3d0f4f77f025bf8ce0d771e2b043e83b309caf3d..856888c72f6655087e4960760becfe9d52e9d847 100644
--- a/templates/bookserie.html
+++ b/templates/bookserie.html
@@ -30,6 +30,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg-primary align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/experiences.html b/templates/experiences.html
index b5b07dce0f2cbf4aaf298ccc2a3602ab33dd423c..75d865345e148e260d2f8205a5daf007f8a0b789 100644
--- a/templates/experiences.html
+++ b/templates/experiences.html
@@ -22,6 +22,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/film.html b/templates/film.html
index 4c80b2dc6b51490f8d5e6379ab8e618a6e5b1035..400ff9bb786f84f442db4f14c8dd8ff886b5f71e 100644
--- a/templates/film.html
+++ b/templates/film.html
@@ -30,6 +30,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg-primary align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/index.html b/templates/index.html
index b8607a8b13412deef398b82600bc00cdb0761353..4f4a98a3a4db47089728c41f960bbee5822866b8 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -27,6 +27,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/person.html b/templates/person.html
index 9717f75edfbbf3ecb35b7cedd9f5b0e02618c78f..a847f7e8865f2dfbf7eeb4c0900f0159cf819d06 100644
--- a/templates/person.html
+++ b/templates/person.html
@@ -30,6 +30,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg-primary align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/search.html b/templates/search.html
index b76e40f7a0fc87c6dc3af3bebd88db8b0f7d252f..2051654f261558d87b593a2b70a7e091fc89f09a 100644
--- a/templates/search.html
+++ b/templates/search.html
@@ -30,6 +30,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/tvqueue.html b/templates/tvqueue.html
new file mode 100644
index 0000000000000000000000000000000000000000..6549f900da3a9e2cbdc079c0145ad5ae1ba3e5c5
--- /dev/null
+++ b/templates/tvqueue.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
+		<title>{{.State.User.Username}}’s TV queue — a·muse</title>
+		<link rel="stylesheet" href="/static/style/style.css" />
+		<link rel="icon" type="image/svg+xml" href="/static/img/logo.svg">
+		<link rel="apple-touch-icon" type="image/svg+xml" href="/static/img/logo.svg">
+	</head>
+	<body>
+		<header class="w12 padding-bottom-_25 flex flex-row flex-justify-space flex-align-centre">
+			<a href="/" class="decoration-none">
+				<h1 class="inline valign-mid text sans margin-lr-1">a·muse</h1>
+			</a>
+			<div class="margin-lr-1 text">
+				<nav>
+					<label for="hamburger" class="cursor-hand">
+						<img src="/users/{{.State.User.Username}}/avatar?size=small" class="border-radius-25 width-1_5"/>
+					</label>
+					<input type="checkbox" id="hamburger" class="display-none" />
+					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg align-right list-style-none sans">
+						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
+						<li class="bg-error">
+							<form action="/users/{{.State.User.Username}}/sessions/{{.State.User.Session}}" method="POST" class="inline">
+								<input type="hidden" value="DELETE" name="method" />
+								<input type="submit" value="Log out" class="border-none bg-none font-normal text-accent padding-lr-0 cursor-hand font-1" />
+							</form><span class="material-icon padding-lr-_5">&#xe7ff;</span>
+						</li>
+					</ul>
+				</nav>
+			</div>
+		</header>
+		<main class="margin-lr-1">
+			<!-- search, filter, order -->
+			<div class="flex flex-row flex-wrap flex-centre flex-align-start margin-top-1">
+				<form method="GET" class="flex inline margin-lr-1 border-bottom">
+					<input type="search" name="filter" class="border-none bg-none sans text" placeholder="filter TV queue" value="{{.Data.Query}}" />
+				</form>
+			</div>
+			<div class="flex flex-row flex-wrap flex-justify-space flex-align-start margin-top-1">
+				<div>
+					{{if gt .Data.Page 1}}
+					<a href="/users/{{.State.User.Username}}/tvqueue?filter={{.Data.Query}}&page={{.Data.PrevPage}}" class="decoration-none" title="{{.Strings.Search.prev_link_title}}"><span class="material-icon font-2">&#xe408;</span></a>
+					{{end}}
+				</div>
+				<div>
+					{{if lt .Data.Page .Data.Pages}}
+					<a href="/users/{{.State.User.Username}}/tvqueue?filter={{.Data.Query}}&page={{.Data.NextPage}}" class="decoration-none" title="{{.Strings.Search.next_link_title}}"><span class="material-icon font-2">&#xe409;</span></a>
+					{{end}}
+				</div>
+			</div>
+			<div class="flex flex-row flex-wrap flex-justify-space flex-align-start">
+				{{range .Data.List}}
+				<a href="/tvseries/{{.Id}}" class="decoration-none force-width-18 margin-tb-1 no-outline">
+					<div class="flex">
+						<div>
+							{{if .Cover}}
+							<img src="https://image.tmdb.org/t/p/w154{{.Cover}}" class="width-154px{{if .IsUnreleased "tvseries"}} bw{{end}}" />
+							{{else}}
+							<img src="/static/img/tv_empty.webp" class="width-154px" />
+							{{end}}
+							<!-- todo progress -->
+						</div>
+						<div class="margin-lr-1">
+							<p class="sans">{{.Title}}</p>
+							<p class="sans font-_875 text-grey">{{.GetYears}}</p>
+							<p class="sans font-_875">
+								{{.GetGenres $.Data.Genres}}
+							</p>
+							<p class="sans font-_875 text-grey">Watched episodes: {{.WatchedEpisodes}}</p>
+							<p class="font-_875 text-grey">
+								<!-- todo based on -->
+							</p>
+							<!-- todo if last episode < week old then NEW -->
+						</div>
+					</div>
+				</a>
+				{{end}}
+			</div>
+			<div class="flex flex-row flex-wrap flex-justify-space flex-align-start margin-top-1">
+				<div>
+					{{if gt .Data.Page 1}}
+					<a href="/users/{{.State.User.Username}}/tvqueue?filter={{.Data.Query}}&page={{.Data.PrevPage}}" class="decoration-none" title="{{.Strings.Search.prev_link_title}}"><span class="material-icon font-2">&#xe408;</span></a>
+					{{end}}
+				</div>
+				<div>
+					{{if lt .Data.Page .Data.Pages}}
+					<a href="/users/{{.State.User.Username}}/tvqueue?filter={{.Data.Query}}&page={{.Data.NextPage}}" class="decoration-none" title="{{.Strings.Search.next_link_title}}"><span class="material-icon font-2">&#xe409;</span></a>
+					{{end}}
+				</div>
+			</div>
+		</main>
+	</body>
+</html>




diff --git a/templates/tvserie.html b/templates/tvserie.html
index 9a93ff9c763a9025c513cd8553cddacb5262df06..103d2befe076409c819d7f63e404d2434f99aab3 100644
--- a/templates/tvserie.html
+++ b/templates/tvserie.html
@@ -30,6 +30,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg-primary align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">




diff --git a/templates/watchlist.html b/templates/watchlist.html
index d65dcfae4dc3ffcfe0f6e600c17f9da9bd75089d..09551dc1d3aa446ba9ebb1a1d2056cb9bc869bac 100644
--- a/templates/watchlist.html
+++ b/templates/watchlist.html
@@ -22,6 +22,7 @@ 					
 					<ul class="absolute right top-1 padding-lr-1 padding-tb-_5 bg align-right list-style-none sans">
 						<li><a href="/users/{{.State.User.Username}}" class="decoration-none text-accent">Profile</a><span class="material-icon padding-lr-_5">&#xe851;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/watchlist" class="decoration-none text-accent">Watchlist</a><span class="material-icon padding-lr-_5">&#xe04a;</span></li>
+						<li><a href="/users/{{.State.User.Username}}/tvqueue" class="decoration-none text-accent">TV Queue</a><span class="material-icon padding-lr-_5">&#xe1b2;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/readlist" class="decoration-none text-accent">Readlist</a><span class="material-icon padding-lr-_5">&#xe431;</span></li>
 						<li><a href="/users/{{.State.User.Username}}/experiences" class="decoration-none text-accent">Experiences</a><span class="material-icon padding-lr-_5">&#xe042;</span></li>
 						<li class="bg-error">
@@ -59,7 +60,7 @@ 				
 					<div class="flex">
 						<div>
 							{{if .Cover}}
-							<img src="https://image.tmdb.org/t/p/w154{{.Cover}}" class="width-154px{{if .IsUnreleased}} bw{{end}}" />
+							<img src="https://image.tmdb.org/t/p/w154{{.Cover}}" class="width-154px{{if .IsUnreleased "film"}} bw{{end}}" />
 							{{else}}
 							<img src="/static/img/poster_empty.webp" class="width-154px" />
 							{{end}}




diff --git a/tmdb/genres.go b/tmdb/genres.go
index d2c049e5fc9fe2e8b1a4e360bf326cb9231be43e..5de0e5f0f20b1f481a36b90c6871b1f59e1c2961 100644
--- a/tmdb/genres.go
+++ b/tmdb/genres.go
@@ -1,9 +1,11 @@
 package tmdb
 
 import (
+	"notabug.org/apiote/amuse/datastructure"
 	"notabug.org/apiote/amuse/network"
 
 	"encoding/json"
+	"errors"
 	"net/http"
 
 	"notabug.org/apiote/gott"
@@ -20,7 +22,17 @@ func createGenresRequest(args ...interface{}) (interface{}, error) {
 	request := args[0].(*network.Request)
 	result := args[1].(*network.Result)
 	result.Client = &http.Client{}
-	httpRequest, err := http.NewRequest("GET", "https://api.themoviedb.org/3/genre/movie/list?api_key="+API_KEY+"&language="+request.Language, nil)
+
+	var itemType string
+	if request.Id == datastructure.ItemTypeFilm {
+		itemType = "movie"
+	} else if request.Id == datastructure.ItemTypeTvserie {
+		itemType = "tv"
+	} else {
+		return gott.Tuple(args), errors.New("Wrong itemType: " + request.Id)
+	}
+
+	httpRequest, err := http.NewRequest("GET", "https://api.themoviedb.org/3/genre/"+itemType+"/list?api_key="+API_KEY+"&language="+request.Language, nil)
 	result.Request = httpRequest
 	return gott.Tuple(args), err
 }
@@ -37,14 +49,14 @@ 	genreMap := map[int]string{}
 	for _, genre := range genres.Genres {
 		genreMap[genre.Id] = genre.Name
 	}
-	
+
 	result.Result = genreMap
 	return gott.Tuple(args), nil
 }
 
-func GetGenres(language string) (map[int]string, error) {
+func GetGenres(language string, itemType datastructure.ItemType) (map[int]string, error) {
 	genres, err := gott.
-		NewResult(gott.Tuple{&network.Request{Language: language}, &network.Result{}}).
+		NewResult(gott.Tuple{&network.Request{Id: string(itemType), Language: language}, &network.Result{}}).
 		Bind(createGenresRequest).
 		Bind(getCacheEntry).
 		Map(network.AddHeaders).




diff --git a/tmdb/serie.go b/tmdb/serie.go
index f7841897b85fb0ceab32e85134623e0ad84b5894..c196e66ce9aa4994b63cc563bf33336d5dd576e0 100644
--- a/tmdb/serie.go
+++ b/tmdb/serie.go
@@ -60,7 +60,7 @@ 	Last_air_date_str     string `json:"last_air_date"`
 	Last_air_date         time.Time
 	Last_episode_to_air   Episode `json:"last_episode_to_air"`
 	Name                  string
-	Number_of_episodes    int    `json:"number_of_episodes"`
+	Number_of_episodes    int    `json:"number_of_episodes"`  // todo to item_cache and to calculating
 	Original_name         string `json:"original_name"`
 	Overview              string
 	Poster_path           string `json:"poster_path"`