You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
5.5 KiB
252 lines
5.5 KiB
package file
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.aiterp.net/rpdata/api/internal/store"
|
|
"github.com/globalsign/mgo"
|
|
"github.com/globalsign/mgo/bson"
|
|
)
|
|
|
|
var fileCollection *mgo.Collection
|
|
|
|
// A File is a record of a file stored in the Space.
|
|
type File struct {
|
|
ID string `bson:"_id" json:"id"`
|
|
Time time.Time `bson:"time" json:"time"`
|
|
Kind string `bson:"kind" json:"kind"`
|
|
Public bool `bson:"public" json:"public"`
|
|
Name string `bson:"name" json:"name"`
|
|
MimeType string `bson:"mimeType" json:"mimeType"`
|
|
Size int64 `bson:"size" json:"size"`
|
|
Author string `bson:"author" json:"author"`
|
|
URL string `bson:"url,omitempty" json:"url,omitempty"`
|
|
}
|
|
|
|
// Edit edits the file, changing up to both the two mutable properties
|
|
func (file *File) Edit(name *string, public *bool) error {
|
|
changes := bson.M{}
|
|
changedFile := *file
|
|
|
|
if name != nil && *name != file.Name {
|
|
changes["name"] = *name
|
|
changedFile.Name = *name
|
|
}
|
|
if public != nil && *public != file.Public {
|
|
changes["public"] = *public
|
|
changedFile.Public = *public
|
|
}
|
|
|
|
if len(changes) == 0 {
|
|
return nil
|
|
}
|
|
|
|
err := fileCollection.UpdateId(file.ID, bson.M{"$set": changes})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*file = changedFile
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes the file information from the database, and deletes the file.
|
|
func (file *File) Delete() error {
|
|
err := fileCollection.RemoveId(file.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if file.Kind == "upload" {
|
|
err = store.RemoveFile("files", file.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
file.URL = ""
|
|
|
|
return nil
|
|
}
|
|
|
|
// Insert manually inserts file information into the database. This should never, ever be API accessible
|
|
func Insert(name, kind, mimeType, author string, time time.Time, size int64, url string) (File, error) {
|
|
file := File{
|
|
ID: makeFileID(),
|
|
Kind: kind,
|
|
Time: time,
|
|
Public: false,
|
|
Author: author,
|
|
Name: name,
|
|
MimeType: mimeType,
|
|
Size: size,
|
|
URL: url,
|
|
}
|
|
|
|
err := fileCollection.Insert(file)
|
|
if err != nil {
|
|
return File{}, err
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
// Upload adds a file to the space.
|
|
func Upload(ctx context.Context, name, mimeType, author string, size int64, input io.Reader) (File, error) {
|
|
if !allowdMimeTypes[mimeType] {
|
|
return File{}, errors.New("File type not allowed:" + mimeType)
|
|
}
|
|
|
|
if name == "" {
|
|
date := time.Now().UTC().Format("Jan 02 2006 15:04:05 MST")
|
|
name = "Unnamed file (" + date + ")"
|
|
}
|
|
|
|
if mimeType == "" {
|
|
mimeType = "binary/octet-stream"
|
|
}
|
|
|
|
id := makeFileID()
|
|
|
|
path, err := store.UploadFile(ctx, "files", id, mimeType, input, size)
|
|
if err != nil {
|
|
return File{}, err
|
|
}
|
|
|
|
file := File{
|
|
ID: id,
|
|
Kind: "upload",
|
|
Time: time.Now(),
|
|
Public: false,
|
|
Author: author,
|
|
Name: name,
|
|
MimeType: mimeType,
|
|
Size: size,
|
|
URL: store.URLFromPath(path),
|
|
}
|
|
|
|
err = fileCollection.Insert(file)
|
|
if err != nil {
|
|
return File{}, err
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
// FindID finds a file by ID
|
|
func FindID(id string) (File, error) {
|
|
file := File{}
|
|
|
|
err := fileCollection.FindId(id).One(&file)
|
|
if err != nil {
|
|
return File{}, err
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
// FindName finds a file by kind, name and author
|
|
func FindName(kind string, name string, author string) (File, error) {
|
|
query := bson.M{"kind": kind, "name": name, "author": author}
|
|
file := File{}
|
|
|
|
err := fileCollection.Find(query).One(&file)
|
|
if err != nil {
|
|
return File{}, err
|
|
}
|
|
|
|
return file, nil
|
|
}
|
|
|
|
// List lists files according to the standard lookup. By default it's just the author's own files,
|
|
// but if `public` is true it will alos include files made public by other authors. If `mimeTypes` contains
|
|
// any, it will limit the results to that. If `author` is empty, it will only list public files
|
|
func List(author string, public bool, mimeTypes []string) ([]File, error) {
|
|
query := bson.M{}
|
|
|
|
if author != "" {
|
|
if public {
|
|
query["$or"] = []bson.M{
|
|
bson.M{"author": author},
|
|
bson.M{"public": true},
|
|
}
|
|
} else {
|
|
query["author"] = author
|
|
}
|
|
} else {
|
|
if !public {
|
|
return nil, errors.New("No author specified, and public is unset")
|
|
}
|
|
|
|
query["public"] = true
|
|
}
|
|
|
|
if len(mimeTypes) > 0 {
|
|
query["mimeTypes"] = bson.M{"$in": mimeTypes}
|
|
}
|
|
|
|
return listFiles(query)
|
|
}
|
|
|
|
func listFiles(query interface{}) ([]File, error) {
|
|
list := make([]File, 0, 32)
|
|
|
|
err := fileCollection.Find(query).Sort("-time").All(&list)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
// makeFileID makes a random file ID that's 32 characters long
|
|
func makeFileID() string {
|
|
result := "F" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
offset := 0
|
|
data := make([]byte, 32)
|
|
|
|
rand.Read(data)
|
|
for len(result) < 32 {
|
|
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36)
|
|
offset += 8
|
|
|
|
if offset >= 32 {
|
|
rand.Read(data)
|
|
offset = 0
|
|
}
|
|
}
|
|
|
|
return result[:32]
|
|
}
|
|
|
|
func init() {
|
|
store.HandleInit(func(db *mgo.Database) {
|
|
fileCollection = db.C("file.headers")
|
|
|
|
fileCollection.EnsureIndexKey("author")
|
|
fileCollection.EnsureIndexKey("public")
|
|
fileCollection.EnsureIndexKey("kind", "name", "author")
|
|
fileCollection.EnsureIndexKey("author", "public")
|
|
fileCollection.EnsureIndexKey("kind")
|
|
})
|
|
}
|
|
|
|
var allowdMimeTypes = map[string]bool{
|
|
"": false,
|
|
"image/jpeg": true,
|
|
"image/png": true,
|
|
"image/gif": true,
|
|
"text/plain": true,
|
|
"application/json": true,
|
|
"application/pdf": false,
|
|
"binary/octet-stream": false,
|
|
"video/mp4": false,
|
|
"audio/mp3": false,
|
|
}
|