// // tank // // Import and Playlist Daemon for autoradio project // // // Copyright (C) 2017-2019 Christian Pointner <equinox@helsinki.at> // // This file is part of tank. // // tank is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // tank is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with tank. If not, see <http://www.gnu.org/licenses/>. // package store import ( "errors" "fmt" "os" "path/filepath" "github.com/jinzhu/gorm" ) func (st *Store) getShowPath(show string) string { return filepath.Join(st.basePath, show) } func (st *Store) createShow(tx *gorm.DB, name string) (show *Show, err error) { showDirPath := st.getShowPath(name) if err := os.Mkdir(showDirPath, 0755); err != nil { if !os.IsExist(err) { return nil, errors.New("unable to create directory for show '" + name + "': " + err.Error()) } if stat, err := os.Stat(showDirPath); err != nil { return nil, errors.New("unable to create directory for show '" + name + "': " + err.Error()) } else if !stat.IsDir() { return nil, errors.New("unable to create directory for show '" + name + "': path exists and is not a directory") } } show = &Show{Name: name} err = tx.FirstOrCreate(show).Error if err != nil { os.RemoveAll(showDirPath) } return } // this will not fail if the show already exists func (st *Store) CreateShow(name string) (show *Show, err error) { return st.createShow(st.db, name) } func (st *Store) cloneFiles(tx *gorm.DB, name, from string) (fileIDMapping map[uint64]uint64, err error) { fileIDMapping = make(map[uint64]uint64) var files Files if err = tx.Where("show_name = ?", from).Find(&files).Error; err != nil { return } for _, file := range files { fromFileID := file.ID file.ID = 0 file.ShowName = name if err = tx.Create(&file).Error; err != nil { return } if err = os.Link(st.GetFilePath(from, fromFileID), st.GetFilePath(name, file.ID)); err != nil { return } fileIDMapping[fromFileID] = file.ID var logs []ImportLog if err = tx.Where("file_id = ?", file.ID).Find(&logs).Error; err != nil { return } for _, log := range logs { log.ID = 0 log.File = File{ID: file.ID, ShowName: name} log.FileID = 0 if err = tx.Create(&log).Error; err != nil { return } } } return } func (st *Store) clonePlaylists(tx *gorm.DB, name, from string, fileIDMapping map[uint64]uint64) (err error) { var playlists Playlists err = tx.Where("show_name = ?", from).Preload("Entries", func(db *gorm.DB) *gorm.DB { return db.Order("playlist_entries.line_num asc") }).Preload("Entries.File").Find(&playlists).Error if err != nil { return } for _, playlist := range playlists { playlist.ID = 0 playlist.ShowName = name for idx := range playlist.Entries { playlist.Entries[idx].ID = 0 playlist.Entries[idx].PlaylistID = 0 if playlist.Entries[idx].File != nil { toFileID, exists := fileIDMapping[playlist.Entries[idx].File.ID] if exists { playlist.Entries[idx].File = &File{ID: toFileID, ShowName: name} } } } if err = tx.Create(&playlist).Error; err != nil { return } } return } func (st *Store) CloneShow(name, from string) (show *Show, err error) { tx := st.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() os.RemoveAll(st.getShowPath(name)) if err == nil { err = fmt.Errorf("runtime panic: %+v", r) } } }() if err = tx.Error; err != nil { return nil, err } cnt := 0 if err = tx.Model(&Show{Name: from}).Count(&cnt).Error; err != nil { tx.Rollback() return nil, err } if cnt == 0 { tx.Rollback() return nil, ErrNotFound } if err = tx.Model(&Show{Name: name}).Count(&cnt).Error; err != nil { tx.Rollback() return nil, err } if cnt > 0 { tx.Rollback() return nil, ErrShowAlreadyExists } if show, err = st.createShow(tx, name); err != nil { tx.Rollback() return nil, err } var fileIDMapping map[uint64]uint64 if fileIDMapping, err = st.cloneFiles(tx, name, from); err != nil { tx.Rollback() os.RemoveAll(st.getShowPath(name)) return nil, err } if err = st.clonePlaylists(tx, name, from, fileIDMapping); err != nil { tx.Rollback() os.RemoveAll(st.getShowPath(name)) return nil, err } return show, tx.Commit().Error } // this will also remove all files and playlists belonging to the show! // TODO: fix cascading errors (deletion of files is tried before deletion of playlists // which does not work when files are still refrenced by playlists... func (st *Store) DeleteShow(name string) error { if err := st.db.Delete(&Show{Name: name}).Error; err != nil { return err } return os.RemoveAll(st.getShowPath(name)) } func (st *Store) ListShows() (shows Shows, err error) { err = st.db.Find(&shows).Error return }