// // tank, Import and Playlist Daemon for Aura project // Copyright (C) 2017-2020 Christian Pointner <equinox@helsinki.at> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. // package store import ( "fmt" "os" "path/filepath" ) func (st *Store) GetFilePath(showID uint64, fileID uint64) string { filename := fmt.Sprintf("%d%s", fileID, st.Audio.Format.Extension()) return filepath.Join(st.getShowPath(showID), filename) } func (st *Store) ListFiles(showID uint64, offset, limit int) (files []File, err error) { err = st.db.Where("show_id = ?", showID).Order("id").Offset(offset).Limit(limit).Find(&files).Error return } func (st *Store) CreateFile(showID uint64, file File) (*File, error) { if _, err := st.CreateShow(showID); err != nil { return nil, err } file.ID = 0 file.ShowID = showID err := st.db.Create(&file).Error return &file, err } func (st *Store) GetFile(showID uint64, fileID uint64) (file *File, err error) { file = &File{} // we have to make sure that the file actually belongs to <show> since permissions are enforced // based on show membership err = st.db.Where("show_id = ?", showID).First(file, fileID).Error return } func (st *Store) UpdateFile(showID uint64, id uint64, file File) (out *File, err error) { tx := st.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() if err == nil { err = fmt.Errorf("runtime panic: %+v", r) } } }() if err = tx.Error; err != nil { return } // make sure the file exists and actually belongs to <show> since permissions are enforced // based on show membership if err = tx.Where("show_id = ?", showID).First(&File{}, id).Error; err != nil { tx.Rollback() return } file.ID = id file.ShowID = showID if err = tx.Save(&file).Error; err != nil { tx.Rollback() return } err = tx.Commit().Error out = &file return } func (st *Store) UpdateFileMetadata(showID uint64, id uint64, metadata map[string]string) (file *File, err error) { tx := st.db.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() if err == nil { err = fmt.Errorf("runtime panic: %+v", r) } } }() if err = tx.Error; err != nil { return } file = &File{ID: id} // make sure the file exists and actually belongs to <show> since permissions are enforced // based on show membership if err = tx.Where("show_id = ?", showID).First(&file).Error; err != nil { tx.Rollback() return } if file.Source.Import.State != ImportDone { tx.Rollback() err = ErrFileImportNotDone return } var fields map[string]interface{} if fields, err = st.metadataFieldsToFile(showID, id, metadata); err != nil { tx.Rollback() return } if err = tx.Model(&file).Where("show_id = ?", showID).Update(fields).Error; err != nil { tx.Rollback() return } err = tx.Commit().Error return } func (st *Store) updateFile(showID uint64, id uint64, values ...interface{}) (file *File, err error) { file = &File{ID: id} if err = st.db.Model(&file).Where("show_id = ?", showID).Update(values...).Error; err != nil { return nil, err } return file, nil } func (st *Store) SetFileImportStateInitializing(showID uint64, id uint64) (file *File, err error) { file = &File{ID: id} result := st.db.Model(&file).Where("show_id = ? and source__import__state = ?", showID, ImportNew).Update("source__import__state", ImportInitializing) if result.Error != nil { return nil, result.Error } if result.RowsAffected != 1 { return nil, ErrFileNotNew } return file, nil } func (st *Store) SetFileImportStatePending(showID uint64, id uint64) (file *File, err error) { return st.updateFile(showID, id, "source__import__state", ImportPending) } func (st *Store) SetFileImportStateRunning(showID uint64, id uint64) (file *File, err error) { return st.updateFile(showID, id, "source__import__state", ImportRunning) } func (st *Store) SetFileImportStateDone(showID uint64, id uint64) (*File, error) { fields, err := st.metadataFieldsFromFile(showID, id) if err != nil { return nil, err } fields["source__import__state"] = ImportDone return st.updateFile(showID, id, fields) } func (st *Store) SetFileImportStateAborted(showID uint64, id uint64, error string) (file *File, err error) { fields := make(map[string]interface{}) fields["source__import__state"] = ImportAborted fields["source__import__error"] = error return st.updateFile(showID, id, fields) } func (st *Store) UpdateFileSourceHash(showID uint64, id uint64, hash string) (*File, error) { return st.updateFile(showID, id, "source__hash", hash) } func (st *Store) getFileUsage(id uint64, playlists *[]Playlist) (err error) { sub := st.db.Model(PlaylistEntry{}).Select("playlist_id").Where("file_id = ?", id).Group("playlist_id").SubQuery() err = st.db.Where("id in ?", sub).Find(playlists).Error return } func (st *Store) GetFileUsage(showID uint64, id uint64) (playlists []Playlist, err error) { // make sure the file exists and actually belongs to <show> since permissions are enforced // based on show membership cnt := 0 if err = st.db.Model(&File{ID: id}).Where("show_id = ?", showID).Count(&cnt).Error; err != nil { return } if cnt == 0 { return nil, ErrNotFound } err = st.getFileUsage(id, &playlists) return } func (st *Store) DeleteFile(showID uint64, id uint64) (err error) { // make sure the file actually belongs to <show> since permissions are enforced // based on show membership result := st.db.Where("show_id = ?", showID).Delete(&File{ID: id}) if err = result.Error; err != nil { // we assume this is due to a FK constraint -> file in use by playlist_entry usageErr := &ErrFileInUse{} if err = st.getFileUsage(id, &usageErr.Playlists); err != nil { return } return usageErr } if result.RowsAffected == 0 { return ErrNotFound } filename := st.GetFilePath(showID, id) if err := os.Remove(filename); err != nil && !os.IsNotExist(err) { return fmt.Errorf("unable to delete file '%s': %v", filename, err) } return } func (st *Store) GetFileShowID(id uint64) (uint64, error) { if err := st.db.Select("show_id").First(File{}, "id = ?", id).Error; err != nil { return 0, err } return File{}.ShowID, nil }