1/* 2Copyright 2018 The Perkeep Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17// Package importshare provides a method to import blobs shared from another 18// Perkeep server. 19package importshare 20 21import ( 22 "context" 23 "fmt" 24 "net/url" 25 "strings" 26 "time" 27 28 "perkeep.org/internal/httputil" 29 "perkeep.org/pkg/auth" 30 "perkeep.org/pkg/client" 31 "perkeep.org/pkg/types/camtypes" 32 33 "golang.org/x/net/context/ctxhttp" 34) 35 36const refreshPeriod = 2 * time.Second // how often to refresh the import dialog in the web UI 37 38// Import sends the shareURL to the server, so it can import all the blobs 39// transitively reachable through the claim in that share URL. It then regularly 40// polls the server to get the state of the currently running import process, and 41// it uses updateDialogFunc to update the web UI with that state. Message is 42// printed everytime the dialog updates, and importedBlobRef, if not nil, is used 43// at the end of a successful import to create a link to the newly imported file or 44// directory. 45func Import(ctx context.Context, config map[string]string, shareURL string, 46 updateDialogFunc func(message string, importedBlobRef string)) { 47 printerr := func(msg string) { 48 showError(msg, func(msg string) { 49 updateDialogFunc(msg, "") 50 }) 51 } 52 if config == nil { 53 printerr("Nil config for Import share") 54 return 55 } 56 57 authToken, ok := config["authToken"] 58 if !ok { 59 printerr("No authToken in config for Import share") 60 return 61 } 62 importSharePrefix, ok := config["importShare"] 63 if !ok { 64 printerr("No importShare in config for Import share") 65 return 66 } 67 68 am, err := auth.TokenOrNone(authToken) 69 if err != nil { 70 printerr(fmt.Sprintf("Error with authToken: %v", err)) 71 return 72 } 73 cl, err := client.New(client.OptionAuthMode(am)) 74 if err != nil { 75 printerr(fmt.Sprintf("Error with client initialization: %v", err)) 76 return 77 } 78 go func() { 79 if err := cl.Post(ctx, importSharePrefix, "application/x-www-form-urlencoded", 80 strings.NewReader(url.Values{"shareurl": {shareURL}}.Encode())); err != nil { 81 printerr(err.Error()) 82 return 83 } 84 for { 85 select { 86 case <-ctx.Done(): 87 printerr(ctx.Err().Error()) 88 return 89 case <-time.After(refreshPeriod): 90 var progress camtypes.ShareImportProgress 91 res, err := ctxhttp.Get(ctx, cl.HTTPClient(), importSharePrefix) 92 if err != nil { 93 printerr(err.Error()) 94 continue 95 } 96 if err := httputil.DecodeJSON(res, &progress); err != nil { 97 printerr(err.Error()) 98 continue 99 } 100 updateDialog(progress, updateDialogFunc) 101 if !progress.Running { 102 return 103 } 104 } 105 } 106 }() 107} 108 109// updateDialog uses updateDialogFunc to refresh the dialog that displays the 110// status of the import. Message is printed first in the dialog, and 111// importBlobRef is only passed when the import is done, to be displayed below as a 112// link to the newly imported file or directory. 113func updateDialog(progress camtypes.ShareImportProgress, 114 updateDialogFunc func(message string, importedBlobRef string)) { 115 if progress.Running { 116 if progress.Assembled { 117 updateDialogFunc("Importing file in progress", "") 118 return 119 } 120 updateDialogFunc(fmt.Sprintf("Working - %d/%d files imported", progress.FilesCopied, progress.FilesSeen), "") 121 return 122 } 123 124 if progress.Assembled { 125 updateDialogFunc(fmt.Sprintf("File successfully imported as"), progress.BlobRef.String()) 126 return 127 } 128 updateDialogFunc(fmt.Sprintf("Done - %d/%d files imported under", progress.FilesCopied, progress.FilesSeen), progress.BlobRef.String()) 129} 130 131func showError(message string, updateDialogFunc func(string)) { 132 println(message) 133 updateDialogFunc(message) 134} 135