Skip to content
Snippets Groups Projects
files.go 7.89 KiB
Newer Older
  • Learn to ignore specific revisions
  • Christian Pointner's avatar
    Christian Pointner committed
    //
    
    Christian Pointner's avatar
    Christian Pointner committed
    //  tank, Import and Playlist Daemon for Aura project
    //  Copyright (C) 2017-2020 Christian Pointner <equinox@helsinki.at>
    
    Christian Pointner's avatar
    Christian Pointner committed
    //
    
    Christian Pointner's avatar
    Christian Pointner committed
    //  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.
    
    Christian Pointner's avatar
    Christian Pointner committed
    //
    
    Christian Pointner's avatar
    Christian Pointner committed
    //  This program is distributed in the hope that it will be useful,
    
    Christian Pointner's avatar
    Christian Pointner committed
    //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    Christian Pointner's avatar
    Christian Pointner committed
    //  GNU Affero General Public License for more details.
    
    Christian Pointner's avatar
    Christian Pointner committed
    //
    
    Christian Pointner's avatar
    Christian Pointner committed
    //  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/>.
    
    Christian Pointner's avatar
    Christian Pointner committed
    //
    
    package store
    
    
    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
    
    func (st *Store) CreateFile(showID uint64, file File) (*File, error) {
    	if _, err := st.CreateShow(showID); err != nil {
    
    		return nil, err
    	}
    	file.ID = 0
    
    	err := st.db.Create(&file).Error
    	return &file, err
    
    func (st *Store) GetFile(showID uint64, fileID uint64) (file *File, err error) {
    
    	// 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
    
    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()
    
    	if err = tx.Save(&file).Error; err != nil {
    
    	err = tx.Commit().Error
    	out = &file
    	return
    
    func (st *Store) UpdateFileMetadata(showID uint64, id uint64, metadata map[string]string) (file *File, err error) {
    
    	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 {
    
    	if file.Source.Import.State != ImportDone {
    
    		err = ErrFileImportNotDone
    		return
    
    	var fields map[string]interface{}
    
    	if fields, err = st.metadataFieldsToFile(showID, id, metadata); err != nil {
    
    	if err = tx.Model(&file).Where("show_id = ?", showID).Updates(fields).Error; err != nil {
    
    	err = tx.Commit().Error
    	return
    
    func (st *Store) updateFile(showID uint64, id uint64, values ...interface{}) (file *File, err error) {
    
    	if err = st.db.Model(&file).Where("show_id = ?", showID).Updates(values).Error; err != nil {
    
    		return nil, err
    
    func (st *Store) updateImportState(showID uint64, id uint64, state ImportState) (file *File, err error) {
    	file = &File{ID: id}
    
    	if err = st.db.Model(&file).Where("show_id = ?", showID).Update("source__import__state", state).Error; err != nil {
    		return nil, err
    	}
    
    	return file, nil
    }
    
    func (st *Store) updateImportError(showID uint64, id uint64, error string) (file *File, err error) {
    	file = &File{ID: id}
    
    	if err = st.db.Model(&file).Where("show_id = ?", showID).Update("source__import__error", error).Error; err != nil {
    		return nil, err
    	}
    
    	return file, nil
    }
    
    func (st *Store) updateHash(showID uint64, id uint64, hash string) (file *File, err error) {
    	file = &File{ID: id}
    
    	if err = st.db.Model(&file).Where("show_id = ?", showID).Update("source__hash", hash).Error; err != nil {
    		return nil, err
    	}
    
    	return file, nil
    }
    
    func (st *Store) updateMetadata(showID, id uint64, metadata map[string]interface{}) (file *File, err error) {
    	file = &File{ID: id}
    
    	// we need to select all the fields with "*" to update them using this function
    	if err = st.db.Model(&file).Where("show_id = ?", showID).Select("*").Updates(metadata).Error; err != nil {
    		return nil, err
    	}
    
    	return file, nil
    }
    
    
    func (st *Store) SetFileImportStateInitializing(showID uint64, id uint64) (file *File, err error) {
    
    	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.updateImportState(showID, id, ImportPending)
    
    func (st *Store) SetFileImportStateRunning(showID uint64, id uint64) (file *File, err error) {
    
    	return st.updateImportState(showID, id, ImportRunning)
    
    func (st *Store) SetFileImportStateDone(showID uint64, id uint64) (file *File, err error) {
    	if file, err = st.updateImportState(showID, id, ImportDone); err != nil {
    		return nil, err
    	}
    
    
    	fields, err := st.metadataFieldsFromFile(showID, id)
    
    	if err != nil {
    
    		return nil, err
    	}
    
    	return st.updateMetadata(showID, id, fields)
    
    func (st *Store) SetFileImportStateAborted(showID uint64, id uint64, error string) (file *File, err error) {
    
    	if file, err = st.updateImportState(showID, id, ImportAborted); err != nil {
    		return nil, err
    	}
    
    	return st.updateImportError(showID, id, error)
    
    func (st *Store) UpdateFileSourceHash(showID uint64, id uint64, hash string) (file *File, err error) {
    	return st.updateHash(showID, id, hash)
    
    Christian Pointner's avatar
    Christian Pointner committed
    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")
    
    	err = st.db.Where("id in ?", sub).Find(playlists).Error
    
    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
    
    	if err = st.db.Model(&File{ID: id}).Where("show_id = ?", showID).Count(&cnt).Error; err != nil {
    
    Christian Pointner's avatar
    Christian Pointner committed
    	err = st.getFileUsage(id, &playlists)
    
    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{}
    
    Christian Pointner's avatar
    Christian Pointner committed
    		if err = st.getFileUsage(id, &usageErr.Playlists); err != nil {
    
    		return usageErr
    
    	if result.RowsAffected == 0 {
    		return ErrNotFound
    	}
    
    	if err := os.Remove(filename); err != nil && !os.IsNotExist(err) {
    		return fmt.Errorf("unable to delete file '%s': %v", filename, err)
    	}
    
    
    func (st *Store) GetFileShowID(id uint64) (uint64, error) {
    
    	// WARNING: using this function subverts the checks performed in the other functions
    
    	file := &File{}
    	if err := st.db.First(file, "id = ?", id).Error; err != nil {
    
    	return file.ShowID, nil