1#!/usr/bin/env python3 2############################################################################### 3# $Id: gdal_cp.py faa1d4a1e4ec5876bf06c69377920f635779126a 2021-04-22 11:13:58 +0200 Even Rouault $ 4# 5# Project: GDAL samples 6# Purpose: Copy a virtual file 7# Author: Even Rouault <even dot rouault at spatialys.com> 8# 9############################################################################### 10# Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com> 11# 12# Permission is hereby granted, free of charge, to any person obtaining a 13# copy of this software and associated documentation files (the "Software"), 14# to deal in the Software without restriction, including without limitation 15# the rights to use, copy, modify, merge, publish, distribute, sublicense, 16# and/or sell copies of the Software, and to permit persons to whom the 17# Software is furnished to do so, subject to the following conditions: 18# 19# The above copyright notice and this permission notice shall be included 20# in all copies or substantial portions of the Software. 21# 22# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 23# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 25# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28# DEALINGS IN THE SOFTWARE. 29############################################################################### 30 31import fnmatch 32import os 33import stat 34import sys 35 36from osgeo import gdal 37 38 39def needsVSICurl(filename): 40 return filename.startswith('http://') or filename.startswith('https://') or filename.startswith('ftp://') 41 42 43def Usage(): 44 print('Usage: gdal_cp [-progress] [-r] [-skipfailures] source_file target_file') 45 return -1 46 47 48class TermProgress(object): 49 def __init__(self): 50 self.nLastTick = -1 51 self.nThisTick = 0 52 53 def Progress(self, dfComplete, message): 54 # pylint: disable=unused-argument 55 self.nThisTick = int(dfComplete * 40.0) 56 if self.nThisTick > 40: 57 self.nThisTick = 40 58 59 # // Have we started a new progress run? 60 if self.nThisTick < self.nLastTick and self.nLastTick >= 39: 61 self.nLastTick = -1 62 63 if self.nThisTick <= self.nLastTick: 64 return True 65 66 while self.nThisTick > self.nLastTick: 67 self.nLastTick = self.nLastTick + 1 68 if self.nLastTick % 4 == 0: 69 val = int((self.nLastTick / 4) * 10) 70 sys.stdout.write("%d" % val) 71 else: 72 sys.stdout.write(".") 73 74 if self.nThisTick == 40: 75 print(" - done.") 76 77 sys.stdout.flush() 78 79 return True 80 81 82class ScaledProgress(object): 83 def __init__(self, dfMin, dfMax, UnderlyingProgress): 84 self.dfMin = dfMin 85 self.dfMax = dfMax 86 self.UnderlyingProgress = UnderlyingProgress 87 88 def Progress(self, dfComplete, message): 89 return self.UnderlyingProgress.Progress(dfComplete * (self.dfMax - self.dfMin) + self.dfMin, 90 message) 91 92 93def gdal_cp_single(srcfile, targetfile, progress): 94 if targetfile.endswith('/'): 95 stat_res = gdal.VSIStatL(targetfile) 96 else: 97 stat_res = gdal.VSIStatL(targetfile + '/') 98 99 if (stat_res is None and targetfile.endswith('/')) or \ 100 (stat_res is not None and stat.S_ISDIR(stat_res.mode)): 101 (_, tail) = os.path.split(srcfile) 102 if targetfile.endswith('/'): 103 targetfile = targetfile + tail 104 else: 105 targetfile = targetfile + '/' + tail 106 107 fin = gdal.VSIFOpenL(srcfile, "rb") 108 if fin is None: 109 print('Cannot open %s' % srcfile) 110 return -1 111 112 fout = gdal.VSIFOpenL(targetfile, "wb") 113 if fout is None: 114 print('Cannot create %s' % targetfile) 115 gdal.VSIFCloseL(fin) 116 return -1 117 118 version_num = int(gdal.VersionInfo('VERSION_NUM')) 119 total_size = 0 120 if version_num < 1900 or progress is not None: 121 gdal.VSIFSeekL(fin, 0, 2) 122 total_size = gdal.VSIFTellL(fin) 123 gdal.VSIFSeekL(fin, 0, 0) 124 125 buf_max_size = 4096 126 copied = 0 127 ret = 0 128 # print('Copying %s...' % srcfile) 129 if progress is not None: 130 if not progress.Progress(0.0, 'Copying %s' % srcfile): 131 print('Copy stopped by user') 132 ret = -2 133 134 while ret == 0: 135 if total_size != 0 and copied + buf_max_size > total_size: 136 to_read = total_size - copied 137 else: 138 to_read = buf_max_size 139 buf = gdal.VSIFReadL(1, to_read, fin) 140 if buf is None: 141 if copied == 0: 142 print('Cannot read %d bytes in %s' % (to_read, srcfile)) 143 ret = -1 144 break 145 buf_size = len(buf) 146 if gdal.VSIFWriteL(buf, 1, buf_size, fout) != buf_size: 147 print('Error writing %d bytes' % buf_size) 148 ret = -1 149 break 150 copied += buf_size 151 if progress is not None and total_size != 0: 152 if not progress.Progress(copied * 1.0 / total_size, 'Copying %s' % srcfile): 153 print('Copy stopped by user') 154 ret = -2 155 break 156 if to_read < buf_max_size or buf_size != buf_max_size: 157 break 158 159 gdal.VSIFCloseL(fin) 160 gdal.VSIFCloseL(fout) 161 162 return ret 163 164 165def gdal_cp_recurse(srcdir, targetdir, progress, skip_failure): 166 167 if srcdir[-1] == '/': 168 srcdir = srcdir[0:len(srcdir) - 1] 169 lst = gdal.ReadDir(srcdir) 170 if lst is None: 171 print('%s is not a directory' % srcdir) 172 return -1 173 174 if gdal.VSIStatL(targetdir) is None: 175 gdal.Mkdir(targetdir, int('0755', 8)) 176 177 for srcfile in lst: 178 if srcfile == '.' or srcfile == '..': 179 continue 180 fullsrcfile = srcdir + '/' + srcfile 181 statBuf = gdal.VSIStatL(fullsrcfile, gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG) 182 if statBuf.IsDirectory(): 183 ret = gdal_cp_recurse(fullsrcfile, targetdir + '/' + srcfile, progress, skip_failure) 184 else: 185 ret = gdal_cp_single(fullsrcfile, targetdir, progress) 186 if ret == -2 or (ret == -1 and not skip_failure): 187 return ret 188 return 0 189 190 191def gdal_cp_pattern_match(srcdir, pattern, targetfile, progress, skip_failure): 192 193 if srcdir == '': 194 srcdir = '.' 195 196 lst = gdal.ReadDir(srcdir) 197 lst2 = [] 198 if lst is None: 199 print('Cannot read directory %s' % srcdir) 200 return -1 201 202 for filename in lst: 203 if filename == '.' or filename == '..': 204 continue 205 if srcdir != '.': 206 lst2.append(srcdir + '/' + filename) 207 else: 208 lst2.append(filename) 209 210 if progress is not None: 211 total_size = 0 212 filesizelst = [] 213 for srcfile in lst2: 214 filesize = 0 215 if fnmatch.fnmatch(srcfile, pattern): 216 fin = gdal.VSIFOpenL(srcfile, "rb") 217 if fin is not None: 218 gdal.VSIFSeekL(fin, 0, 2) 219 filesize = gdal.VSIFTellL(fin) 220 gdal.VSIFCloseL(fin) 221 222 filesizelst.append(filesize) 223 total_size = total_size + filesize 224 225 if total_size == 0: 226 return -1 227 228 cursize = 0 229 i = 0 230 for srcfile in lst2: 231 if filesizelst[i] != 0: 232 dfMin = cursize * 1.0 / total_size 233 dfMax = (cursize + filesizelst[i]) * 1.0 / total_size 234 scaled_progress = ScaledProgress(dfMin, dfMax, progress) 235 236 ret = gdal_cp_single(srcfile, targetfile, scaled_progress) 237 if ret == -2 or (ret == -1 and not skip_failure): 238 return ret 239 240 cursize += filesizelst[i] 241 242 i = i + 1 243 244 else: 245 for srcfile in lst2: 246 if fnmatch.fnmatch(srcfile, pattern): 247 ret = gdal_cp_single(srcfile, targetfile, progress) 248 if ret == -2 or (ret == -1 and not skip_failure): 249 return ret 250 return 0 251 252 253def gdal_cp(argv, progress=None): 254 srcfile = None 255 targetfile = None 256 recurse = False 257 skip_failure = False 258 259 argv = gdal.GeneralCmdLineProcessor(argv) 260 if argv is None: 261 return -1 262 263 for i in range(1, len(argv)): 264 if argv[i] == '-progress': 265 progress = TermProgress() 266 elif argv[i] == '-r': 267 version_num = int(gdal.VersionInfo('VERSION_NUM')) 268 if version_num < 1900: 269 print('ERROR: Python bindings of GDAL 1.9.0 or later required for -r option') 270 return -1 271 recurse = True 272 elif len(argv[i]) >= 5 and argv[i][0:5] == '-skip': 273 skip_failure = True 274 elif argv[i][0] == '-': 275 print('Unrecognized option : %s' % argv[i]) 276 return Usage() 277 elif srcfile is None: 278 srcfile = argv[i] 279 elif targetfile is None: 280 targetfile = argv[i] 281 else: 282 print('Unexpected option : %s' % argv[i]) 283 return Usage() 284 285 if srcfile is None or targetfile is None: 286 return Usage() 287 288 if needsVSICurl(srcfile): 289 srcfile = '/vsicurl/' + srcfile 290 291 if recurse: 292 # Make sure that 'gdal_cp.py -r [srcdir/]lastsubdir targetdir' creates 293 # targetdir/lastsubdir if targetdir already exists (like cp -r does). 294 if srcfile[-1] == '/': 295 srcfile = srcfile[0:len(srcfile) - 1] 296 statBufSrc = gdal.VSIStatL(srcfile, gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG) 297 if statBufSrc is None: 298 statBufSrc = gdal.VSIStatL(srcfile + '/', gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG) 299 statBufDst = gdal.VSIStatL(targetfile, gdal.VSI_STAT_EXISTS_FLAG | gdal.VSI_STAT_NATURE_FLAG) 300 if statBufSrc is not None and statBufSrc.IsDirectory() and statBufDst is not None and statBufDst.IsDirectory(): 301 if targetfile[-1] == '/': 302 targetfile = targetfile[0:-1] 303 if srcfile.rfind('/') != -1: 304 targetfile = targetfile + srcfile[srcfile.rfind('/'):] 305 else: 306 targetfile = targetfile + '/' + srcfile 307 308 if gdal.VSIStatL(targetfile) is None: 309 gdal.Mkdir(targetfile, int('0755', 8)) 310 311 return gdal_cp_recurse(srcfile, targetfile, progress, skip_failure) 312 313 (srcdir, pattern) = os.path.split(srcfile) 314 if not srcdir.startswith('/vsi') and ('*' in pattern or '?' in pattern): 315 return gdal_cp_pattern_match(srcdir, pattern, targetfile, progress, skip_failure) 316 return gdal_cp_single(srcfile, targetfile, progress) 317 318 319def main(argv): 320 version_num = int(gdal.VersionInfo('VERSION_NUM')) 321 if version_num < 1800: 322 print('ERROR: Python bindings of GDAL 1.8.0 or later required') 323 return 1 324 return gdal_cp(argv) 325 326 327if __name__ == '__main__': 328 sys.exit(main(sys.argv)) 329