2020-02-07 17:27:45 +01:00
|
|
|
// Vikunja is a to-do list application to facilitate your life.
|
2021-02-02 20:19:13 +01:00
|
|
|
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
2019-10-16 22:52:29 +02:00
|
|
|
//
|
2019-12-04 20:39:56 +01:00
|
|
|
// This program is free software: you can redistribute it and/or modify
|
2020-12-23 16:41:52 +01:00
|
|
|
// it under the terms of the GNU Affero General Public Licensee as published by
|
2019-12-04 20:39:56 +01:00
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
2019-10-16 22:52:29 +02:00
|
|
|
//
|
2019-12-04 20:39:56 +01:00
|
|
|
// 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
|
2020-12-23 16:41:52 +01:00
|
|
|
// GNU Affero General Public Licensee for more details.
|
2019-10-16 22:52:29 +02:00
|
|
|
//
|
2020-12-23 16:41:52 +01:00
|
|
|
// You should have received a copy of the GNU Affero General Public Licensee
|
2019-12-04 20:39:56 +01:00
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2019-10-16 22:52:29 +02:00
|
|
|
|
|
|
|
package files
|
|
|
|
|
|
|
|
import (
|
2022-03-27 16:55:37 +02:00
|
|
|
"errors"
|
2020-10-11 22:10:03 +02:00
|
|
|
"io"
|
2021-07-20 10:14:49 +02:00
|
|
|
"os"
|
2020-10-11 22:10:03 +02:00
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2021-09-04 21:26:31 +02:00
|
|
|
"xorm.io/xorm"
|
|
|
|
|
2019-10-16 22:52:29 +02:00
|
|
|
"code.vikunja.io/api/pkg/config"
|
2020-12-23 16:32:28 +01:00
|
|
|
"code.vikunja.io/api/pkg/db"
|
2021-07-20 11:00:20 +02:00
|
|
|
"code.vikunja.io/api/pkg/log"
|
2019-10-16 22:52:29 +02:00
|
|
|
"code.vikunja.io/web"
|
2019-10-18 17:30:25 +02:00
|
|
|
"github.com/c2h5oh/datasize"
|
2019-10-16 22:52:29 +02:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
)
|
|
|
|
|
|
|
|
// File holds all information about a file
|
|
|
|
type File struct {
|
2020-12-18 17:51:22 +01:00
|
|
|
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"`
|
2019-10-16 22:52:29 +02:00
|
|
|
Name string `xorm:"text not null" json:"name"`
|
|
|
|
Mime string `xorm:"text null" json:"mime"`
|
2020-12-18 17:51:22 +01:00
|
|
|
Size uint64 `xorm:"bigint not null" json:"size"`
|
2019-10-16 22:52:29 +02:00
|
|
|
|
2020-06-27 19:04:01 +02:00
|
|
|
Created time.Time `xorm:"created" json:"created"`
|
2020-12-18 17:51:22 +01:00
|
|
|
CreatedByID int64 `xorm:"bigint not null" json:"-"`
|
2019-10-16 22:52:29 +02:00
|
|
|
|
|
|
|
File afero.File `xorm:"-" json:"-"`
|
2020-01-19 17:52:16 +01:00
|
|
|
// This ReadCloser is only used for migration purposes. Use with care!
|
|
|
|
// There is currentlc no better way of doing this.
|
|
|
|
FileContent []byte `xorm:"-" json:"-"`
|
2019-10-16 22:52:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// TableName is the table name for the files table
|
|
|
|
func (File) TableName() string {
|
|
|
|
return "files"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *File) getFileName() string {
|
|
|
|
return config.FilesBasePath.GetString() + "/" + strconv.FormatInt(f.ID, 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadFileByID returns a file by its ID
|
|
|
|
func (f *File) LoadFileByID() (err error) {
|
|
|
|
f.File, err = afs.Open(f.getFileName())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadFileMetaByID loads everything about a file without loading the actual file
|
|
|
|
func (f *File) LoadFileMetaByID() (err error) {
|
|
|
|
exists, err := x.Where("id = ?", f.ID).Get(f)
|
|
|
|
if !exists {
|
|
|
|
return ErrFileDoesNotExist{FileID: f.ID}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create creates a new file from an FileHeader
|
2020-08-02 19:16:58 +02:00
|
|
|
func Create(f io.Reader, realname string, realsize uint64, a web.Auth) (file *File, err error) {
|
|
|
|
return CreateWithMime(f, realname, realsize, a, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateWithMime creates a new file from an FileHeader and sets its mime type
|
|
|
|
func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
2021-09-04 21:26:31 +02:00
|
|
|
s := db.NewSession()
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
|
|
|
|
if err != nil {
|
|
|
|
_ = s.Rollback()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2019-10-16 22:52:29 +02:00
|
|
|
|
2021-09-04 21:26:31 +02:00
|
|
|
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
2019-10-18 17:30:25 +02:00
|
|
|
// Get and parse the configured file size
|
|
|
|
var maxSize datasize.ByteSize
|
|
|
|
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if realsize > maxSize.Bytes() {
|
2019-10-16 22:52:29 +02:00
|
|
|
return nil, ErrFileIsTooLarge{Size: realsize}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We first insert the file into the db to get it's ID
|
|
|
|
file = &File{
|
|
|
|
Name: realname,
|
|
|
|
Size: realsize,
|
|
|
|
CreatedByID: a.GetID(),
|
2020-08-02 19:16:58 +02:00
|
|
|
Mime: mime,
|
2019-10-16 22:52:29 +02:00
|
|
|
}
|
|
|
|
|
2020-12-23 16:32:28 +01:00
|
|
|
_, err = s.Insert(file)
|
2019-10-16 22:52:29 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save the file to storage with its new ID as path
|
2020-06-21 17:30:48 +02:00
|
|
|
err = file.Save(f)
|
2019-10-16 22:52:29 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete removes a file from the DB and the file system
|
|
|
|
func (f *File) Delete() (err error) {
|
2020-12-23 16:32:28 +01:00
|
|
|
s := db.NewSession()
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
deleted, err := s.Where("id = ?", f.ID).Delete(f)
|
2019-10-16 22:52:29 +02:00
|
|
|
if err != nil {
|
2020-12-23 16:32:28 +01:00
|
|
|
_ = s.Rollback()
|
2019-10-16 22:52:29 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if deleted == 0 {
|
2020-12-23 16:32:28 +01:00
|
|
|
_ = s.Rollback()
|
2019-10-16 22:52:29 +02:00
|
|
|
return ErrFileDoesNotExist{FileID: f.ID}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = afs.Remove(f.getFileName())
|
2020-12-23 16:32:28 +01:00
|
|
|
if err != nil {
|
2022-03-27 17:52:33 +02:00
|
|
|
var perr *os.PathError
|
|
|
|
if errors.As(err, &perr) {
|
2021-07-20 10:14:49 +02:00
|
|
|
// Don't fail when removing the file failed
|
2022-03-27 16:55:37 +02:00
|
|
|
log.Errorf("Error deleting file %d: %w", err)
|
2021-07-20 10:14:49 +02:00
|
|
|
return s.Commit()
|
|
|
|
}
|
|
|
|
|
2020-12-23 16:32:28 +01:00
|
|
|
_ = s.Rollback()
|
|
|
|
return err
|
|
|
|
}
|
2021-07-20 10:14:49 +02:00
|
|
|
|
2019-10-16 22:52:29 +02:00
|
|
|
return
|
|
|
|
}
|
2020-06-21 17:30:48 +02:00
|
|
|
|
|
|
|
// Save saves a file to storage
|
2020-08-02 19:16:58 +02:00
|
|
|
func (f *File) Save(fcontent io.Reader) error {
|
2020-06-21 17:30:48 +02:00
|
|
|
return afs.WriteReader(f.getFileName(), fcontent)
|
|
|
|
}
|