grim/youtrack-import
Clone
Summary
Browse
Changes
Graph
use `unset` as the value when a state is moving to/from an unknown states. Fixes YI-27
2020-01-17, Gary Kramlich
741af4ca4d62
use `unset` as the value when a state is moving to/from an unknown states. Fixes YI-27
package
youtrack
import
(
"fmt"
"io"
"mime"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/avast/retry-go"
)
type
Attachment
struct
{
issue
int
author
string
created
time
.
Time
payload
*
strings
.
Builder
writer
*
multipart
.
Writer
files
[]
string
}
func
NewAttachment
(
issue
int
,
author
string
,
created
time
.
Time
)
*
Attachment
{
a
:=
&
Attachment
{
issue
:
issue
,
author
:
author
,
created
:
created
,
files
:
[]
string
{},
}
a
.
payload
=
&
strings
.
Builder
{}
a
.
writer
=
multipart
.
NewWriter
(
a
.
payload
)
return
a
}
func
(
a
*
Attachment
)
Payload
()
(
string
,
error
)
{
if
err
:=
a
.
writer
.
Close
();
err
!=
nil
{
return
""
,
err
}
return
a
.
payload
.
String
(),
nil
}
var
quoteEscaper
=
strings
.
NewReplacer
(
"\\"
,
"\\\\"
,
`"`
,
"\\\""
)
func
escapeQuotes
(
s
string
)
string
{
return
quoteEscaper
.
Replace
(
s
)
}
func
(
a
*
Attachment
)
AddFile
(
r
io
.
ReadCloser
,
filename
string
)
error
{
header
:=
textproto
.
MIMEHeader
{}
header
.
Set
(
"Content-Disposition"
,
fmt
.
Sprintf
(
"form-data; name=%q; filename=%q"
,
escapeQuotes
(
filename
),
escapeQuotes
(
filename
),
),
)
contentType
:=
""
ext
:=
filepath
.
Ext
(
filename
)
if
ext
!=
""
{
contentType
=
mime
.
TypeByExtension
(
ext
)
}
if
contentType
==
""
{
contentType
=
"application/octet-stream"
}
header
.
Set
(
"Content-Type"
,
contentType
)
w
,
err
:=
a
.
writer
.
CreatePart
(
header
)
if
err
!=
nil
{
return
err
}
_
,
err
=
io
.
Copy
(
w
,
r
)
if
err
!=
nil
{
return
err
}
defer
r
.
Close
()
a
.
files
=
append
(
a
.
files
,
filename
)
return
nil
}
func
(
a
*
Attachment
)
ContentType
()
string
{
return
a
.
writer
.
FormDataContentType
()
}
func
(
c
*
Client
)
waitForIssue
(
issueID
string
)
error
{
return
retry
.
Do
(
func
()
error
{
resp
,
err
:=
c
.
request
(
http
.
MethodGet
,
c
.
uri
+
"/issue/"
+
issueID
,
nil
,
)
if
err
!=
nil
{
return
err
}
defer
resp
.
Body
.
Close
()
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
err
}
return
nil
},
retry
.
Attempts
(
3
),
)
}
func
(
c
*
Client
)
importAttachment
(
p
*
Project
,
a
*
Attachment
)
error
{
issueID
:=
fmt
.
Sprintf
(
"%s-%d"
,
p
.
ID
,
a
.
issue
)
err
:=
c
.
waitForIssue
(
issueID
)
if
err
!=
nil
{
return
err
}
values
:=
url
.
Values
{}
values
.
Add
(
"authorLogin"
,
a
.
author
)
values
.
Add
(
"created"
,
formatTimeString
(
a
.
created
))
payload
,
err
:=
a
.
Payload
()
if
err
!=
nil
{
return
err
}
reader
:=
strings
.
NewReader
(
payload
)
resp
,
err
:=
c
.
requestContentType
(
http
.
MethodPost
,
c
.
uri
+
"/import/"
+
issueID
+
"/attachment?"
+
values
.
Encode
(),
a
.
ContentType
(),
reader
,
)
if
err
!=
nil
{
return
err
}
defer
resp
.
Body
.
Close
()
return
c
.
validateXmlImportReport
(
[]
int
{
http
.
StatusOK
,
http
.
StatusCreated
,
http
.
StatusBadRequest
},
resp
,
)
}
func
(
c
*
Client
)
ImportAttachments
(
p
*
Project
)
error
{
for
_
,
issue
:=
range
p
.
Issues
{
l
:=
len
(
issue
.
Attachments
)
for
idx
,
attachment
:=
range
issue
.
Attachments
{
fmt
.
Printf
(
"importing attachment for issue %d: (%d of %d) ... "
,
attachment
.
issue
,
idx
+
1
,
l
,
)
if
err
:=
c
.
importAttachment
(
p
,
attachment
);
err
!=
nil
{
fmt
.
Printf
(
"failed.\n"
)
return
err
}
fmt
.
Printf
(
"done.\n"
)
}
}
return
nil
}
func
extractAttachments
(
issue
int
,
author
,
message
string
,
created
time
.
Time
)
(
string
,
[]
*
Attachment
,
error
)
{
pattern
:=
`!\[(.*?)\]\((.+?)(\s+["'].*?["'])?\)`
regex
:=
regexp
.
MustCompile
(
pattern
)
output
:=
message
attachments
:=
[]
*
Attachment
{}
matches
:=
regex
.
FindAllStringSubmatch
(
message
,
-
1
)
for
_
,
match
:=
range
matches
{
old
:=
match
[
0
]
filename
:=
match
[
1
]
uri
:=
match
[
2
]
if
filename
==
""
{
decoded
,
err
:=
url
.
QueryUnescape
(
uri
)
if
err
!=
nil
{
return
""
,
attachments
,
err
}
filename
=
filepath
.
Base
(
decoded
)
}
img
:=
fmt
.
Sprintf
(
"![](%s)"
,
filename
)
resp
,
err
:=
http
.
Get
(
uri
)
if
err
!=
nil
{
return
""
,
attachments
,
err
}
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
""
,
attachments
,
fmt
.
Errorf
(
"unexpected status code %d"
,
resp
.
StatusCode
)
}
attachment
:=
NewAttachment
(
issue
,
author
,
created
)
if
err
:=
attachment
.
AddFile
(
resp
.
Body
,
filename
);
err
!=
nil
{
return
""
,
attachments
,
err
}
output
=
strings
.
Replace
(
output
,
old
,
img
,
1
)
attachments
=
append
(
attachments
,
attachment
)
}
return
output
,
attachments
,
nil
}