package db import ( "database/sql" "errors" "fmt" "strconv" "strings" "time" "github.com/akhilrex/podgrab/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) func GetPodcastByURL(url string, podcast *Podcast) error { result := DB.Preload(clause.Associations).Where(&Podcast{URL: url}).First(&podcast) return result.Error } func GetPodcastsByURLList(urls []string, podcasts *[]Podcast) error { result := DB.Preload(clause.Associations).Where("url in ?", urls).First(&podcasts) return result.Error } func GetAllPodcasts(podcasts *[]Podcast, sorting string) error { if sorting == "" { sorting = "created_at" } result := DB.Preload("Tags").Order(sorting).Find(&podcasts) return result.Error } func GetAllPodcastItems(podcasts *[]PodcastItem) error { result := DB.Preload("Podcast").Order("pub_date desc").Find(&podcasts) return result.Error } func GetAllPodcastItemsWithoutSize() (*[]PodcastItem, error) { var podcasts []PodcastItem result := DB.Where("file_size<=?", 0).Order("pub_date desc").Find(&podcasts) return &podcasts, result.Error } func getSortOrder(sorting model.EpisodeSort) string { switch sorting { case model.RELEASE_ASC: return "pub_date asc" case model.RELEASE_DESC: return "pub_date desc" case model.DURATION_ASC: return "duration asc" case model.DURATION_DESC: return "duration desc" default: return "pub_date desc" } } func GetPaginatedPodcastItemsNew(queryModel model.EpisodesFilter) (*[]PodcastItem, int64, error) { var podcasts []PodcastItem var total int64 query := DB.Debug().Preload("Podcast") if queryModel.IsDownloaded != nil { isDownloaded, err := strconv.ParseBool(*queryModel.IsDownloaded) if err == nil { if isDownloaded { query = query.Where("download_status=?", Downloaded) } else { query = query.Where("download_status!=?", Downloaded) } } } if queryModel.IsPlayed != nil { isPlayed, err := strconv.ParseBool(*queryModel.IsPlayed) if err == nil { if isPlayed { query = query.Where("is_played=?", 1) } else { query = query.Where("is_played=?", 0) } } } if queryModel.Q != "" { query = query.Where("UPPER(title) like ?", "%"+strings.TrimSpace(strings.ToUpper(queryModel.Q))+"%") } if len(queryModel.TagIds) > 0 { query = query.Where("podcast_id in (select podcast_id from podcast_tags where tag_id in ?)", queryModel.TagIds) } if len(queryModel.PodcastIds) > 0 { query = query.Where("podcast_id in ?", queryModel.PodcastIds) } totalsQuery := query.Order(getSortOrder(queryModel.Sorting)).Find(&podcasts) totalsQuery.Count(&total) result := query.Limit(queryModel.Count).Offset((queryModel.Page - 1) * queryModel.Count).Order("pub_date desc").Find(&podcasts) return &podcasts, total, result.Error } func GetPaginatedPodcastItems(page int, count int, downloadedOnly *bool, playedOnly *bool, fromDate time.Time, podcasts *[]PodcastItem, total *int64) error { query := DB.Preload("Podcast") if downloadedOnly != nil { if *downloadedOnly { query = query.Where("download_status=?", Downloaded) } else { query = query.Where("download_status!=?", Downloaded) } } if playedOnly != nil { if *playedOnly { query = query.Where("is_played=?", 1) } else { query = query.Where("is_played=?", 0) } } if (fromDate != time.Time{}) { query = query.Where("pub_date>=?", fromDate) } totalsQuery := query.Order("pub_date desc").Find(&podcasts) totalsQuery.Count(total) result := query.Limit(count).Offset((page - 1) * count).Order("pub_date desc").Find(&podcasts) return result.Error } func GetPaginatedTags(page int, count int, tags *[]Tag, total *int64) error { query := DB.Preload("Podcasts") result := query.Limit(count).Offset((page - 1) * count).Order("created_at desc").Find(&tags) query.Count(total) return result.Error } func GetPodcastById(id string, podcast *Podcast) error { result := DB.Preload("PodcastItems", func(db *gorm.DB) *gorm.DB { return db.Order("podcast_items.pub_date DESC") }).First(&podcast, "id=?", id) return result.Error } func GetPodcastItemById(id string, podcastItem *PodcastItem) error { result := DB.Preload(clause.Associations).First(&podcastItem, "id=?", id) return result.Error } func DeletePodcastItemById(id string) error { result := DB.Where("id=?", id).Delete(&PodcastItem{}) return result.Error } func DeletePodcastById(id string) error { result := DB.Where("id=?", id).Delete(&Podcast{}) return result.Error } func DeleteTagById(id string) error { result := DB.Where("id=?", id).Delete(&Tag{}) return result.Error } func GetAllPodcastItemsByPodcastId(podcastId string, podcastItems *[]PodcastItem) error { result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId}).Find(&podcastItems) return result.Error } func GetAllPodcastItemsByPodcastIds(podcastIds []string, podcastItems *[]PodcastItem) error { result := DB.Preload(clause.Associations).Where("podcast_id in ?", podcastIds).Order("pub_date desc").Find(&podcastItems) return result.Error } func GetAllPodcastItemsByIds(podcastItemIds []string) (*[]PodcastItem, error) { var podcastItems []PodcastItem var sb strings.Builder sb.WriteString("\n CASE ID \n") for i, v := range podcastItemIds { sb.WriteString(fmt.Sprintf("WHEN '%v' THEN %v \n", v, i+1)) } sb.WriteString(fmt.Sprintln("END")) result := DB.Debug().Preload(clause.Associations).Where("id in ?", podcastItemIds).Order(sb.String()).Find(&podcastItems) return &podcastItems, result.Error } func SetAllEpisodesToDownload(podcastId string) error { result := DB.Model(PodcastItem{}).Where(&PodcastItem{PodcastID: podcastId, DownloadStatus: Deleted}).Update("download_status", NotDownloaded) return result.Error } func UpdateLastEpisodeDateForPodcast(podcastId string, lastEpisode time.Time) error { result := DB.Model(Podcast{}).Where("id=?", podcastId).Update("last_episode", lastEpisode) return result.Error } func UpdatePodcastItemFileSize(podcastItemId string, size int64) error { result := DB.Model(PodcastItem{}).Where("id=?", podcastItemId).Update("file_size", size) return result.Error } func GetAllPodcastItemsWithoutImage() (*[]PodcastItem, error) { var podcastItems []PodcastItem result := DB.Preload(clause.Associations).Where("local_image is ?", nil).Where("image != ?", "").Where("download_status=?", Downloaded).Order("created_at desc").Find(&podcastItems) //fmt.Println("To be downloaded : " + string(len(podcastItems))) return &podcastItems, result.Error } func GetAllPodcastItemsToBeDownloaded() (*[]PodcastItem, error) { var podcastItems []PodcastItem result := DB.Preload(clause.Associations).Where("download_status=?", NotDownloaded).Find(&podcastItems) //fmt.Println("To be downloaded : " + string(len(podcastItems))) return &podcastItems, result.Error } func GetAllPodcastItemsAlreadyDownloaded() (*[]PodcastItem, error) { var podcastItems []PodcastItem result := DB.Preload(clause.Associations).Where("download_status=?", Downloaded).Find(&podcastItems) return &podcastItems, result.Error } func GetPodcastEpisodeStats() (*[]PodcastItemStatsModel, error) { var stats []PodcastItemStatsModel result := DB.Model(&PodcastItem{}).Select("download_status,podcast_id, count(1) as count,sum(file_size) as size").Group("podcast_id,download_status").Find(&stats) return &stats, result.Error } func GetPodcastEpisodeDiskStats() (PodcastItemConsolidateDiskStatsModel, error) { var stats []PodcastItemDiskStatsModel result := DB.Model(&PodcastItem{}).Select("download_status,count(1) as count,sum(file_size) as size").Group("download_status").Find(&stats) dict := make(map[DownloadStatus]int64) for _, stat := range stats { dict[stat.DownloadStatus] = stat.Size } toReturn := PodcastItemConsolidateDiskStatsModel{ Downloaded: dict[Downloaded], Downloading: dict[Downloading], Deleted: dict[Deleted], NotDownloaded: dict[NotDownloaded], PendingDownload: dict[NotDownloaded] + dict[Downloading], } return toReturn, result.Error } func GetEpisodeNumber(podcastItemId, podcastId string) (int, error) { var id string var sequence int row := DB.Raw(`;With cte as( SELECT id, RANK() OVER (ORDER BY pub_date) as sequence FROM podcast_items WHERE podcast_id=? ) select * from cte where id = ? `, podcastId, podcastItemId).Row() error := row.Scan(&id, &sequence) return sequence, 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) { var podcastItems []PodcastItem result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId}).Where("guid IN ?", guids).Find(&podcastItems) return &podcastItems, result.Error } func GetPodcastItemByPodcastIdAndGUID(podcastId string, guid string, podcastItem *PodcastItem) error { result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId, GUID: guid}).First(&podcastItem) return result.Error } func GetPodcastByTitleAndAuthor(title string, author string, podcast *Podcast) error { result := DB.Preload(clause.Associations).Where(&Podcast{Title: title, Author: author}).First(&podcast) return result.Error } func CreatePodcast(podcast *Podcast) error { tx := DB.Create(&podcast) return tx.Error } func CreatePodcastItem(podcastItem *PodcastItem) error { tx := DB.Omit("Podcast").Create(&podcastItem) return tx.Error } func UpdatePodcastItem(podcastItem *PodcastItem) error { tx := DB.Omit("Podcast").Save(&podcastItem) return tx.Error } func UpdateSettings(setting *Setting) error { tx := DB.Save(&setting) return tx.Error } func GetOrCreateSetting() *Setting { var setting Setting result := DB.First(&setting) if errors.Is(result.Error, gorm.ErrRecordNotFound) { DB.Save(&Setting{}) DB.First(&setting) } return &setting } func GetLock(name string) *JobLock { var jobLock JobLock result := DB.Where("name = ?", name).First(&jobLock) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return &JobLock{ Name: name, } } return &jobLock } func Lock(name string, duration int) { jobLock := GetLock(name) if jobLock == nil { jobLock = &JobLock{ Name: name, } } jobLock.Duration = duration jobLock.Date = time.Now() if jobLock.ID == "" { DB.Create(&jobLock) } else { DB.Save(&jobLock) } } func Unlock(name string) { jobLock := GetLock(name) if jobLock == nil { return } jobLock.Duration = 0 jobLock.Date = time.Time{} DB.Save(&jobLock) } func UnlockMissedJobs() { var jobLocks []JobLock result := DB.Find(&jobLocks) if result.Error != nil { return } for _, job := range jobLocks { if (job.Date == time.Time{}) { continue } var duration time.Duration duration = time.Duration(job.Duration) d := job.Date.Add(time.Minute * duration) if d.Before(time.Now()) { fmt.Println(job.Name + " is unlocked") Unlock(job.Name) } } } func GetAllTags(sorting string) (*[]Tag, error) { var tags []Tag if sorting == "" { sorting = "created_at" } result := DB.Preload(clause.Associations).Order(sorting).Find(&tags) return &tags, result.Error } func GetTagById(id string) (*Tag, error) { var tag Tag result := DB.Preload(clause.Associations). First(&tag, "id=?", id) return &tag, result.Error } func GetTagsByIds(ids []string) (*[]Tag, error) { var tag []Tag result := DB.Preload(clause.Associations).Where("id in ?", ids).Find(&tag) return &tag, result.Error } func GetTagByLabel(label string) (*Tag, error) { var tag Tag result := DB.Preload(clause.Associations). First(&tag, "label=?", label) return &tag, result.Error } func CreateTag(tag *Tag) error { tx := DB.Omit("Podcasts").Create(&tag) return tx.Error } func UpdateTag(tag *Tag) error { tx := DB.Omit("Podcast").Save(&tag) return tx.Error } func AddTagToPodcast(id, tagId string) error { tx := DB.Exec("INSERT INTO `podcast_tags` (`podcast_id`,`tag_id`) VALUES (?,?) ON CONFLICT DO NOTHING", id, tagId) return tx.Error } func RemoveTagFromPodcast(id, tagId string) error { tx := DB.Exec("DELETE FROM `podcast_tags` WHERE `podcast_id`=? AND `tag_id`=?", id, tagId) return tx.Error } func UntagAllByTagId(tagId string) error { tx := DB.Exec("DELETE FROM `podcast_tags` WHERE `tag_id`=?", tagId) return tx.Error }