// PutUnchecked uploads the object // // This will create a duplicate if we upload a new file without // checking to see if there is one already - use Put() for that. func (f *Fs) PutUnchecked(in io.Reader, src fs.ObjectInfo) (fs.Object, error) { remote := src.Remote() size := src.Size() modTime := src.ModTime() o, createInfo, err := f.createFileInfo(remote, modTime, size) if err != nil { return nil, err } var info *drive.File if size == 0 || size < int64(driveUploadCutoff) { // Make the API request to upload metadata and file data. // Don't retry, return a retry error instead err = f.pacer.CallNoRetry(func() (bool, error) { info, err = f.svc.Files.Insert(createInfo).Media(in, googleapi.ContentType("")).Do() return shouldRetry(err) }) if err != nil { return o, err } } else { // Upload the file in chunks info, err = f.Upload(in, size, createInfo.MimeType, createInfo, remote) if err != nil { return o, err } } o.setMetaData(info) return o, nil }
func (w *Writer) open() error { attrs := w.ObjectAttrs // Check the developer didn't change the object Name (this is unfortunate, but // we don't want to store an object under the wrong name). if attrs.Name != w.o.object { return fmt.Errorf("storage: Writer.Name %q does not match object name %q", attrs.Name, w.o.object) } if !utf8.ValidString(attrs.Name) { return fmt.Errorf("storage: object name %q is not valid UTF-8", attrs.Name) } pr, pw := io.Pipe() w.pw = pw w.opened = true if w.ChunkSize < 0 { return errors.New("storage: Writer.ChunkSize must non-negative") } mediaOpts := []googleapi.MediaOption{ googleapi.ChunkSize(w.ChunkSize), } if c := attrs.ContentType; c != "" { mediaOpts = append(mediaOpts, googleapi.ContentType(c)) } go func() { defer close(w.donec) call := w.o.c.raw.Objects.Insert(w.o.bucket, attrs.toRawObject(w.o.bucket)). Media(pr, mediaOpts...). Projection("full"). Context(w.ctx) if err := setEncryptionHeaders(call.Header(), w.o.encryptionKey, false); err != nil { w.err = err pr.CloseWithError(w.err) return } var resp *raw.Object err := applyConds("NewWriter", w.o.gen, w.o.conds, call) if err == nil { err = runWithRetry(w.ctx, func() error { resp, err = call.Do(); return err }) } if err != nil { w.err = err pr.CloseWithError(w.err) return } w.obj = newObject(resp) }() return nil }
// Update the object with the contents of the io.Reader, modTime and size // // The new object may have been created if an error is returned func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error { size := src.Size() modTime := src.ModTime() object := storage.Object{ Bucket: o.fs.bucket, Name: o.fs.root + o.remote, ContentType: fs.MimeType(o), Size: uint64(size), Updated: modTime.Format(timeFormatOut), // Doesn't get set Metadata: metadataFromModTime(modTime), } newObject, err := o.fs.svc.Objects.Insert(o.fs.bucket, &object).Media(in, googleapi.ContentType("")).Name(object.Name).PredefinedAcl(o.fs.objectAcl).Do() if err != nil { return err } // Set the metadata for the new object while we have it o.setMetaData(newObject) return nil }
func (w *Writer) open() error { attrs := w.ObjectAttrs // Check the developer didn't change the object Name (this is unfortunate, but // we don't want to store an object under the wrong name). if attrs.Name != w.o.object { return fmt.Errorf("storage: Writer.Name %q does not match object name %q", attrs.Name, w.o.object) } if !utf8.ValidString(attrs.Name) { return fmt.Errorf("storage: object name %q is not valid UTF-8", attrs.Name) } pr, pw := io.Pipe() w.pw = pw w.opened = true var mediaOpts []googleapi.MediaOption if c := attrs.ContentType; c != "" { mediaOpts = append(mediaOpts, googleapi.ContentType(c)) } go func() { defer close(w.donec) call := w.o.c.raw.Objects.Insert(w.o.bucket, attrs.toRawObject(w.o.bucket)). Media(pr, mediaOpts...). Projection("full"). Context(w.ctx) var resp *raw.Object err := applyConds("NewWriter", w.o.conds, call) if err == nil { resp, err = call.Do() } if err != nil { w.err = err pr.CloseWithError(w.err) return } w.obj = newObject(resp) }() return nil }
// Update the already existing object // // Copy the reader into the object updating modTime and size // // The new object may have been created if an error is returned func (o *Object) Update(in io.Reader, src fs.ObjectInfo) error { size := src.Size() modTime := src.ModTime() if o.isDocument { return errors.New("can't update a google document") } updateInfo := &drive.File{ Id: o.id, MimeType: fs.MimeType(o), ModifiedDate: modTime.Format(timeFormatOut), } // Make the API request to upload metadata and file data. var err error var info *drive.File if size == 0 || size < int64(driveUploadCutoff) { // Don't retry, return a retry error instead err = o.fs.pacer.CallNoRetry(func() (bool, error) { info, err = o.fs.svc.Files.Update(updateInfo.Id, updateInfo).SetModifiedDate(true).Media(in, googleapi.ContentType("")).Do() return shouldRetry(err) }) if err != nil { return err } } else { // Upload the file in chunks info, err = o.fs.Upload(in, size, fs.MimeType(o), updateInfo, o.remote) if err != nil { return err } } o.setMetaData(info) return nil }
func TestContentType(t *testing.T) { s := createService() if s == nil { t.Fatal("Could not create service") } type testCase struct { objectContentType string useOptionContentType bool optionContentType string wantContentType string } // The Media method will use resumable upload if the supplied data is // larger than googleapi.DefaultUploadChunkSize We run the following // tests with two different file contents: one that will trigger // resumable upload, and one that won't. forceResumableData := bytes.Repeat([]byte("a"), googleapi.DefaultUploadChunkSize+1) smallData := bytes.Repeat([]byte("a"), 2) // In the following test, the content type, if any, in the Object struct is always "text/plain". // The content type configured via googleapi.ContentType, if any, is always "text/html". for _, tc := range []testCase{ // With content type specified in the object struct { objectContentType: "text/plain", useOptionContentType: true, optionContentType: "text/html", wantContentType: "text/html", }, { objectContentType: "text/plain", useOptionContentType: true, optionContentType: "", wantContentType: "text/plain", }, { objectContentType: "text/plain", useOptionContentType: false, wantContentType: "text/plain; charset=utf-8", // sniffed. }, // Without content type specified in the object struct { useOptionContentType: true, optionContentType: "text/html", wantContentType: "text/html", }, { useOptionContentType: true, optionContentType: "", wantContentType: "", // Result is an object without a content type. }, { useOptionContentType: false, wantContentType: "text/plain; charset=utf-8", // sniffed. }, } { // The behavior should be the same, regardless of whether resumable upload is used or not. for _, data := range [][]byte{smallData, forceResumableData} { o := &storage.Object{ Bucket: bucket, Name: "test-content-type", ContentType: tc.objectContentType, } call := s.Objects.Insert(bucket, o) var opts []googleapi.MediaOption if tc.useOptionContentType { opts = append(opts, googleapi.ContentType(tc.optionContentType)) } call.Media(bytes.NewReader(data), opts...) _, err := call.Do() if err != nil { t.Fatalf("unable to insert object %q: %v", o.Name, err) } readObj, err := s.Objects.Get(bucket, o.Name).Do() if err != nil { t.Error(err) } if got, want := readObj.ContentType, tc.wantContentType; got != want { t.Errorf("contentType of %q; got %q; want %q", o.Name, got, want) } } } }
func main() { configs := createConfigsModelFromEnvs() fmt.Println() configs.print() if err := configs.validate(); err != nil { fmt.Println() log.Error("Issue with input: %s", err) os.Exit(1) } // // Create client fmt.Println() log.Info("Authenticateing") jwtConfig := new(jwt.Config) if configs.JSONKeyPath != "" { jsonKeyPth := "" if strings.HasPrefix(configs.JSONKeyPath, "file://") { jsonKeyPth = strings.TrimPrefix(configs.JSONKeyPath, "file://") } else { tmpDir, err := pathutil.NormalizedOSTempDirPath("__google-play-deploy__") if err != nil { log.Error("Failed to create tmp dir, error: %s", err) os.Exit(1) } jsonKeyPth = filepath.Join(tmpDir, "key.json") if err := downloadFile(configs.JSONKeyPath, jsonKeyPth); err != nil { log.Error("Failed to download json key file, error: %s", err) os.Exit(1) } } authConfig, err := jwtConfigFromJSONKeyFile(jsonKeyPth) if err != nil { log.Error("Failed to create auth config from json key file, error: %s", err) os.Exit(1) } jwtConfig = authConfig } else { p12KeyPath := "" if strings.HasPrefix(configs.P12KeyPath, "file://") { p12KeyPath = strings.TrimPrefix(configs.P12KeyPath, "file://") } else { tmpDir, err := pathutil.NormalizedOSTempDirPath("__google-play-deploy__") if err != nil { log.Error("Failed to create tmp dir, error: %s", err) os.Exit(1) } p12KeyPath = filepath.Join(tmpDir, "key.p12") if err := downloadFile(configs.P12KeyPath, p12KeyPath); err != nil { log.Error("Failed to download p12 key file, error: %s", err) os.Exit(1) } } authConfig, err := jwtConfigFromP12KeyFile(p12KeyPath, configs.ServiceAccountEmail) if err != nil { log.Error("Failed to create auth config from p12 key file, error: %s", err) os.Exit(1) } jwtConfig = authConfig } client := jwtConfig.Client(oauth2.NoContext) service, err := androidpublisher.New(client) if err != nil { log.Error("Failed to create publisher service, error: %s", err) os.Exit(1) } log.Done("Authenticated client created") // --- // // Create insert edit fmt.Println() log.Info("Create new edit") editsService := androidpublisher.NewEditsService(service) editsInsertCall := editsService.Insert(configs.PackageName, nil) appEdit, err := editsInsertCall.Do() if err != nil { log.Error("Failed to perform edit insert call, error: %s", err) os.Exit(1) } log.Detail(" editID: %s", appEdit.Id) // --- // // Upload APKs fmt.Println() log.Info("Upload apks") versionCodes := []int64{} apkPaths := strings.Split(configs.ApkPath, "|") for _, apkPath := range apkPaths { apkFile, err := os.Open(apkPath) if err != nil { log.Error("Failed to read apk (%s), error: %s", apkPath, err) os.Exit(1) } editsApksService := androidpublisher.NewEditsApksService(service) editsApksUloadCall := editsApksService.Upload(configs.PackageName, appEdit.Id) editsApksUloadCall.Media(apkFile, googleapi.ContentType("application/vnd.android.package-archive")) apk, err := editsApksUloadCall.Do() if err != nil { log.Error("Failed to upload apk, error: %s", err) os.Exit(1) } log.Detail(" uploaded apk version: %d", apk.VersionCode) versionCodes = append(versionCodes, apk.VersionCode) } // --- // // Update track fmt.Println() log.Info("Update track") editsTracksService := androidpublisher.NewEditsTracksService(service) newTrack := androidpublisher.Track{ Track: configs.Track, VersionCodes: versionCodes, } if configs.Track == "rollout" { userFraction, err := strconv.ParseFloat(configs.UserFraction, 64) if err != nil { log.Error("Failed to parse user fraction, error: %s", err) os.Exit(1) } newTrack.UserFraction = userFraction } editsTracksUpdateCall := editsTracksService.Update(configs.PackageName, appEdit.Id, configs.Track, &newTrack) track, err := editsTracksUpdateCall.Do() if err != nil { log.Error("Failed to update track, error: %s", err) os.Exit(1) } log.Detail(" updated track: %s", track.Track) log.Detail(" assigned apk versions: %v", track.VersionCodes) // --- // // Update listing if configs.WhatsnewsDir != "" { fmt.Println() log.Info("Update listing") recentChangesMap, err := readLocalisedRecentChanges(configs.WhatsnewsDir) if err != nil { log.Error("Failed to read whatsnews, error: %s", err) os.Exit(1) } editsApklistingsService := androidpublisher.NewEditsApklistingsService(service) for _, versionCode := range versionCodes { log.Detail(" updating recent changes for version: %d", versionCode) for language, recentChanges := range recentChangesMap { newApkListing := androidpublisher.ApkListing{ Language: language, RecentChanges: recentChanges, } editsApkListingsCall := editsApklistingsService.Update(configs.PackageName, appEdit.Id, versionCode, language, &newApkListing) apkListing, err := editsApkListingsCall.Do() if err != nil { log.Error("Failed to update listing, error: %s", err) os.Exit(1) } log.Detail(" - language: %s", apkListing.Language) } } } // --- // // Validate edit fmt.Println() log.Info("Validating edit") editsValidateCall := editsService.Validate(configs.PackageName, appEdit.Id) if _, err := editsValidateCall.Do(); err != nil { log.Error("Failed to validate edit, error: %s", err) os.Exit(1) } log.Done("Edit is valid") // --- // // Commit edit fmt.Println() log.Info("Committing edit") editsCommitCall := editsService.Commit(configs.PackageName, appEdit.Id) if _, err := editsCommitCall.Do(); err != nil { log.Error("Failed to commit edit, error: %s", err) os.Exit(1) } log.Done("Edit committed") // --- }