Browse code

split in 3 files

Victor Vieux authored on 2013/11/29 05:16:57
Showing 7 changed files
... ...
@@ -202,7 +202,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
202 202
 	// FIXME: ProgressReader shouldn't be this annoying to use
203 203
 	if context != nil {
204 204
 		sf := utils.NewStreamFormatter(false)
205
-		body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf, true)
205
+		body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf, true, "", "Uploading context")
206 206
 	}
207 207
 	// Upload the build context
208 208
 	v := &url.Values{}
... ...
@@ -205,7 +205,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression,
205 205
 	if err != nil {
206 206
 		return nil, err
207 207
 	}
208
-	return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp)
208
+	return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf, true, "", "Buffering to disk"), tmp)
209 209
 }
210 210
 
211 211
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
... ...
@@ -451,7 +451,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
451 451
 		return err
452 452
 	}
453 453
 
454
-	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, false), path); err != nil {
454
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, "", "Downloading"), path); err != nil {
455 455
 		return err
456 456
 	}
457 457
 	// FIXME: Handle custom repo, tag comment, author
... ...
@@ -761,7 +761,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
761 761
 	if err != nil {
762 762
 		return err
763 763
 	}
764
-	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependent layers"))
764
+	out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil))
765 765
 	// FIXME: Try to stream the images?
766 766
 	// FIXME: Launch the getRemoteImage() in goroutines
767 767
 
... ...
@@ -776,33 +776,33 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
776 776
 		defer srv.poolRemove("pull", "layer:"+id)
777 777
 
778 778
 		if !srv.runtime.graph.Exists(id) {
779
-			out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata"))
779
+			out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil))
780 780
 			imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
781 781
 			if err != nil {
782
-				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
782
+				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
783 783
 				// FIXME: Keep going in case of error?
784 784
 				return err
785 785
 			}
786 786
 			img, err := NewImgJSON(imgJSON)
787 787
 			if err != nil {
788
-				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
788
+				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
789 789
 				return fmt.Errorf("Failed to parse json: %s", err)
790 790
 			}
791 791
 
792 792
 			// Get the layer
793
-			out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "fs layer"))
793
+			out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling fs layer", nil))
794 794
 			layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
795 795
 			if err != nil {
796
-				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
796
+				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil))
797 797
 				return err
798 798
 			}
799 799
 			defer layer.Close()
800
-			if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf, false), img); err != nil {
801
-				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependent layers"))
800
+			if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), img); err != nil {
801
+				out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil))
802 802
 				return err
803 803
 			}
804 804
 		}
805
-		out.Write(sf.FormatProgress(utils.TruncateID(id), "Download", "complete"))
805
+		out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil))
806 806
 
807 807
 	}
808 808
 	return nil
... ...
@@ -875,29 +875,29 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName
875 875
 			}
876 876
 			defer srv.poolRemove("pull", "img:"+img.ID)
877 877
 
878
-			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Pulling", fmt.Sprintf("image (%s) from %s", img.Tag, localName)))
878
+			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
879 879
 			success := false
880 880
 			var lastErr error
881 881
 			for _, ep := range repoData.Endpoints {
882
-				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Pulling", fmt.Sprintf("image (%s) from %s, endpoint: %s", img.Tag, localName, ep)))
882
+				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
883 883
 				if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
884 884
 					// Its not ideal that only the last error  is returned, it would be better to concatenate the errors.
885 885
 					// As the error is also given to the output stream the user will see the error.
886 886
 					lastErr = err
887
-					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Error pulling", fmt.Sprintf("image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err)))
887
+					out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil))
888 888
 					continue
889 889
 				}
890 890
 				success = true
891 891
 				break
892 892
 			}
893 893
 			if !success {
894
-				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Error pulling", fmt.Sprintf("image (%s) from %s, %s", img.Tag, localName, lastErr)))
894
+				out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, %s", img.Tag, localName, lastErr), nil))
895 895
 				if parallel {
896 896
 					errors <- fmt.Errorf("Could not find repository on any of the indexed registries.")
897 897
 					return
898 898
 				}
899 899
 			}
900
-			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download", "complete"))
900
+			out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil))
901 901
 
902 902
 			if parallel {
903 903
 				errors <- nil
... ...
@@ -1171,7 +1171,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID,
1171 1171
 	defer os.RemoveAll(layerData.Name())
1172 1172
 
1173 1173
 	// Send the layer
1174
-	checksum, err = r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "Pushing", "%8v/%v (%v)"), sf, false), ep, token, jsonRaw)
1174
+	checksum, err = r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, "", "Pushing"), ep, token, jsonRaw)
1175 1175
 	if err != nil {
1176 1176
 		return "", err
1177 1177
 	}
... ...
@@ -1251,7 +1251,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
1251 1251
 		if err != nil {
1252 1252
 			return err
1253 1253
 		}
1254
-		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("", "Importing", "%8v/%v (%v)"), sf, true)
1254
+		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf, true, "", "Importing")
1255 1255
 	}
1256 1256
 	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
1257 1257
 	if err != nil {
1258 1258
new file mode 100644
... ...
@@ -0,0 +1,118 @@
0
+package utils
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"io"
6
+	"time"
7
+)
8
+
9
+type JSONError struct {
10
+	Code    int    `json:"code,omitempty"`
11
+	Message string `json:"message,omitempty"`
12
+}
13
+
14
+func (e *JSONError) Error() string {
15
+	return e.Message
16
+}
17
+
18
+type JSONProgress struct {
19
+	Current int `json:"current,omitempty"`
20
+	Total   int `json:"total,omitempty"`
21
+}
22
+
23
+func (p *JSONProgress) String() string {
24
+	if p.Current == 0 && p.Total == 0 {
25
+		return ""
26
+	}
27
+	current := HumanSize(int64(p.Current))
28
+	if p.Total == 0 {
29
+		return fmt.Sprintf("%8v/?", current)
30
+	}
31
+	total := HumanSize(int64(p.Total))
32
+	percentage := float64(p.Current) / float64(p.Total) * 100
33
+	return fmt.Sprintf("%8v/%v (%.0f%%)", current, total, percentage)
34
+}
35
+
36
+type JSONMessage struct {
37
+	Status          string        `json:"status,omitempty"`
38
+	Progress        *JSONProgress `json:"progressDetail,omitempty"`
39
+	ProgressMessage string        `json:"progress,omitempty"` //deprecated
40
+	ID              string        `json:"id,omitempty"`
41
+	From            string        `json:"from,omitempty"`
42
+	Time            int64         `json:"time,omitempty"`
43
+	Error           *JSONError    `json:"errorDetail,omitempty"`
44
+	ErrorMessage    string        `json:"error,omitempty"` //deprecated
45
+}
46
+
47
+func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
48
+	if jm.Error != nil {
49
+		if jm.Error.Code == 401 {
50
+			return fmt.Errorf("Authentication is required.")
51
+		}
52
+		return jm.Error
53
+	}
54
+	endl := ""
55
+	if isTerminal {
56
+		// <ESC>[2K = erase entire current line
57
+		fmt.Fprintf(out, "%c[2K\r", 27)
58
+		endl = "\r"
59
+	}
60
+	if jm.Time != 0 {
61
+		fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
62
+	}
63
+	if jm.ID != "" {
64
+		fmt.Fprintf(out, "%s: ", jm.ID)
65
+	}
66
+	if jm.From != "" {
67
+		fmt.Fprintf(out, "(from %s) ", jm.From)
68
+	}
69
+	if jm.Progress != nil {
70
+		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
71
+	} else if jm.ProgressMessage != "" { //deprecated
72
+		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
73
+	} else {
74
+		fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
75
+	}
76
+	return nil
77
+}
78
+
79
+func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error {
80
+	dec := json.NewDecoder(in)
81
+	ids := make(map[string]int)
82
+	diff := 0
83
+	for {
84
+		jm := JSONMessage{}
85
+		if err := dec.Decode(&jm); err == io.EOF {
86
+			break
87
+		} else if err != nil {
88
+			return err
89
+		}
90
+		if (jm.Progress != nil || jm.ProgressMessage != "") && jm.ID != "" {
91
+			line, ok := ids[jm.ID]
92
+			if !ok {
93
+				line = len(ids)
94
+				ids[jm.ID] = line
95
+				fmt.Fprintf(out, "\n")
96
+				diff = 0
97
+			} else {
98
+				diff = len(ids) - line
99
+			}
100
+			if isTerminal {
101
+				// <ESC>[{diff}A = move cursor up diff rows
102
+				fmt.Fprintf(out, "%c[%dA", 27, diff)
103
+			}
104
+		}
105
+		err := jm.Display(out, isTerminal)
106
+		if jm.ID != "" {
107
+			if isTerminal {
108
+				// <ESC>[{diff}B = move cursor down diff rows
109
+				fmt.Fprintf(out, "%c[%dB", 27, diff)
110
+			}
111
+		}
112
+		if err != nil {
113
+			return err
114
+		}
115
+	}
116
+	return nil
117
+}
0 118
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+package utils
1
+
2
+import (
3
+	"io"
4
+)
5
+
6
+// Reader with progress bar
7
+type progressReader struct {
8
+	reader   io.ReadCloser // Stream to read from
9
+	output   io.Writer     // Where to send progress bar to
10
+	progress JSONProgress
11
+	//	readTotal    int    // Expected stream length (bytes)
12
+	//	readProgress int    // How much has been read so far (bytes)
13
+	lastUpdate int // How many bytes read at least update
14
+	ID         string
15
+	action     string
16
+	//	template   string // Template to print. Default "%v/%v (%v)"
17
+	sf      *StreamFormatter
18
+	newLine bool
19
+}
20
+
21
+func (r *progressReader) Read(p []byte) (n int, err error) {
22
+	read, err := io.ReadCloser(r.reader).Read(p)
23
+	r.progress.Current += read
24
+	updateEvery := 1024 * 512 //512kB
25
+	if r.progress.Total > 0 {
26
+		// Update progress for every 1% read if 1% < 512kB
27
+		if increment := int(0.01 * float64(r.progress.Total)); increment < updateEvery {
28
+			updateEvery = increment
29
+		}
30
+	}
31
+	if r.progress.Current-r.lastUpdate > updateEvery || err != nil {
32
+		r.output.Write(r.sf.FormatProgress(r.ID, r.action, &r.progress))
33
+		r.lastUpdate = r.progress.Current
34
+	}
35
+	// Send newline when complete
36
+	if r.newLine && err != nil {
37
+		r.output.Write(r.sf.FormatStatus("", ""))
38
+	}
39
+	return read, err
40
+}
41
+func (r *progressReader) Close() error {
42
+	return io.ReadCloser(r.reader).Close()
43
+}
44
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, sf *StreamFormatter, newline bool, ID, action string) *progressReader {
45
+	return &progressReader{
46
+		reader:   r,
47
+		output:   NewWriteFlusher(output),
48
+		ID:       ID,
49
+		action:   action,
50
+		progress: JSONProgress{Total: size},
51
+		sf:       sf,
52
+		newLine:  newline,
53
+	}
54
+}
0 55
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+package utils
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+)
6
+
7
+type StreamFormatter struct {
8
+	json bool
9
+	used bool
10
+}
11
+
12
+func NewStreamFormatter(json bool) *StreamFormatter {
13
+	return &StreamFormatter{json, false}
14
+}
15
+
16
+func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte {
17
+	sf.used = true
18
+	str := fmt.Sprintf(format, a...)
19
+	if sf.json {
20
+		b, err := json.Marshal(&JSONMessage{ID: id, Status: str})
21
+		if err != nil {
22
+			return sf.FormatError(err)
23
+		}
24
+		return b
25
+	}
26
+	return []byte(str + "\r\n")
27
+}
28
+
29
+func (sf *StreamFormatter) FormatError(err error) []byte {
30
+	sf.used = true
31
+	if sf.json {
32
+		jsonError, ok := err.(*JSONError)
33
+		if !ok {
34
+			jsonError = &JSONError{Message: err.Error()}
35
+		}
36
+		if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
37
+			return b
38
+		}
39
+		return []byte("{\"error\":\"format error\"}")
40
+	}
41
+	return []byte("Error: " + err.Error() + "\r\n")
42
+}
43
+
44
+func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgress) []byte {
45
+	if progress == nil {
46
+		progress = &JSONProgress{}
47
+	}
48
+	sf.used = true
49
+	if sf.json {
50
+
51
+		b, err := json.Marshal(&JSONMessage{
52
+			Status:          action,
53
+			ProgressMessage: progress.String(),
54
+			Progress:        progress,
55
+			ID:              id,
56
+		})
57
+		if err != nil {
58
+			return nil
59
+		}
60
+		return b
61
+	}
62
+	return []byte(action + " " + progress.String() + "\r")
63
+}
64
+
65
+func (sf *StreamFormatter) Used() bool {
66
+	return sf.used
67
+}
... ...
@@ -94,56 +94,6 @@ func Errorf(format string, a ...interface{}) {
94 94
 	logf("error", format, a...)
95 95
 }
96 96
 
97
-// Reader with progress bar
98
-type progressReader struct {
99
-	reader       io.ReadCloser // Stream to read from
100
-	output       io.Writer     // Where to send progress bar to
101
-	readTotal    int           // Expected stream length (bytes)
102
-	readProgress int           // How much has been read so far (bytes)
103
-	lastUpdate   int           // How many bytes read at least update
104
-	template     string        // Template to print. Default "%v/%v (%v)"
105
-	sf           *StreamFormatter
106
-	newLine      bool
107
-}
108
-
109
-func (r *progressReader) Read(p []byte) (n int, err error) {
110
-	read, err := io.ReadCloser(r.reader).Read(p)
111
-	r.readProgress += read
112
-	updateEvery := 1024 * 512 //512kB
113
-	if r.readTotal > 0 {
114
-		// Update progress for every 1% read if 1% < 512kB
115
-		if increment := int(0.01 * float64(r.readTotal)); increment < updateEvery {
116
-			updateEvery = increment
117
-		}
118
-	}
119
-	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
120
-		if r.readTotal > 0 {
121
-			fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
122
-		} else {
123
-			fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
124
-		}
125
-		r.lastUpdate = r.readProgress
126
-	}
127
-	// Send newline when complete
128
-	if r.newLine && err != nil {
129
-		r.output.Write(r.sf.FormatStatus("", ""))
130
-	}
131
-	return read, err
132
-}
133
-func (r *progressReader) Close() error {
134
-	return io.ReadCloser(r.reader).Close()
135
-}
136
-func ProgressReader(r io.ReadCloser, size int, output io.Writer, tpl []byte, sf *StreamFormatter, newline bool) *progressReader {
137
-	return &progressReader{
138
-		reader:    r,
139
-		output:    NewWriteFlusher(output),
140
-		readTotal: size,
141
-		template:  string(tpl),
142
-		sf:        sf,
143
-		newLine:   newline,
144
-	}
145
-}
146
-
147 97
 // HumanDuration returns a human-readable approximation of a duration
148 98
 // (eg. "About a minute", "4 hours ago", etc.)
149 99
 func HumanDuration(d time.Duration) string {
... ...
@@ -754,25 +704,6 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
754 754
 	return &WriteFlusher{w: w, flusher: flusher}
755 755
 }
756 756
 
757
-type JSONError struct {
758
-	Code    int    `json:"code,omitempty"`
759
-	Message string `json:"message,omitempty"`
760
-}
761
-
762
-type JSONMessage struct {
763
-	Status       string     `json:"status,omitempty"`
764
-	Progress     string     `json:"progress,omitempty"`
765
-	ErrorMessage string     `json:"error,omitempty"` //deprecated
766
-	ID           string     `json:"id,omitempty"`
767
-	From         string     `json:"from,omitempty"`
768
-	Time         int64      `json:"time,omitempty"`
769
-	Error        *JSONError `json:"errorDetail,omitempty"`
770
-}
771
-
772
-func (e *JSONError) Error() string {
773
-	return e.Message
774
-}
775
-
776 757
 func NewHTTPRequestError(msg string, res *http.Response) error {
777 758
 	return &JSONError{
778 759
 		Message: msg,
... ...
@@ -780,129 +711,6 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
780 780
 	}
781 781
 }
782 782
 
783
-func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
784
-	if jm.Error != nil {
785
-		if jm.Error.Code == 401 {
786
-			return fmt.Errorf("Authentication is required.")
787
-		}
788
-		return jm.Error
789
-	}
790
-	endl := ""
791
-	if isTerminal {
792
-		// <ESC>[2K = erase entire current line
793
-		fmt.Fprintf(out, "%c[2K\r", 27)
794
-		endl = "\r"
795
-	}
796
-	if jm.Time != 0 {
797
-		fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
798
-	}
799
-	if jm.ID != "" {
800
-		fmt.Fprintf(out, "%s: ", jm.ID)
801
-	}
802
-	if jm.From != "" {
803
-		fmt.Fprintf(out, "(from %s) ", jm.From)
804
-	}
805
-	if jm.Progress != "" {
806
-		fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress, endl)
807
-	} else {
808
-		fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
809
-	}
810
-	return nil
811
-}
812
-
813
-func DisplayJSONMessagesStream(in io.Reader, out io.Writer, isTerminal bool) error {
814
-	dec := json.NewDecoder(in)
815
-	ids := make(map[string]int)
816
-	diff := 0
817
-	for {
818
-		jm := JSONMessage{}
819
-		if err := dec.Decode(&jm); err == io.EOF {
820
-			break
821
-		} else if err != nil {
822
-			return err
823
-		}
824
-		if jm.Progress != "" && jm.ID != "" {
825
-			line, ok := ids[jm.ID]
826
-			if !ok {
827
-				line = len(ids)
828
-				ids[jm.ID] = line
829
-				fmt.Fprintf(out, "\n")
830
-				diff = 0
831
-			} else {
832
-				diff = len(ids) - line
833
-			}
834
-			if isTerminal {
835
-				// <ESC>[{diff}A = move cursor up diff rows
836
-				fmt.Fprintf(out, "%c[%dA", 27, diff)
837
-			}
838
-		}
839
-		err := jm.Display(out, isTerminal)
840
-		if jm.ID != "" {
841
-			if isTerminal {
842
-				// <ESC>[{diff}B = move cursor down diff rows
843
-				fmt.Fprintf(out, "%c[%dB", 27, diff)
844
-			}
845
-		}
846
-		if err != nil {
847
-			return err
848
-		}
849
-	}
850
-	return nil
851
-}
852
-
853
-type StreamFormatter struct {
854
-	json bool
855
-	used bool
856
-}
857
-
858
-func NewStreamFormatter(json bool) *StreamFormatter {
859
-	return &StreamFormatter{json, false}
860
-}
861
-
862
-func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte {
863
-	sf.used = true
864
-	str := fmt.Sprintf(format, a...)
865
-	if sf.json {
866
-		b, err := json.Marshal(&JSONMessage{ID: id, Status: str})
867
-		if err != nil {
868
-			return sf.FormatError(err)
869
-		}
870
-		return b
871
-	}
872
-	return []byte(str + "\r\n")
873
-}
874
-
875
-func (sf *StreamFormatter) FormatError(err error) []byte {
876
-	sf.used = true
877
-	if sf.json {
878
-		jsonError, ok := err.(*JSONError)
879
-		if !ok {
880
-			jsonError = &JSONError{Message: err.Error()}
881
-		}
882
-		if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
883
-			return b
884
-		}
885
-		return []byte("{\"error\":\"format error\"}")
886
-	}
887
-	return []byte("Error: " + err.Error() + "\r\n")
888
-}
889
-
890
-func (sf *StreamFormatter) FormatProgress(id, action, progress string) []byte {
891
-	sf.used = true
892
-	if sf.json {
893
-		b, err := json.Marshal(&JSONMessage{Status: action, Progress: progress, ID: id})
894
-		if err != nil {
895
-			return nil
896
-		}
897
-		return b
898
-	}
899
-	return []byte(action + " " + progress + "\r")
900
-}
901
-
902
-func (sf *StreamFormatter) Used() bool {
903
-	return sf.used
904
-}
905
-
906 783
 func IsURL(str string) bool {
907 784
 	return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://")
908 785
 }