Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Download Playlist #84

Merged
merged 13 commits into from
Jun 30, 2024
Merged
124 changes: 0 additions & 124 deletions app/cmd/seeder/seeder.go

This file was deleted.

5 changes: 4 additions & 1 deletion app/cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"dankmuzikk/handlers/pages"
"dankmuzikk/log"
"dankmuzikk/models"
"dankmuzikk/services/archive"
"dankmuzikk/services/history"
"dankmuzikk/services/jwt"
"dankmuzikk/services/login"
Expand Down Expand Up @@ -44,8 +45,9 @@ func StartServer(staticFS embed.FS) error {
historyRepo := db.NewBaseDB[models.History](dbConn)
playlistVotersRepo := db.NewBaseDB[models.PlaylistSongVoter](dbConn)

zipService := archive.NewService()
downloadService := download.New(songRepo)
playlistsService := playlists.New(playlistRepo, playlistOwnersRepo, playlistSongsRepo)
playlistsService := playlists.New(playlistRepo, playlistOwnersRepo, playlistSongsRepo, zipService)
songsService := songs.New(playlistSongsRepo, playlistOwnersRepo, songRepo, playlistRepo, playlistVotersRepo, downloadService)
historyService := history.New(historyRepo, songRepo)

Expand Down Expand Up @@ -112,6 +114,7 @@ func StartServer(staticFS embed.FS) error {
apisHandler.HandleFunc("PUT /playlist/public", gHandler.AuthApi(playlistsApi.HandleTogglePublicPlaylist))
apisHandler.HandleFunc("PUT /playlist/join", gHandler.AuthApi(playlistsApi.HandleToggleJoinPlaylist))
apisHandler.HandleFunc("DELETE /playlist", gHandler.AuthApi(playlistsApi.HandleDeletePlaylist))
apisHandler.HandleFunc("GET /playlist/zip", gHandler.AuthApi(playlistsApi.HandleDonwnloadPlaylist))
apisHandler.HandleFunc("GET /history/{page}", gHandler.AuthApi(historyApi.HandleGetMoreHistoryItems))

applicationHandler := http.NewServeMux()
Expand Down
25 changes: 25 additions & 0 deletions app/handlers/apis/playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"dankmuzikk/views/components/ui"
"dankmuzikk/views/pages"
"encoding/json"
"io"
"net/http"
)

Expand Down Expand Up @@ -208,3 +209,27 @@ func (p *playlistApi) HandleGetPlaylistsForPopover(w http.ResponseWriter, r *htt
playlist.PlaylistsSelector(songId, playlists, songsInPlaylists).
Render(r.Context(), w)
}

func (p *playlistApi) HandleDonwnloadPlaylist(w http.ResponseWriter, r *http.Request) {
profileId, profileIdCorrect := r.Context().Value(handlers.ProfileIdKey).(uint)
if !profileIdCorrect {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("🤷‍♂️"))
return
}

playlistId := r.URL.Query().Get("playlist-id")
if playlistId == "" {
w.WriteHeader(http.StatusBadRequest)
return
}

playlistZip, err := p.service.Download(playlistId, profileId)
if err != nil {
log.Errorln(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("🤷‍♂️"))
return
}
_, _ = io.Copy(w, playlistZip)
}
3 changes: 0 additions & 3 deletions app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"dankmuzikk/cmd/migrator"
"dankmuzikk/cmd/seeder"
"dankmuzikk/cmd/server"
"dankmuzikk/log"
"embed"
Expand All @@ -22,8 +21,6 @@ func main() {
err = server.StartServer(static)
case "migrate", "migration", "theotherthing":
err = migrator.Migrate()
case "seed", "seeder", "theotherotherthing":
err = seeder.SeedDb()
}
if err != nil {
log.Fatalln(log.ErrorLevel, err)
Expand Down
89 changes: 89 additions & 0 deletions app/services/archive/zip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package archive

import (
"archive/zip"
"bytes"
"io"
"os"
)

const tmpDir = "/tmp"

type Service struct{}

func NewService() *Service {
return &Service{}
}

func (z *Service) CreateZip() (Archive, error) {
zipFile, err := os.CreateTemp(tmpDir, "playlist_*.zip")
if err != nil {
return nil, err
}
return newZip(zipFile), nil
}

type Archive interface {
AddFile(*os.File) error
RemoveFile(string) error
Deflate() (io.Reader, error)
}

type Zip struct {
files []*os.File
zipW *zip.Writer
zipF *os.File
}

func newZip(zipFile *os.File) *Zip {
zipWriter := zip.NewWriter(zipFile)
return &Zip{
zipF: zipFile,
zipW: zipWriter,
}
}

func (z *Zip) AddFile(f *os.File) error {
stat, err := f.Stat()
if err != nil {
return err
}
header, err := zip.FileInfoHeader(stat)
if err != nil {
return err
}
header.Method = zip.Deflate
fileInArchive, err := z.zipW.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(fileInArchive, f)
if err != nil {
return err
}

return nil
}

func (z *Zip) RemoveFile(_ string) error {
panic("not implemented") // TODO: Implement
}

func (z *Zip) Deflate() (io.Reader, error) {
defer func() {
_ = z.zipF.Close()
_ = os.Remove(z.zipF.Name())
}()
_ = z.zipW.Flush()
_ = z.zipW.Close()

z.zipF.Seek(0, 0)

buf := bytes.NewBuffer([]byte{})
_, err := io.Copy(buf, z.zipF)
if err != nil {
return nil, err
}

return buf, nil
}
61 changes: 60 additions & 1 deletion app/services/playlists/playlists.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package playlists

import (
"dankmuzikk/config"
"dankmuzikk/db"
"dankmuzikk/entities"
"dankmuzikk/models"
"dankmuzikk/services/archive"
"dankmuzikk/services/nanoid"
"errors"
"fmt"
"io"
"os"
"time"
)

Expand All @@ -16,15 +20,22 @@ type Service struct {
repo db.UnsafeCRUDRepo[models.Playlist]
playlistOwnersRepo db.CRUDRepo[models.PlaylistOwner]
playlistSongsRepo db.UnsafeCRUDRepo[models.PlaylistSong]
zipService *archive.Service
}

// New accepts a playlist repo, a playlist pwners, and returns a new instance to the playlists service.
func New(
repo db.UnsafeCRUDRepo[models.Playlist],
playlistOwnersRepo db.CRUDRepo[models.PlaylistOwner],
playlistSongsRepo db.UnsafeCRUDRepo[models.PlaylistSong],
zipService *archive.Service,
) *Service {
return &Service{repo, playlistOwnersRepo, playlistSongsRepo}
return &Service{
repo: repo,
playlistOwnersRepo: playlistOwnersRepo,
playlistSongsRepo: playlistSongsRepo,
zipService: zipService,
}
}

// CreatePlaylist creates a new playlist with with provided details for the given account's profile.
Expand Down Expand Up @@ -287,3 +298,51 @@ func (p *Service) GetAllMappedForAddPopover(ownerId uint) ([]entities.Playlist,

return playlists, mappedPlaylists, nil
}

// Download zips the provided playlist,
// then returns an io.Reader with the playlist's songs, and an occurring error.
func (p *Service) Download(playlistPubId string, ownerId uint) (io.Reader, error) {
pl, _, err := p.Get(playlistPubId, ownerId)
if err != nil {
return nil, err
}

fileNames := make([]string, len(pl.Songs))
for i, song := range pl.Songs {
ogFile, err := os.Open(fmt.Sprintf("%s/%s.mp3", config.Env().YouTube.MusicDir, song.YtId))
if err != nil {
return nil, err
}
newShit, err := os.OpenFile(
fmt.Sprintf("%s/%d-%s.mp3", config.Env().YouTube.MusicDir, i+1, song.Title),
os.O_WRONLY|os.O_CREATE, 0644,
)
io.Copy(newShit, ogFile)
fileNames[i] = newShit.Name()
_ = newShit.Close()
_ = ogFile.Close()
}

zip, err := p.zipService.CreateZip()
if err != nil {
return nil, err
}

for _, fileName := range fileNames {
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
err = zip.AddFile(file)
if err != nil {
return nil, err
}
_ = file.Close()
_ = os.Remove(file.Name())
}

defer func() {
}()

return zip.Deflate()
}
5 changes: 5 additions & 0 deletions app/services/playlists/songs/songs.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ func (s *Service) ToggleSongInPlaylist(songId, playlistPubId string, ownerId uin
if err != nil {
return
}
err = s.downloadService.DownloadYoutubeSongQueue(songId)
if err != nil {
return
}

return true, s.downloadService.DownloadYoutubeSongQueue(songId)
} else {
return false, s.
Expand Down
Loading