Skip to content

Commit

Permalink
Merge pull request #84 from mbaraa/feat/download-playlist
Browse files Browse the repository at this point in the history
Feat: Download Playlist
  • Loading branch information
mbaraa authored Jun 30, 2024
2 parents df4b07c + c846443 commit 23bd052
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 129 deletions.
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

0 comments on commit 23bd052

Please sign in to comment.