code/app/storage.go (174 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "encoding/json" "fmt" "io" "mime/multipart" "net/url" "path/filepath" "strings" "cloud.google.com/go/storage" "google.golang.org/api/iterator" ) type CloudStorage struct { Client storage.Client Bucket string ctx context.Context } func NewCloudStorage(bucket string) (CloudStorage, error) { cs := CloudStorage{} ctx := context.Background() client, err := storage.NewClient(ctx) if err != nil { return cs, fmt.Errorf("failed to create client: %v", err) } cs.Client = *client cs.Bucket = bucket cs.ctx = ctx return cs, nil } func (cs *CloudStorage) Close() error { return cs.Client.Close() } func (cs CloudStorage) List() (CSFiles, error) { i := CSFiles{} bucket := cs.Client.Bucket(cs.Bucket) query := &storage.Query{} it := bucket.Objects(cs.ctx, query) for { obj, err := it.Next() if err == iterator.Done { break } if err != nil { return i, fmt.Errorf("error iterating over bucket query: %s", err) } u, err := url.Parse(obj.MediaLink) if err != nil { return i, fmt.Errorf("cannot create url from %s: %s", obj.MediaLink, err) } img := CSFile{obj.Name, cs.Bucket, u} i = append(i, img) } return i, nil } func (cs CloudStorage) Read(id string) (CSFiles, error) { i := CSFiles{} bucket := cs.Client.Bucket(cs.Bucket) query := &storage.Query{Prefix: fmt.Sprintf("processed/%s", id)} it := bucket.Objects(cs.ctx, query) for { obj, err := it.Next() if err == iterator.Done { break } if err != nil { return i, fmt.Errorf("error iterating over bucket query: %s", err) } u, err := url.Parse(obj.MediaLink) if err != nil { return i, fmt.Errorf("cannot create url from %s: %s", obj.MediaLink, err) } img := CSFile{obj.Name, cs.Bucket, u} i = append(i, img) } return i, nil } func (cs CloudStorage) Create(name string, file multipart.File) error { csPath := fmt.Sprintf("uploads/%s", name) obj := cs.Client.Bucket(cs.Bucket).Object(csPath).NewWriter(cs.ctx) if _, err := io.Copy(obj, file); err != nil { return fmt.Errorf("could not write file to CloudStorage") } defer obj.Close() return nil } func (cs CloudStorage) Delete(id string) error { bucket := cs.Client.Bucket(cs.Bucket) query := &storage.Query{Prefix: fmt.Sprintf("processed/%s/", id)} it := bucket.Objects(cs.ctx, query) for { i, err := it.Next() if err == iterator.Done { break } if err != nil { return fmt.Errorf("error iterating over bucket query: %s", err) } obj := cs.Client.Bucket(cs.Bucket).Object(i.Name) if err := obj.Delete(cs.ctx); err != nil { return fmt.Errorf("error deleting %s: %s", i.Name, err) } } return nil } type CSFile struct { Name string Bucket string URL *url.URL } type CSFiles []CSFile type Image struct { Name string `json:"name"` Original string `json:"original"` Thumbnail string `json:"thumbnail"` } // Load converts a Cloud Storage Object to the format we need for this app. func (i *Image) Load(f CSFile) error { if strings.Index(f.Name, "original.") > -1 { dir := filepath.Dir(f.Name) base := filepath.Base(f.Name) name := strings.Replace(dir, "processed/", "", 1) o := fmt.Sprintf("https://storage.googleapis.com/%s/%s/%s", f.Bucket, dir, base) t := fmt.Sprintf("https://storage.googleapis.com/%s/%s/%s", f.Bucket, dir, strings.Replace(base, "original.", "thumbnail.", 1)) img := Image{name, o, t} *i = img } return nil } // JSON marshalls the content of Image to json. func (i Image) JSON() (string, error) { bytes, err := json.Marshal(i) if err != nil { return "", fmt.Errorf("could not marshal json for response: %s", err) } return string(bytes), nil } // JSONBytes marshalls the content of Image to json. func (i Image) JSONBytes() ([]byte, error) { bytes, err := json.Marshal(i) if err != nil { return []byte{}, fmt.Errorf("could not marshal json for response: %s", err) } return bytes, nil } type Images []Image // NewImages returns a list of images in the format we need for this app. func NewImages(fs CSFiles) (Images, error) { is := Images{} err := is.Load(fs) return is, err } // Load converts a slice of Cloud Storage Objects to the format we need for this app. func (is *Images) Load(fs CSFiles) error { for _, v := range fs { if strings.Index(v.Name, "original.") > -1 { i := Image{} if err := i.Load(v); err != nil { return err } *is = append(*is, i) } } return nil } // JSON marshalls the content of Images to json. func (is Images) JSON() (string, error) { bytes, err := json.Marshal(is) if err != nil { return "", fmt.Errorf("could not marshal json for response: %s", err) } return string(bytes), nil } // JSONBytes marshalls the content of Images to json. func (is Images) JSONBytes() ([]byte, error) { bytes, err := json.Marshal(is) if err != nil { return []byte{}, fmt.Errorf("could not marshal json for response: %s", err) } return bytes, nil }