|
|
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, }
|