// // tank // // Import and Playlist Daemon for autoradio project // // // Copyright (C) 2017-2018 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 ( "encoding/json" "errors" "github.com/coreos/bbolt" ) func getFilesAndImportsBucket(tx *bolt.Tx, group string) (fb, ib *bolt.Bucket, err error) { var gb *bolt.Bucket if gb, err = getGroupBucket(tx, group); err != nil { return } if gb == nil { if !tx.Writable() { return // for read-only transactions this function might return files and imports = nil } if gb, err = createGroup(tx, group); err != nil { return } } fb = gb.Bucket([]byte(filesBn)) if fb == nil { err = errors.New("invalid store: files bucket of group '" + group + "' not found") return } ib = gb.Bucket([]byte(importsBn)) if ib == nil { err = errors.New("invalid store: imports bucket of group '" + group + "' not found") return } return } func markImportAborted(tx *bolt.Tx, fb *bolt.Bucket, k []byte) (err error) { bFile := fb.Get(k) if bFile == nil { return nil // this is not really a poblem, the corresponding import will be deleted anyway } var file File if err = json.Unmarshal(bFile, &file); err != nil { return } file.Source.Import.State = ImportAborted file.Source.Import.Success = false file.Source.Import.Log.Append("aborted after unexpected error") return } func cleanupAndRepairFilesAndImportsOfGroup(tx *bolt.Tx, gb *bolt.Bucket) (err error) { var fb, ib *bolt.Bucket fb, err = gb.CreateBucketIfNotExists([]byte(filesBn)) if err != nil { return } ib, err = gb.CreateBucketIfNotExists([]byte(importsBn)) if err != nil { return } ic := ib.Cursor() for k, v := ic.First(); k != nil; k, v = ic.Next() { if v == nil { // TODO: delete bucket keys? continue } is := ImportNew is.UnmarshalText(v) // ignoring errors here and treat the case as if state == New (aka delete the corresponding file object)" if is == ImportNew { fb.Delete(k) } else { if err = markImportAborted(tx, fb, k); err != nil { return } } ib.Delete(k) } return } func (st *Store) ListFiles(group string) (files Files, err error) { err = st.db.View(func(tx *bolt.Tx) error { fb, _, err := getFilesAndImportsBucket(tx, group) if err != nil { return err } if fb == nil { // this is a read-only transaction getFilesBucket might return fb = nil return nil } files = make(Files) c := fb.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { var file File if err := json.Unmarshal(v, &file); err != nil { return err } files[btoi(k)] = file } return err }) return } func (st *Store) CreateFile(group string, file File) (id uint64, err error) { err = st.db.Update(func(tx *bolt.Tx) error { fb, ib, err := getFilesAndImportsBucket(tx, group) if err != nil { return err } if id, err = getNextID(tx); err != nil { return err } bFile, err := json.Marshal(file) if err != nil { return err } bImportState, err := ImportNew.MarshalText() if err != nil { return err } if err = ib.Put(itob(id), bImportState); err != nil { return err } return fb.Put(itob(id), bFile) }) return } func (st *Store) GetFile(group string, id uint64) (file File, err error) { err = st.db.View(func(tx *bolt.Tx) error { fb, _, err := getFilesAndImportsBucket(tx, group) if err != nil { return err } if fb == nil { // this is a read-only transaction getFilesBucket might return fb = nil return ErrNotFound } bFile := fb.Get(itob(id)) if bFile == nil { return ErrNotFound } return json.Unmarshal(bFile, &file) }) return } func (st *Store) UpdateFile(group string, id uint64, file File) (err error) { err = st.db.Update(func(tx *bolt.Tx) error { fb, _, err := getFilesAndImportsBucket(tx, group) if err != nil { return err } bFile := fb.Get(itob(id)) if bFile == nil { return ErrNotFound } bFile, err = json.Marshal(file) if err != nil { return err } // TODO: update metdata fileheader // TODO: update affiliation with auto-playlists return fb.Put(itob(id), bFile) }) return } func (st *Store) DeleteFile(group string, id uint64) (err error) { err = st.db.Update(func(tx *bolt.Tx) error { fb, _, err := getFilesAndImportsBucket(tx, group) if err != nil { return err } bFile := fb.Get(itob(id)) if bFile == nil { return ErrNotFound } var file File if err = json.Unmarshal(bFile, &file); err != nil { return err } if len(file.UsedBy) > 0 { return ErrFileInUse } // TODO: remove file from auto-playlists // TODO: delete file in filesystem return fb.Delete(itob(id)) }) return }