ยปCore Development>Code coverage>Tools/scripts/ftpmirror.py

Python code coverage for Tools/scripts/ftpmirror.py

#countcontent
1n/a#! /usr/bin/env python3
2n/a
3n/a"""Mirror a remote ftp subtree into a local directory tree.
4n/a
5n/ausage: ftpmirror [-v] [-q] [-i] [-m] [-n] [-r] [-s pat]
6n/a [-l username [-p passwd [-a account]]]
7n/a hostname[:port] [remotedir [localdir]]
8n/a-v: verbose
9n/a-q: quiet
10n/a-i: interactive mode
11n/a-m: macintosh server (NCSA telnet 2.4) (implies -n -s '*.o')
12n/a-n: don't log in
13n/a-r: remove local files/directories no longer pertinent
14n/a-l username [-p passwd [-a account]]: login info (default .netrc or anonymous)
15n/a-s pat: skip files matching pattern
16n/ahostname: remote host w/ optional port separated by ':'
17n/aremotedir: remote directory (default initial)
18n/alocaldir: local directory (default current)
19n/a"""
20n/a
21n/aimport os
22n/aimport sys
23n/aimport time
24n/aimport getopt
25n/aimport ftplib
26n/aimport netrc
27n/afrom fnmatch import fnmatch
28n/a
29n/a# Print usage message and exit
30n/adef usage(*args):
31n/a sys.stdout = sys.stderr
32n/a for msg in args: print(msg)
33n/a print(__doc__)
34n/a sys.exit(2)
35n/a
36n/averbose = 1 # 0 for -q, 2 for -v
37n/ainteractive = 0
38n/amac = 0
39n/armok = 0
40n/anologin = 0
41n/askippats = ['.', '..', '.mirrorinfo']
42n/a
43n/a# Main program: parse command line and start processing
44n/adef main():
45n/a global verbose, interactive, mac, rmok, nologin
46n/a try:
47n/a opts, args = getopt.getopt(sys.argv[1:], 'a:bil:mnp:qrs:v')
48n/a except getopt.error as msg:
49n/a usage(msg)
50n/a login = ''
51n/a passwd = ''
52n/a account = ''
53n/a if not args: usage('hostname missing')
54n/a host = args[0]
55n/a port = 0
56n/a if ':' in host:
57n/a host, port = host.split(':', 1)
58n/a port = int(port)
59n/a try:
60n/a auth = netrc.netrc().authenticators(host)
61n/a if auth is not None:
62n/a login, account, passwd = auth
63n/a except (netrc.NetrcParseError, IOError):
64n/a pass
65n/a for o, a in opts:
66n/a if o == '-l': login = a
67n/a if o == '-p': passwd = a
68n/a if o == '-a': account = a
69n/a if o == '-v': verbose = verbose + 1
70n/a if o == '-q': verbose = 0
71n/a if o == '-i': interactive = 1
72n/a if o == '-m': mac = 1; nologin = 1; skippats.append('*.o')
73n/a if o == '-n': nologin = 1
74n/a if o == '-r': rmok = 1
75n/a if o == '-s': skippats.append(a)
76n/a remotedir = ''
77n/a localdir = ''
78n/a if args[1:]:
79n/a remotedir = args[1]
80n/a if args[2:]:
81n/a localdir = args[2]
82n/a if args[3:]: usage('too many arguments')
83n/a #
84n/a f = ftplib.FTP()
85n/a if verbose: print("Connecting to '%s%s'..." % (host,
86n/a (port and ":%d"%port or "")))
87n/a f.connect(host,port)
88n/a if not nologin:
89n/a if verbose:
90n/a print('Logging in as %r...' % (login or 'anonymous'))
91n/a f.login(login, passwd, account)
92n/a if verbose: print('OK.')
93n/a pwd = f.pwd()
94n/a if verbose > 1: print('PWD =', repr(pwd))
95n/a if remotedir:
96n/a if verbose > 1: print('cwd(%s)' % repr(remotedir))
97n/a f.cwd(remotedir)
98n/a if verbose > 1: print('OK.')
99n/a pwd = f.pwd()
100n/a if verbose > 1: print('PWD =', repr(pwd))
101n/a #
102n/a mirrorsubdir(f, localdir)
103n/a
104n/a# Core logic: mirror one subdirectory (recursively)
105n/adef mirrorsubdir(f, localdir):
106n/a pwd = f.pwd()
107n/a if localdir and not os.path.isdir(localdir):
108n/a if verbose: print('Creating local directory', repr(localdir))
109n/a try:
110n/a makedir(localdir)
111n/a except OSError as msg:
112n/a print("Failed to establish local directory", repr(localdir))
113n/a return
114n/a infofilename = os.path.join(localdir, '.mirrorinfo')
115n/a try:
116n/a text = open(infofilename, 'r').read()
117n/a except IOError as msg:
118n/a text = '{}'
119n/a try:
120n/a info = eval(text)
121n/a except (SyntaxError, NameError):
122n/a print('Bad mirror info in', repr(infofilename))
123n/a info = {}
124n/a subdirs = []
125n/a listing = []
126n/a if verbose: print('Listing remote directory %r...' % (pwd,))
127n/a f.retrlines('LIST', listing.append)
128n/a filesfound = []
129n/a for line in listing:
130n/a if verbose > 1: print('-->', repr(line))
131n/a if mac:
132n/a # Mac listing has just filenames;
133n/a # trailing / means subdirectory
134n/a filename = line.strip()
135n/a mode = '-'
136n/a if filename[-1:] == '/':
137n/a filename = filename[:-1]
138n/a mode = 'd'
139n/a infostuff = ''
140n/a else:
141n/a # Parse, assuming a UNIX listing
142n/a words = line.split(None, 8)
143n/a if len(words) < 6:
144n/a if verbose > 1: print('Skipping short line')
145n/a continue
146n/a filename = words[-1].lstrip()
147n/a i = filename.find(" -> ")
148n/a if i >= 0:
149n/a # words[0] had better start with 'l'...
150n/a if verbose > 1:
151n/a print('Found symbolic link %r' % (filename,))
152n/a linkto = filename[i+4:]
153n/a filename = filename[:i]
154n/a infostuff = words[-5:-1]
155n/a mode = words[0]
156n/a skip = 0
157n/a for pat in skippats:
158n/a if fnmatch(filename, pat):
159n/a if verbose > 1:
160n/a print('Skip pattern', repr(pat), end=' ')
161n/a print('matches', repr(filename))
162n/a skip = 1
163n/a break
164n/a if skip:
165n/a continue
166n/a if mode[0] == 'd':
167n/a if verbose > 1:
168n/a print('Remembering subdirectory', repr(filename))
169n/a subdirs.append(filename)
170n/a continue
171n/a filesfound.append(filename)
172n/a if filename in info and info[filename] == infostuff:
173n/a if verbose > 1:
174n/a print('Already have this version of',repr(filename))
175n/a continue
176n/a fullname = os.path.join(localdir, filename)
177n/a tempname = os.path.join(localdir, '@'+filename)
178n/a if interactive:
179n/a doit = askabout('file', filename, pwd)
180n/a if not doit:
181n/a if filename not in info:
182n/a info[filename] = 'Not retrieved'
183n/a continue
184n/a try:
185n/a os.unlink(tempname)
186n/a except OSError:
187n/a pass
188n/a if mode[0] == 'l':
189n/a if verbose:
190n/a print("Creating symlink %r -> %r" % (filename, linkto))
191n/a try:
192n/a os.symlink(linkto, tempname)
193n/a except IOError as msg:
194n/a print("Can't create %r: %s" % (tempname, msg))
195n/a continue
196n/a else:
197n/a try:
198n/a fp = open(tempname, 'wb')
199n/a except IOError as msg:
200n/a print("Can't create %r: %s" % (tempname, msg))
201n/a continue
202n/a if verbose:
203n/a print('Retrieving %r from %r as %r...' % (filename, pwd, fullname))
204n/a if verbose:
205n/a fp1 = LoggingFile(fp, 1024, sys.stdout)
206n/a else:
207n/a fp1 = fp
208n/a t0 = time.time()
209n/a try:
210n/a f.retrbinary('RETR ' + filename,
211n/a fp1.write, 8*1024)
212n/a except ftplib.error_perm as msg:
213n/a print(msg)
214n/a t1 = time.time()
215n/a bytes = fp.tell()
216n/a fp.close()
217n/a if fp1 != fp:
218n/a fp1.close()
219n/a try:
220n/a os.unlink(fullname)
221n/a except OSError:
222n/a pass # Ignore the error
223n/a try:
224n/a os.rename(tempname, fullname)
225n/a except OSError as msg:
226n/a print("Can't rename %r to %r: %s" % (tempname, fullname, msg))
227n/a continue
228n/a info[filename] = infostuff
229n/a writedict(info, infofilename)
230n/a if verbose and mode[0] != 'l':
231n/a dt = t1 - t0
232n/a kbytes = bytes / 1024.0
233n/a print(int(round(kbytes)), end=' ')
234n/a print('Kbytes in', end=' ')
235n/a print(int(round(dt)), end=' ')
236n/a print('seconds', end=' ')
237n/a if t1 > t0:
238n/a print('(~%d Kbytes/sec)' % \
239n/a int(round(kbytes/dt),))
240n/a print()
241n/a #
242n/a # Remove files from info that are no longer remote
243n/a deletions = 0
244n/a for filename in list(info.keys()):
245n/a if filename not in filesfound:
246n/a if verbose:
247n/a print("Removing obsolete info entry for", end=' ')
248n/a print(repr(filename), "in", repr(localdir or "."))
249n/a del info[filename]
250n/a deletions = deletions + 1
251n/a if deletions:
252n/a writedict(info, infofilename)
253n/a #
254n/a # Remove local files that are no longer in the remote directory
255n/a try:
256n/a if not localdir: names = os.listdir(os.curdir)
257n/a else: names = os.listdir(localdir)
258n/a except OSError:
259n/a names = []
260n/a for name in names:
261n/a if name[0] == '.' or name in info or name in subdirs:
262n/a continue
263n/a skip = 0
264n/a for pat in skippats:
265n/a if fnmatch(name, pat):
266n/a if verbose > 1:
267n/a print('Skip pattern', repr(pat), end=' ')
268n/a print('matches', repr(name))
269n/a skip = 1
270n/a break
271n/a if skip:
272n/a continue
273n/a fullname = os.path.join(localdir, name)
274n/a if not rmok:
275n/a if verbose:
276n/a print('Local file', repr(fullname), end=' ')
277n/a print('is no longer pertinent')
278n/a continue
279n/a if verbose: print('Removing local file/dir', repr(fullname))
280n/a remove(fullname)
281n/a #
282n/a # Recursively mirror subdirectories
283n/a for subdir in subdirs:
284n/a if interactive:
285n/a doit = askabout('subdirectory', subdir, pwd)
286n/a if not doit: continue
287n/a if verbose: print('Processing subdirectory', repr(subdir))
288n/a localsubdir = os.path.join(localdir, subdir)
289n/a pwd = f.pwd()
290n/a if verbose > 1:
291n/a print('Remote directory now:', repr(pwd))
292n/a print('Remote cwd', repr(subdir))
293n/a try:
294n/a f.cwd(subdir)
295n/a except ftplib.error_perm as msg:
296n/a print("Can't chdir to", repr(subdir), ":", repr(msg))
297n/a else:
298n/a if verbose: print('Mirroring as', repr(localsubdir))
299n/a mirrorsubdir(f, localsubdir)
300n/a if verbose > 1: print('Remote cwd ..')
301n/a f.cwd('..')
302n/a newpwd = f.pwd()
303n/a if newpwd != pwd:
304n/a print('Ended up in wrong directory after cd + cd ..')
305n/a print('Giving up now.')
306n/a break
307n/a else:
308n/a if verbose > 1: print('OK.')
309n/a
310n/a# Helper to remove a file or directory tree
311n/adef remove(fullname):
312n/a if os.path.isdir(fullname) and not os.path.islink(fullname):
313n/a try:
314n/a names = os.listdir(fullname)
315n/a except OSError:
316n/a names = []
317n/a ok = 1
318n/a for name in names:
319n/a if not remove(os.path.join(fullname, name)):
320n/a ok = 0
321n/a if not ok:
322n/a return 0
323n/a try:
324n/a os.rmdir(fullname)
325n/a except OSError as msg:
326n/a print("Can't remove local directory %r: %s" % (fullname, msg))
327n/a return 0
328n/a else:
329n/a try:
330n/a os.unlink(fullname)
331n/a except OSError as msg:
332n/a print("Can't remove local file %r: %s" % (fullname, msg))
333n/a return 0
334n/a return 1
335n/a
336n/a# Wrapper around a file for writing to write a hash sign every block.
337n/aclass LoggingFile:
338n/a def __init__(self, fp, blocksize, outfp):
339n/a self.fp = fp
340n/a self.bytes = 0
341n/a self.hashes = 0
342n/a self.blocksize = blocksize
343n/a self.outfp = outfp
344n/a def write(self, data):
345n/a self.bytes = self.bytes + len(data)
346n/a hashes = int(self.bytes) / self.blocksize
347n/a while hashes > self.hashes:
348n/a self.outfp.write('#')
349n/a self.outfp.flush()
350n/a self.hashes = self.hashes + 1
351n/a self.fp.write(data)
352n/a def close(self):
353n/a self.outfp.write('\n')
354n/a
355n/adef raw_input(prompt):
356n/a sys.stdout.write(prompt)
357n/a sys.stdout.flush()
358n/a return sys.stdin.readline()
359n/a
360n/a# Ask permission to download a file.
361n/adef askabout(filetype, filename, pwd):
362n/a prompt = 'Retrieve %s %s from %s ? [ny] ' % (filetype, filename, pwd)
363n/a while 1:
364n/a reply = raw_input(prompt).strip().lower()
365n/a if reply in ['y', 'ye', 'yes']:
366n/a return 1
367n/a if reply in ['', 'n', 'no', 'nop', 'nope']:
368n/a return 0
369n/a print('Please answer yes or no.')
370n/a
371n/a# Create a directory if it doesn't exist. Recursively create the
372n/a# parent directory as well if needed.
373n/adef makedir(pathname):
374n/a if os.path.isdir(pathname):
375n/a return
376n/a dirname = os.path.dirname(pathname)
377n/a if dirname: makedir(dirname)
378n/a os.mkdir(pathname, 0o777)
379n/a
380n/a# Write a dictionary to a file in a way that can be read back using
381n/a# rval() but is still somewhat readable (i.e. not a single long line).
382n/a# Also creates a backup file.
383n/adef writedict(dict, filename):
384n/a dir, fname = os.path.split(filename)
385n/a tempname = os.path.join(dir, '@' + fname)
386n/a backup = os.path.join(dir, fname + '~')
387n/a try:
388n/a os.unlink(backup)
389n/a except OSError:
390n/a pass
391n/a fp = open(tempname, 'w')
392n/a fp.write('{\n')
393n/a for key, value in dict.items():
394n/a fp.write('%r: %r,\n' % (key, value))
395n/a fp.write('}\n')
396n/a fp.close()
397n/a try:
398n/a os.rename(filename, backup)
399n/a except OSError:
400n/a pass
401n/a os.rename(tempname, filename)
402n/a
403n/a
404n/aif __name__ == '__main__':
405n/a main()