optimize home page query & paginte podcast listing

pull/18/merge
Akhil Gupta 4 years ago
parent 50ec6deff2
commit eb96373e4e

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PodGrab</title> <title>{{.title}} - PodGrab</title>
{{template "commoncss"}} {{template "commoncss"}}
<style> <style>
img { img {

@ -68,9 +68,11 @@
<div class="row"> <div class="row">
<div class="columns four"> <div class="columns four">
Last Ep : {{ latestEpisodeDate .PodcastItems}} {{if .LastEpisode}}
Last Ep : {{ formatDate .LastEpisode }}
{{end}}
</div> </div>
{{$downloading:=downloadingEpisodes .PodcastItems}} {{$downloading:= .DownloadingEpisodesCount}}
<div <div
class="columns five" class="columns five"
title="{{downloadedEpisodes .PodcastItems}} episodes downloaded out of total {{ len .PodcastItems}} episodes. title="{{downloadedEpisodes .PodcastItems}} episodes downloaded out of total {{ len .PodcastItems}} episodes.
@ -78,7 +80,7 @@
" "
> >
{{if gt $downloading 0}} ({{$downloading}})/{{end}}{{downloadedEpisodes .PodcastItems}}/{{ len .PodcastItems }} episodes {{if gt $downloading 0}} ({{$downloading}})/{{end}}{{.DownloadedEpisodesCount}}/{{.AllEpisodesCount}} episodes
</div> </div>
<div class="columns three" style=""> <div class="columns three" style="">

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" integrity="sha512-EZLkOqwILORob+p0BXZc+Vm3RgJBOe1Iq/0fiI7r/wJgzOFZMlsqTa29UEl6v6U6gsV4uIpsNZoV32YZqrCRCQ==" crossorigin="anonymous" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css" integrity="sha512-EZLkOqwILORob+p0BXZc+Vm3RgJBOe1Iq/0fiI7r/wJgzOFZMlsqTa29UEl6v6U6gsV4uIpsNZoV32YZqrCRCQ==" crossorigin="anonymous" />
<title>PodGrab</title> <title>Episodes - PodGrab</title>
<style> <style>
.container { .container {

@ -32,7 +32,7 @@ func AddPage(c *gin.Context) {
} }
func HomePage(c *gin.Context) { func HomePage(c *gin.Context) {
//var podcasts []db.Podcast //var podcasts []db.Podcast
podcasts := service.GetAllPodcasts() podcasts := service.GetAllPodcasts("")
c.HTML(http.StatusOK, "index.html", gin.H{"title": "Podgrab", "podcasts": podcasts}) c.HTML(http.StatusOK, "index.html", gin.H{"title": "Podgrab", "podcasts": podcasts})
} }
func PodcastPage(c *gin.Context) { func PodcastPage(c *gin.Context) {
@ -42,19 +42,46 @@ func PodcastPage(c *gin.Context) {
var podcast db.Podcast var podcast db.Podcast
if err := db.GetPodcastById(searchByIdQuery.Id, &podcast); err == nil { if err := db.GetPodcastById(searchByIdQuery.Id, &podcast); err == nil {
setting := c.MustGet("setting").(*db.Setting) var pagination Pagination
c.HTML(http.StatusOK, "episodes.html", gin.H{ if c.ShouldBindQuery(&pagination) == nil {
"title": podcast.Title, var page, count int
"podcastItems": podcast.PodcastItems, if page = pagination.Page; page == 0 {
"setting": setting, page = 1
"page": 1, }
"count": 10, if count = pagination.Count; count == 0 {
"totalCount": len(podcast.PodcastItems), count = 10
"totalPages": 0, }
"nextPage": 0, setting := c.MustGet("setting").(*db.Setting)
"previousPage": 0, totalCount := len(podcast.PodcastItems)
"downloadedOnly": false, totalPages := math.Ceil(float64(totalCount) / float64(count))
}) nextPage, previousPage := 0, 0
if float64(page) < totalPages {
nextPage = page + 1
}
if page > 1 {
previousPage = page - 1
}
from := (page - 1) * count
to := page * count
if to > totalCount {
to = totalCount
}
c.HTML(http.StatusOK, "episodes.html", gin.H{
"title": podcast.Title,
"podcastItems": podcast.PodcastItems[from:to],
"setting": setting,
"page": page,
"count": count,
"totalCount": totalCount,
"totalPages": totalPages,
"nextPage": nextPage,
"previousPage": previousPage,
"downloadedOnly": false,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
}
} else { } else {
c.JSON(http.StatusBadRequest, err) c.JSON(http.StatusBadRequest, err)
} }
@ -151,7 +178,7 @@ func Search(c *gin.Context) {
if c.ShouldBindQuery(&searchQuery) == nil { if c.ShouldBindQuery(&searchQuery) == nil {
itunesService := new(service.ItunesService) itunesService := new(service.ItunesService)
data := itunesService.Query(searchQuery.Q) data := itunesService.Query(searchQuery.Q)
allPodcasts := service.GetAllPodcasts() allPodcasts := service.GetAllPodcasts("")
urls := make(map[string]string, len(*allPodcasts)) urls := make(map[string]string, len(*allPodcasts))
for _, pod := range *allPodcasts { for _, pod := range *allPodcasts {

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strings"
"github.com/akhilrex/podgrab/model" "github.com/akhilrex/podgrab/model"
"github.com/akhilrex/podgrab/service" "github.com/akhilrex/podgrab/service"
@ -12,11 +13,27 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
const (
DateAdded = "dateadded"
Name = "name"
LastEpisode = "lastepisode"
)
const (
Asc = "asc"
Desc = "desc"
)
type SearchQuery struct { type SearchQuery struct {
Q string `binding:"required" form:"q"` Q string `binding:"required" form:"q"`
Type string `form:"type"` Type string `form:"type"`
} }
type PodcastListQuery struct {
Sort string `uri:"sort" query:"sort" json:"sort" form:"sort" default:"created_at"`
Order string `uri:"order" query:"order" json:"order" form:"order" default:"asc"`
}
type SearchByIdQuery struct { type SearchByIdQuery struct {
Id string `binding:"required" uri:"id" json:"id" form:"id"` Id string `binding:"required" uri:"id" json:"id" form:"id"`
} }
@ -37,9 +54,25 @@ type AddPodcastData struct {
} }
func GetAllPodcasts(c *gin.Context) { func GetAllPodcasts(c *gin.Context) {
var podcasts []db.Podcast var podcastListQuery PodcastListQuery
db.GetAllPodcasts(&podcasts)
c.JSON(200, podcasts) if c.ShouldBindQuery(&podcastListQuery) == nil {
var order = strings.ToLower(podcastListQuery.Order)
var sorting = "created_at"
switch sort := strings.ToLower(podcastListQuery.Sort); sort {
case DateAdded:
sorting = "created_at"
case Name:
sorting = "title"
case LastEpisode:
sorting = "last_episode"
}
if order == Desc {
sorting = fmt.Sprintf("%s desc", sorting)
}
c.JSON(200, service.GetAllPodcasts(sorting))
}
} }
func GetPodcastById(c *gin.Context) { func GetPodcastById(c *gin.Context) {
@ -179,7 +212,6 @@ func DownloadPodcastItem(c *gin.Context) {
var searchByIdQuery SearchByIdQuery var searchByIdQuery SearchByIdQuery
if c.ShouldBindUri(&searchByIdQuery) == nil { if c.ShouldBindUri(&searchByIdQuery) == nil {
go service.DownloadSingleEpisode(searchByIdQuery.Id) go service.DownloadSingleEpisode(searchByIdQuery.Id)
c.JSON(200, gin.H{}) c.JSON(200, gin.H{})
} else { } else {

@ -1,6 +1,7 @@
package db package db
import ( import (
"database/sql"
"errors" "errors"
"fmt" "fmt"
"time" "time"
@ -18,9 +19,11 @@ func GetPodcastsByURLList(urls []string, podcasts *[]Podcast) error {
result := DB.Preload(clause.Associations).Where("url in ?", urls).First(&podcasts) result := DB.Preload(clause.Associations).Where("url in ?", urls).First(&podcasts)
return result.Error return result.Error
} }
func GetAllPodcasts(podcasts *[]Podcast) error { func GetAllPodcasts(podcasts *[]Podcast, sorting string) error {
if sorting == "" {
result := DB.Preload("PodcastItems").Find(&podcasts) sorting = "created_at"
}
result := DB.Debug().Order(sorting).Find(&podcasts)
return result.Error return result.Error
} }
func GetAllPodcastItems(podcasts *[]PodcastItem) error { func GetAllPodcastItems(podcasts *[]PodcastItem) error {
@ -74,6 +77,10 @@ func SetAllEpisodesToDownload(podcastId string) error {
result := DB.Debug().Model(PodcastItem{}).Where(&PodcastItem{PodcastID: podcastId, DownloadStatus: Deleted}).Update("download_status", NotDownloaded) result := DB.Debug().Model(PodcastItem{}).Where(&PodcastItem{PodcastID: podcastId, DownloadStatus: Deleted}).Update("download_status", NotDownloaded)
return result.Error return result.Error
} }
func UpdateLastEpisodeDateForPodcast(podcastId string, lastEpisode time.Time) error {
result := DB.Debug().Model(Podcast{}).Where("id=?", podcastId).Update("last_episode", lastEpisode)
return result.Error
}
func GetAllPodcastItemsToBeDownloaded() (*[]PodcastItem, error) { func GetAllPodcastItemsToBeDownloaded() (*[]PodcastItem, error) {
var podcastItems []PodcastItem var podcastItems []PodcastItem
@ -87,6 +94,16 @@ func GetAllPodcastItemsAlreadyDownloaded() (*[]PodcastItem, error) {
return &podcastItems, result.Error return &podcastItems, result.Error
} }
func GetPodcastEpisodeStats() (*[]PodcastItemStatsModel, error) {
var stats []PodcastItemStatsModel
result := DB.Model(&PodcastItem{}).Select("download_status,podcast_id, count(1) as count").Group("podcast_id,download_status").Find(&stats)
return &stats, result.Error
}
func ForceSetLastEpisodeDate(podcastId string) {
DB.Exec("update podcasts set last_episode = (select max(pi.pub_date) from podcast_items pi where pi.podcast_id = @id) where id = @id", sql.Named("id", podcastId))
}
func GetPodcastItemsByPodcastIdAndGUIDs(podcastId string, guids []string) (*[]PodcastItem, error) { func GetPodcastItemsByPodcastIdAndGUIDs(podcastId string, guids []string) (*[]PodcastItem, error) {
var podcastItems []PodcastItem var podcastItems []PodcastItem
result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId}).Where("guid IN ?", guids).Find(&podcastItems) result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId}).Where("guid IN ?", guids).Find(&podcastItems)

@ -17,7 +17,13 @@ type Podcast struct {
URL string URL string
PodcastItems []PodcastItem LastEpisode *time.Time
PodcastItems []PodcastItem `json:"-"`
DownloadedEpisodesCount int `gorm:"-"`
DownloadingEpisodesCount int `gorm:"-"`
AllEpisodesCount int `gorm:"-"`
} }
//PodcastItem is //PodcastItem is
@ -78,3 +84,9 @@ type JobLock struct {
func (lock *JobLock) IsLocked() bool { func (lock *JobLock) IsLocked() bool {
return lock != nil && lock.Date != time.Time{} return lock != nil && lock.Date != time.Time{}
} }
type PodcastItemStatsModel struct {
PodcastID string
DownloadStatus DownloadStatus
Count int
}

@ -35,6 +35,10 @@ func main() {
funcMap := template.FuncMap{ funcMap := template.FuncMap{
"formatDate": func(raw time.Time) string { "formatDate": func(raw time.Time) string {
if raw == (time.Time{}) {
return ""
}
return raw.Format("Jan 2 2006") return raw.Format("Jan 2 2006")
}, },
"naturalDate": func(raw time.Time) string { "naturalDate": func(raw time.Time) string {

@ -42,10 +42,28 @@ func FetchURL(url string) (model.PodcastData, error) {
err = xml.Unmarshal(body, &response) err = xml.Unmarshal(body, &response)
return response, err return response, err
} }
func GetAllPodcasts() *[]db.Podcast { func GetAllPodcasts(sorting string) *[]db.Podcast {
var podcasts []db.Podcast var podcasts []db.Podcast
db.GetAllPodcasts(&podcasts) db.GetAllPodcasts(&podcasts, sorting)
return &podcasts
stats, _ := db.GetPodcastEpisodeStats()
type Key struct {
PodcastID string
DownloadStatus db.DownloadStatus
}
statsMap := make(map[Key]int)
for _, stat := range *stats {
statsMap[Key{stat.PodcastID, stat.DownloadStatus}] = stat.Count
}
var toReturn []db.Podcast
for _, podcast := range podcasts {
podcast.DownloadedEpisodesCount = statsMap[Key{podcast.ID, db.Downloaded}]
podcast.DownloadingEpisodesCount = statsMap[Key{podcast.ID, db.NotDownloaded}]
podcast.AllEpisodesCount = podcast.DownloadedEpisodesCount + podcast.DownloadingEpisodesCount + statsMap[Key{podcast.ID, db.Deleted}]
toReturn = append(toReturn, podcast)
}
return &toReturn
} }
func AddOpml(content string) error { func AddOpml(content string) error {
@ -84,7 +102,7 @@ func AddOpml(content string) error {
} }
func ExportOmpl() (model.OpmlModel, error) { func ExportOmpl() (model.OpmlModel, error) {
podcasts := GetAllPodcasts() podcasts := GetAllPodcasts("")
var outlines []model.OpmlOutline var outlines []model.OpmlOutline
for _, podcast := range *podcasts { for _, podcast := range *podcasts {
toAdd := model.OpmlOutline{ toAdd := model.OpmlOutline{
@ -160,6 +178,7 @@ func AddPodcastItems(podcast *db.Podcast) error {
for _, item := range *existingItems { for _, item := range *existingItems {
keyMap[item.GUID] = 1 keyMap[item.GUID] = 1
} }
var latestDate = time.Time{}
for i := 0; i < len(data.Channel.Item); i++ { for i := 0; i < len(data.Channel.Item); i++ {
obj := data.Channel.Item[i] obj := data.Channel.Item[i]
@ -177,6 +196,10 @@ func AddPodcastItems(podcast *db.Podcast) error {
pubDate, _ = time.Parse(modifiedRFC1123, obj.PubDate) pubDate, _ = time.Parse(modifiedRFC1123, obj.PubDate)
} }
if latestDate.Before(pubDate) {
latestDate = pubDate
}
var downloadStatus db.DownloadStatus var downloadStatus db.DownloadStatus
if setting.AutoDownload { if setting.AutoDownload {
if i < limit { if i < limit {
@ -202,9 +225,23 @@ func AddPodcastItems(podcast *db.Podcast) error {
db.CreatePodcastItem(&podcastItem) db.CreatePodcastItem(&podcastItem)
} }
} }
if (latestDate != time.Time{}) {
db.UpdateLastEpisodeDateForPodcast(podcast.ID, latestDate)
}
return err return err
} }
func SetPodcastItemAsQueuedForDownload(id string) error {
var podcastItem db.PodcastItem
err := db.GetPodcastItemById(id, &podcastItem)
if err != nil {
return err
}
podcastItem.DownloadStatus = db.NotDownloaded
return db.UpdatePodcastItem(&podcastItem)
}
func SetPodcastItemAsDownloaded(id string, location string) error { func SetPodcastItemAsDownloaded(id string, location string) error {
var podcastItem db.PodcastItem var podcastItem db.PodcastItem
err := db.GetPodcastItemById(id, &podcastItem) err := db.GetPodcastItemById(id, &podcastItem)
@ -326,6 +363,7 @@ func DownloadSingleEpisode(podcastItemId string) error {
} }
setting := db.GetOrCreateSetting() setting := db.GetOrCreateSetting()
SetPodcastItemAsQueuedForDownload(podcastItemId)
url, err := Download(podcastItem.FileURL, podcastItem.Title, podcastItem.Podcast.Title, GetPodcastPrefix(&podcastItem, setting)) url, err := Download(podcastItem.FileURL, podcastItem.Title, podcastItem.Podcast.Title, GetPodcastPrefix(&podcastItem, setting))
if err != nil { if err != nil {
@ -336,14 +374,17 @@ func DownloadSingleEpisode(podcastItemId string) error {
func RefreshEpisodes() error { func RefreshEpisodes() error {
var data []db.Podcast var data []db.Podcast
err := db.GetAllPodcasts(&data) err := db.GetAllPodcasts(&data, "")
if err != nil { if err != nil {
return err return err
} }
for _, item := range data { for _, item := range data {
if item.LastEpisode == nil {
fmt.Println(item.Title)
db.ForceSetLastEpisodeDate(item.ID)
}
AddPodcastItems(&item) AddPodcastItems(&item)
} }
setting := db.GetOrCreateSetting() setting := db.GetOrCreateSetting()
if setting.AutoDownload { if setting.AutoDownload {

Loading…
Cancel
Save