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