github.com/cobalt77/jfrog-client-go@v0.14.5/bintray/services/download.go (about) 1 package services 2 3 import ( 4 "encoding/json" 5 "errors" 6 "net/http" 7 "path" 8 "path/filepath" 9 "strconv" 10 "strings" 11 "sync" 12 13 "github.com/cobalt77/jfrog-client-go/bintray/auth" 14 "github.com/cobalt77/jfrog-client-go/bintray/services/utils" 15 "github.com/cobalt77/jfrog-client-go/bintray/services/versions" 16 "github.com/cobalt77/jfrog-client-go/httpclient" 17 clientutils "github.com/cobalt77/jfrog-client-go/utils" 18 logutil "github.com/cobalt77/jfrog-client-go/utils" 19 "github.com/cobalt77/jfrog-client-go/utils/errorutils" 20 "github.com/cobalt77/jfrog-client-go/utils/io/fileutils" 21 "github.com/cobalt77/jfrog-client-go/utils/log" 22 ) 23 24 func NewDownloadService(client *httpclient.HttpClient) *DownloadService { 25 ds := &DownloadService{client: client} 26 return ds 27 } 28 29 func NewDownloadFileParams() *DownloadFileParams { 30 return &DownloadFileParams{PathDetails: &utils.PathDetails{}} 31 } 32 33 func NewDownloadVersionParams() *DownloadVersionParams { 34 return &DownloadVersionParams{Params: &versions.Params{}} 35 } 36 37 type DownloadService struct { 38 client *httpclient.HttpClient 39 BintrayDetails auth.BintrayDetails 40 Threads int 41 } 42 43 type DownloadFileParams struct { 44 *utils.PathDetails 45 TargetPath string 46 IncludeUnpublished bool 47 Flat bool 48 MinSplitSize int64 49 SplitCount int 50 } 51 52 type DownloadVersionParams struct { 53 *versions.Params 54 TargetPath string 55 IncludeUnpublished bool 56 } 57 58 func (ds *DownloadService) DownloadFile(downloadParams *DownloadFileParams) (totalDownloaded, totalFailed int, err error) { 59 if ds.BintrayDetails.GetUser() == "" { 60 ds.BintrayDetails.SetUser(downloadParams.Subject) 61 } 62 63 err = ds.downloadBintrayFile(downloadParams, "") 64 if err != nil { 65 return 0, 1, err 66 } 67 log.Info("Downloaded 1 artifact.") 68 return 1, 0, nil 69 } 70 71 func (ds *DownloadService) DownloadVersion(downloadParams *DownloadVersionParams) (totalDownloaded, totalFailed int, err error) { 72 versionPathUrl := buildDownloadVersionUrl(ds.BintrayDetails.GetApiUrl(), downloadParams) 73 httpClientsDetails := ds.BintrayDetails.CreateHttpClientDetails() 74 if httpClientsDetails.User == "" { 75 httpClientsDetails.User = downloadParams.Subject 76 } 77 client, err := httpclient.ClientBuilder().Build() 78 if err != nil { 79 return 80 } 81 resp, body, _, _ := client.SendGet(versionPathUrl, true, httpClientsDetails) 82 if resp.StatusCode != http.StatusOK { 83 err = errorutils.CheckError(errors.New(resp.Status + ". " + utils.ReadBintrayMessage(body))) 84 return 85 } 86 var files []VersionFilesResult 87 err = json.Unmarshal(body, &files) 88 if errorutils.CheckError(err) != nil { 89 return 90 } 91 92 totalDownloaded, err = ds.downloadVersionFiles(files, downloadParams) 93 log.Info("Downloaded", strconv.Itoa(totalDownloaded), "artifacts.") 94 totalFailed = len(files) - totalDownloaded 95 return 96 } 97 98 func buildDownloadVersionUrl(apiUrl string, downloadParams *DownloadVersionParams) string { 99 urlPath := apiUrl + path.Join("packages/", downloadParams.Subject, downloadParams.Repo, downloadParams.Package, "versions", downloadParams.Version, "files") 100 if downloadParams.IncludeUnpublished { 101 urlPath += "?include_unpublished=1" 102 } 103 return urlPath 104 } 105 106 func (ds *DownloadService) downloadVersionFiles(files []VersionFilesResult, downloadParams *DownloadVersionParams) (totalDownloaded int, err error) { 107 size := len(files) 108 downloadedForThread := make([]int, ds.Threads) 109 var wg sync.WaitGroup 110 for i := 0; i < ds.Threads; i++ { 111 wg.Add(1) 112 go func(threadId int) { 113 logMsgPrefix := logutil.GetLogMsgPrefix(threadId, false) 114 for j := threadId; j < size; j += ds.Threads { 115 pathDetails := &utils.PathDetails{ 116 Subject: downloadParams.Subject, 117 Repo: downloadParams.Repo, 118 Path: files[j].Path} 119 120 downloadFileParams := &DownloadFileParams{PathDetails: pathDetails, TargetPath: downloadParams.TargetPath} 121 e := ds.downloadBintrayFile(downloadFileParams, logMsgPrefix) 122 if e != nil { 123 err = e 124 continue 125 } 126 downloadedForThread[threadId]++ 127 } 128 wg.Done() 129 }(i) 130 } 131 wg.Wait() 132 133 for i := range downloadedForThread { 134 totalDownloaded += downloadedForThread[i] 135 } 136 return 137 } 138 139 func CreateVersionDetailsForDownloadVersion(versionStr string) (*versions.Path, error) { 140 parts := strings.Split(versionStr, "/") 141 if len(parts) != 4 { 142 err := errorutils.CheckError(errors.New("Argument format should be subject/repository/package/version. Got " + versionStr)) 143 if err != nil { 144 return nil, err 145 } 146 } 147 return versions.CreatePath(versionStr) 148 } 149 150 type VersionFilesResult struct { 151 Path string 152 } 153 154 func (ds *DownloadService) downloadBintrayFile(downloadParams *DownloadFileParams, logMsgPrefix string) error { 155 cleanPath := strings.Replace(downloadParams.Path, "(", "", -1) 156 cleanPath = strings.Replace(cleanPath, ")", "", -1) 157 downloadPath := path.Join(downloadParams.Subject, downloadParams.Repo, cleanPath) 158 159 fileName, filePath := fileutils.GetFileAndDirFromPath(cleanPath) 160 161 url := ds.BintrayDetails.GetDownloadServerUrl() + downloadPath 162 if downloadParams.IncludeUnpublished { 163 url += "?include_unpublished=1" 164 } 165 log.Info(logMsgPrefix+"Downloading", downloadPath) 166 client, err := httpclient.ClientBuilder().Build() 167 if err != nil { 168 return err 169 } 170 171 httpClientsDetails := ds.BintrayDetails.CreateHttpClientDetails() 172 details, resp, err := client.GetRemoteFileDetails(url, httpClientsDetails) 173 if err != nil { 174 return errorutils.CheckError(errors.New("Bintray " + err.Error())) 175 } 176 err = errorutils.CheckResponseStatus(resp, http.StatusOK) 177 if errorutils.CheckError(err) != nil { 178 return err 179 } 180 181 placeHolderTarget, err := clientutils.BuildTargetPath(downloadParams.Path, cleanPath, downloadParams.TargetPath, false) 182 if err != nil { 183 return err 184 } 185 186 localPath, localFileName := fileutils.GetLocalPathAndFile(fileName, filePath, placeHolderTarget, downloadParams.Flat) 187 188 var shouldDownload bool 189 shouldDownload, err = shouldDownloadFile(filepath.Join(localPath, localFileName), details) 190 if err != nil { 191 return err 192 } 193 if !shouldDownload { 194 log.Info(logMsgPrefix, "File already exists locally.") 195 return nil 196 } 197 198 // Check if the file should be downloaded concurrently. 199 if downloadParams.SplitCount == 0 || downloadParams.MinSplitSize < 0 || downloadParams.MinSplitSize*1000 > details.Size { 200 // File should not be downloaded concurrently. Download it as one block. 201 downloadDetails := &httpclient.DownloadFileDetails{ 202 FileName: fileName, 203 DownloadPath: url, 204 LocalPath: localPath, 205 LocalFileName: localFileName} 206 207 resp, err := client.DownloadFile(downloadDetails, logMsgPrefix, httpClientsDetails, utils.BintrayDownloadRetries, false) 208 if err != nil { 209 return err 210 } 211 log.Debug(logMsgPrefix, "Bintray response:", resp.Status) 212 return errorutils.CheckResponseStatus(resp, http.StatusOK) 213 } else { 214 // We should attempt to download the file concurrently, but only if it is provided through the DSN. 215 // To check if the file is provided through the DSN, we first attempt to download the file 216 // with 'follow redirect' disabled. 217 218 var resp *http.Response 219 var redirectUrl string 220 resp, redirectUrl, err = 221 client.DownloadFileNoRedirect(url, localPath, localFileName, httpClientsDetails, utils.BintrayDownloadRetries) 222 // There are two options now. Either the file has just been downloaded as one block, or 223 // we got a redirect to DSN download URL. In case of the later, we should download the file 224 // concurrently from the DSN URL. 225 // 'err' is not nil in case 'redirectUrl' was returned. 226 if redirectUrl != "" { 227 err = nil 228 concurrentDownloadFlags := httpclient.ConcurrentDownloadFlags{ 229 DownloadPath: redirectUrl, 230 FileName: localFileName, 231 LocalFileName: localFileName, 232 LocalPath: localPath, 233 FileSize: details.Size, 234 SplitCount: downloadParams.SplitCount, 235 Retries: utils.BintrayDownloadRetries} 236 237 resp, err = client.DownloadFileConcurrently(concurrentDownloadFlags, "", httpClientsDetails, nil) 238 if errorutils.CheckError(err) != nil { 239 return err 240 } 241 err = errorutils.CheckResponseStatus(resp, http.StatusPartialContent) 242 if err != nil { 243 return err 244 } 245 } else { 246 if errorutils.CheckError(err) != nil { 247 return err 248 } 249 err = errorutils.CheckResponseStatus(resp, http.StatusOK) 250 if err != nil { 251 return err 252 } 253 log.Debug(logMsgPrefix, "Bintray response:", resp.Status) 254 } 255 } 256 return nil 257 } 258 259 func shouldDownloadFile(localFilePath string, remoteFileDetails *fileutils.FileDetails) (bool, error) { 260 exists, err := fileutils.IsFileExists(localFilePath, false) 261 if err != nil { 262 return false, err 263 } 264 if !exists { 265 return true, nil 266 } 267 localFileDetails, err := fileutils.GetFileDetails(localFilePath) 268 if err != nil { 269 return false, err 270 } 271 return localFileDetails.Checksum.Sha1 != remoteFileDetails.Checksum.Sha1, nil 272 }