Adding Download functionality

Adding Unzip utility
Adding Download options to DOCS.md

Signed-off-by: Jonas Franz <info@jonasfranz.de>
development
Jonas Franz 7 years ago
parent 45c74000a1
commit e5ca1a26c0
  1. 9
      DOCS.md
  2. 16
      main.go
  3. 97
      plugin.go
  4. 4
      plugin_test.go
  5. 24
      responses/responses.go
  6. 67
      utils/unzip.go

@ -8,6 +8,9 @@ You must provide in your configuration:
* key: the Crowdin file name
* value: the real path the to file
* `ignore_branch` It will send the Drone branch to Crowdin if it is `false`. (Default: `false`)
* `download` Downloads translated files from Crowdin if it is `true`. (Default: `false`)
* `export_dir` Export directory of the translated strings
* `languages` Languages which should be downloaded/exported from Crowdin. (Default: `all`)
Information about API keys: https://support.crowdin.com/api/api-integration-setup/
## Example
@ -23,4 +26,10 @@ pipeline:
files:
example: options/example.ini
example2: options/example2.ini
ignore_branch: true
download: true
export_dir: langs/
languages:
- de
- fr
```

@ -44,6 +44,22 @@ func main() {
Usage: "if true it will not pass the branch to crowdin",
EnvVar: "PLUGIN_IGNORE_BRANCH",
},
cli.StringFlag{
Name: "export-dir",
Usage: "the directory where the translated files should be extracted in",
EnvVar: "PLUGIN_EXPORT_DIR",
},
cli.BoolFlag{
Name: "download",
Usage: "downloads translated files if true",
EnvVar: "PLUGIN_DOWNLOAD",
},
cli.StringSliceFlag{
Name: "languages",
Usage: "the languages that should be exported",
EnvVar: "PLUGIN_LANGUAGES",
Value: &cli.StringSlice{"all"},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)

@ -5,6 +5,7 @@ import (
"encoding/xml"
"fmt"
"github.com/JonasFranzDEV/drone-crowdin/responses"
"github.com/JonasFranzDEV/drone-crowdin/utils"
"golang.org/x/net/html/charset"
"io"
"mime/multipart"
@ -27,12 +28,27 @@ type (
Config Config
Files Files
Branch string
Languages []string
ExportDirectory string
DoDownload bool
}
)
// ToURL returns the API-endpoint including identifier and API-KEY
func (c Config) ToURL() string {
return fmt.Sprintf("https://api.crowdin.com/api/project/%s/update-file?key=%s", c.Identifier, c.Key)
func (c Config) ToProjectURL() string {
return fmt.Sprintf("https://api/crowdin.com/api/project/%s", c.Identifier)
}
// ToUploadURL returns the API-endpoint including identifier and API-KEY
func (c Config) ToUploadURL() string {
return fmt.Sprintf("%s/update-file?key=%s", c.ToProjectURL(), c.Key)
}
func (p Plugin) ToLanguageDownloadURL(language string) string {
if p.Branch != "" {
return fmt.Sprintf("%s/%s.zip?key=%s&branch=%s", p.Config.ToProjectURL(), language, p.Config.Key, p.Branch)
}
return fmt.Sprintf("%s/%s.zip?key=%s", p.Config.ToProjectURL(), language, p.Config.Key)
}
// Exec starts the plugin and updates the crowdin translation by uploading files from the files map
@ -68,7 +84,7 @@ func (p Plugin) Exec() error {
}
var req *http.Request
var err error
if req, err = http.NewRequest("POST", p.Config.ToURL(), body); err != nil {
if req, err = http.NewRequest("POST", p.Config.ToUploadURL(), body); err != nil {
return err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
@ -87,22 +103,79 @@ func (p Plugin) Exec() error {
return err
}
if resp.StatusCode != 200 {
var errResponse = new(responses.Error)
decoder := xml.NewDecoder(body)
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&errResponse); err != nil {
if e, err := responses.ParseAsError(body); err != nil {
return err
} else {
return e
}
return errResponse
}
var success = new(responses.Success)
decoder := xml.NewDecoder(body)
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&success); err != nil {
if success, err = responses.ParseAsSuccess(body); err != nil {
return err
}
for _, file := range success.Stats {
fmt.Printf("%s: %s\n", file.Name, file.Status)
}
if p.DoDownload {
for _, language := range p.Languages {
if err := p.downloadLanguage(client, language); err != nil {
return err
}
fmt.Printf("Downloaded package: %s\n", language)
}
}
return nil
}
func (p Plugin) downloadLanguage(client *http.Client, language string) error {
// Step 1: Export translations (aka generate server side)
exportURL := fmt.Sprintf("%s/export?key=%s", p.Config.ToProjectURL(), p.Config.Key)
if p.Branch != "" {
exportURL = fmt.Sprintf("%s&branch=%s", exportURL, p.Branch)
}
if resp, err := client.Get(exportURL); err != nil {
return err
} else if resp.StatusCode != 200 {
defer resp.Body.Close()
if e, err := responses.ParseAsError(resp.Body); err != nil {
return err
} else {
return e
}
} else {
defer resp.Body.Close()
}
file, err := downloadFromUrl(p.ToLanguageDownloadURL(language))
if err != nil {
return err
}
err = utils.Unzip(file.Name(), p.ExportDirectory)
if err != nil {
return err
}
err = os.Remove(file.Name())
if err != nil {
return err
}
return nil
}
func downloadFromUrl(url string) (*os.File, error) {
output, err := os.Create("lang.zip")
if err != nil {
return nil, err
}
defer output.Close()
response, err := http.Get(url)
if err != nil {
os.Remove(output.Name())
return nil, err
}
defer response.Body.Close()
_, err = io.Copy(output, response.Body)
return output, err
}

@ -4,8 +4,8 @@ import "testing"
func TestConfig_ToURL(t *testing.T) {
exampleConfig := &Config{Identifier: "test", Key: "MYKEY"}
result := exampleConfig.ToURL()
result := exampleConfig.ToUploadURL()
if result != "https://api.crowdin.com/api/project/test/update-file?key=MYKEY" {
t.Fatalf("ToURL returns \"%s\" instead of the expected \"%s\"", result, "https://api.crowdin.com/api/project/test/update-file?key=MYKEY")
t.Fatalf("ToUploadURL returns \"%s\" instead of the expected \"%s\"", result, "https://api.crowdin.com/api/project/test/update-file?key=MYKEY")
}
}

@ -3,6 +3,8 @@ package responses
import (
"encoding/xml"
"fmt"
"golang.org/x/net/html/charset"
"io"
)
// Error is a crowdin error message
@ -17,12 +19,34 @@ func (e *Error) Error() string {
return fmt.Sprintf("Error from crowdin: %s (error code %d)", e.Message, e.Code)
}
// ParseAsError parses XML to Error
func ParseAsError(body io.Reader) (*Error, error) {
var errResponse = new(Error)
decoder := xml.NewDecoder(body)
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&errResponse); err != nil {
return nil, err
}
return errResponse, nil
}
// Success is a crowdin success message
type Success struct {
XMLName xml.Name `xml:"success"`
Stats []File `xml:"stats>file"`
}
// ParseAsSuccess parses XML to Success
func ParseAsSuccess(body io.Reader) (*Success, error) {
var success = new(Success)
decoder := xml.NewDecoder(body)
decoder.CharsetReader = charset.NewReaderLabel
if err := decoder.Decode(&success); err != nil {
return nil, err
}
return success, nil
}
// File represents the status of an uploaded file
type File struct {
XMLName xml.Name `xml:"file"`

@ -0,0 +1,67 @@
package utils
import (
"archive/zip"
"io"
"os"
"path/filepath"
)
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer func() {
if err := r.Close(); err != nil {
panic(err)
}
}()
os.MkdirAll(dest, 0755)
// Closure to address file descriptors issue with all the deferred .Close() methods
extractAndWriteFile := func(f *zip.File) error {
rc, err := f.Open()
if err != nil {
return err
}
defer func() {
if err := rc.Close(); err != nil {
panic(err)
}
}()
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode())
} else {
os.MkdirAll(filepath.Dir(path), f.Mode())
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer func() {
if err := f.Close(); err != nil {
panic(err)
}
}()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
return nil
}
for _, f := range r.File {
err := extractAndWriteFile(f)
if err != nil {
return err
}
}
return nil
}
Loading…
Cancel
Save