source file: /home/buildslave/tahoe/edgy/build/src/allmydata/scripts/tahoe_cp.py
file stats: 525 lines, 469 executed: 89.3% covered
   1. 
   2. import os.path
   3. import urllib
   4. import simplejson
   5. from allmydata.scripts.common import get_alias, escape_path, DefaultAliasMarker
   6. from allmydata.scripts.common_http import do_http
   7. from allmydata import uri
   8. 
   9. def ascii_or_none(s):
  10.     if s is None:
  11.         return s
  12.     return str(s)
  13. 
  14. class WriteError(Exception):
  15.     pass
  16. class ReadError(Exception):
  17.     pass
  18. class MissingSourceError(Exception):
  19.     pass
  20. 
  21. def GET_to_file(url):
  22.     resp = do_http("GET", url)
  23.     if resp.status == 200:
  24.         return resp
  25.     raise ReadError("Error during GET: %s %s %s" % (resp.status,
  26.                                                     resp.reason,
  27.                                                     resp.read()))
  28. def GET_to_string(url):
  29.     f = GET_to_file(url)
  30.     return f.read()
  31. 
  32. def PUT(url, data):
  33.     resp = do_http("PUT", url, data)
  34.     if resp.status in (200, 201):
  35.         return resp.read()
  36.     raise WriteError("Error during PUT: %s %s %s" % (resp.status, resp.reason,
  37.                                                      resp.read()))
  38. 
  39. def POST(url, data):
  40.     resp = do_http("POST", url, data)
  41.     if resp.status in (200, 201):
  42.         return resp.read()
  43.     raise WriteError("Error during POST: %s %s %s" % (resp.status, resp.reason,
  44.                                                       resp.read()))
  45. 
  46. def mkdir(targeturl):
  47.     url = targeturl + "?t=mkdir"
  48.     resp = do_http("POST", url)
  49.     if resp.status in (200, 201):
  50.         return resp.read().strip()
  51.     raise WriteError("Error during mkdir: %s %s %s" % (resp.status, resp.reason,
  52.                                                        resp.read()))
  53. 
  54. def make_tahoe_subdirectory(nodeurl, parent_writecap, name):
  55.     url = nodeurl + "/".join(["uri",
  56.                               urllib.quote(parent_writecap),
  57.                               urllib.quote(name),
  58.                               ]) + "?t=mkdir"
  59.     resp = do_http("POST", url)
  60.     if resp.status in (200, 201):
  61.         return resp.read().strip()
  62.     raise WriteError("Error during mkdir: %s %s %s" % (resp.status, resp.reason,
  63.                                                        resp.read()))
  64. 
  65. 
  66. class LocalFileSource:
  67.     def __init__(self, pathname):
  68.         self.pathname = pathname
  69. 
  70.     def need_to_copy_bytes(self):
  71.         return True
  72. 
  73.     def open(self):
  74.         return open(self.pathname, "rb")
  75. 
  76. class LocalFileTarget:
  77.     def __init__(self, pathname):
  78.         self.pathname = pathname
  79.     def put_file(self, inf):
  80.         outf = open(self.pathname, "wb")
  81.         while True:
  82.             data = inf.read(32768)
  83.             if not data:
  84.                 break
  85.             outf.write(data)
  86.         outf.close()
  87. 
  88. class LocalMissingTarget:
  89.     def __init__(self, pathname):
  90.         self.pathname = pathname
  91. 
  92.     def put_file(self, inf):
  93.         outf = open(self.pathname, "wb")
  94.         while True:
  95.             data = inf.read(32768)
  96.             if not data:
  97.                 break
  98.             outf.write(data)
  99.         outf.close()
 100. 
 101. class LocalDirectorySource:
 102.     def __init__(self, progressfunc, pathname):
 103.         self.progressfunc = progressfunc
 104.         self.pathname = pathname
 105.         self.children = None
 106. 
 107.     def populate(self, recurse):
 108.         if self.children is not None:
 109.             return
 110.         self.children = {}
 111.         children = os.listdir(self.pathname)
 112.         for i,n in enumerate(children):
 113.             self.progressfunc("examining %d of %d" % (i, len(children)))
 114.             pn = os.path.join(self.pathname, n)
 115.             if os.path.isdir(pn):
 116.                 child = LocalDirectorySource(self.progressfunc, pn)
 117.                 self.children[n] = child
 118.                 if recurse:
 119.                     child.populate(True)
 120.             else:
 121.                 assert os.path.isfile(pn)
 122.                 self.children[n] = LocalFileSource(pn)
 123. 
 124. class LocalDirectoryTarget:
 125.     def __init__(self, progressfunc, pathname):
 126.         self.progressfunc = progressfunc
 127.         self.pathname = pathname
 128.         self.children = None
 129. 
 130.     def populate(self, recurse):
 131.         if self.children is not None:
 132.             return
 133.         self.children = {}
 134.         children = os.listdir(self.pathname)
 135.         for i,n in enumerate(children):
 136.             self.progressfunc("examining %d of %d" % (i, len(children)))
 137.             pn = os.path.join(self.pathname, n)
 138.             if os.path.isdir(pn):
 139.                 child = LocalDirectoryTarget(self.progressfunc, pn)
 140.                 self.children[n] = child
 141.                 if recurse:
 142.                     child.populate(True)
 143.             else:
 144.                 assert os.path.isfile(pn)
 145.                 self.children[n] = LocalFileTarget(pn)
 146. 
 147.     def get_child_target(self, name):
 148.         if self.children is None:
 149.             self.populate(False)
 150.         if name in self.children:
 151.             return self.children[name]
 152.         pathname = os.path.join(self.pathname, name)
 153.         os.makedirs(pathname)
 154.         return LocalDirectoryTarget(self.progressfunc, pathname)
 155. 
 156.     def put_file(self, name, inf):
 157.         pathname = os.path.join(self.pathname, name)
 158.         outf = open(pathname, "wb")
 159.         while True:
 160.             data = inf.read(32768)
 161.             if not data:
 162.                 break
 163.             outf.write(data)
 164.         outf.close()
 165. 
 166.     def set_children(self):
 167.         pass
 168. 
 169. class TahoeFileSource:
 170.     def __init__(self, nodeurl, mutable, writecap, readcap):
 171.         self.nodeurl = nodeurl
 172.         self.mutable = mutable
 173.         self.writecap = writecap
 174.         self.readcap = readcap
 175. 
 176.     def need_to_copy_bytes(self):
 177.         if self.mutable:
 178.             return True
 179.         return False
 180. 
 181.     def open(self):
 182.         url = self.nodeurl + "uri/" + urllib.quote(self.readcap)
 183.         return GET_to_file(url)
 184. 
 185.     def bestcap(self):
 186.         return self.writecap or self.readcap
 187. 
 188. class TahoeFileTarget:
 189.     def __init__(self, nodeurl, mutable, writecap, readcap, url):
 190.         self.nodeurl = nodeurl
 191.         self.mutable = mutable
 192.         self.writecap = writecap
 193.         self.readcap = readcap
 194.         self.url = url
 195. 
 196.     def put_file(self, inf):
 197.         # We want to replace this object in-place.
 198.         assert self.url
 199.         # our do_http() call currently requires a string or a filehandle with
 200.         # a real .seek
 201.         if not hasattr(inf, "seek"):
 202.             inf = inf.read()
 203.         PUT(self.url, inf)
 204.         # TODO: this always creates immutable files. We might want an option
 205.         # to always create mutable files, or to copy mutable files into new
 206.         # mutable files.
 207. 
 208. class TahoeDirectorySource:
 209.     def __init__(self, nodeurl, cache, progressfunc):
 210.         self.nodeurl = nodeurl
 211.         self.cache = cache
 212.         self.progressfunc = progressfunc
 213. 
 214.     def init_from_grid(self, writecap, readcap):
 215.         self.writecap = writecap
 216.         self.readcap = readcap
 217.         bestcap = writecap or readcap
 218.         url = self.nodeurl + "uri/%s" % urllib.quote(bestcap)
 219.         resp = do_http("GET", url + "?t=json")
 220.         assert resp.status == 200
 221.         parsed = simplejson.loads(resp.read())
 222.         nodetype, d = parsed
 223.         assert nodetype == "dirnode"
 224.         self.mutable = d.get("mutable", False) # older nodes don't provide it
 225.         self.children_d = d["children"]
 226.         self.children = None
 227. 
 228.     def init_from_parsed(self, parsed):
 229.         nodetype, d = parsed
 230.         self.writecap = ascii_or_none(d.get("rw_uri"))
 231.         self.readcap = ascii_or_none(d.get("ro_uri"))
 232.         self.mutable = d.get("mutable", False) # older nodes don't provide it
 233.         self.children_d = d["children"]
 234.         self.children = None
 235. 
 236.     def populate(self, recurse):
 237.         if self.children is not None:
 238.             return
 239.         self.children = {}
 240.         for i,(name, data) in enumerate(self.children_d.items()):
 241.             self.progressfunc("examining %d of %d" % (i, len(self.children_d)))
 242.             if data[0] == "filenode":
 243.                 mutable = data[1].get("mutable", False)
 244.                 writecap = ascii_or_none(data[1].get("rw_uri"))
 245.                 readcap = ascii_or_none(data[1].get("ro_uri"))
 246.                 self.children[name] = TahoeFileSource(self.nodeurl, mutable,
 247.                                                       writecap, readcap)
 248.             else:
 249.                 assert data[0] == "dirnode"
 250.                 writecap = ascii_or_none(data[1].get("rw_uri"))
 251.                 readcap = ascii_or_none(data[1].get("ro_uri"))
 252.                 if writecap and writecap in self.cache:
 253.                     child = self.cache[writecap]
 254.                 elif readcap and readcap in self.cache:
 255.                     child = self.cache[readcap]
 256.                 else:
 257.                     child = TahoeDirectorySource(self.nodeurl, self.cache,
 258.                                                  self.progressfunc)
 259.                     child.init_from_grid(writecap, readcap)
 260.                     if writecap:
 261.                         self.cache[writecap] = child
 262.                     if readcap:
 263.                         self.cache[readcap] = child
 264.                     if recurse:
 265.                         child.populate(True)
 266.                 self.children[name] = child
 267. 
 268. class TahoeMissingTarget:
 269.     def __init__(self, url):
 270.         self.url = url
 271. 
 272.     def put_file(self, inf):
 273.         # We want to replace this object in-place.
 274.         if not hasattr(inf, "seek"):
 275.             inf = inf.read()
 276.         PUT(self.url, inf)
 277.         # TODO: this always creates immutable files. We might want an option
 278.         # to always create mutable files, or to copy mutable files into new
 279.         # mutable files.
 280. 
 281.     def put_uri(self, filecap):
 282.         # I'm not sure this will always work
 283.         return PUT(self.url + "?t=uri", filecap)
 284. 
 285. class TahoeDirectoryTarget:
 286.     def __init__(self, nodeurl, cache, progressfunc):
 287.         self.nodeurl = nodeurl
 288.         self.cache = cache
 289.         self.progressfunc = progressfunc
 290.         self.new_children = {}
 291. 
 292.     def init_from_parsed(self, parsed):
 293.         nodetype, d = parsed
 294.         self.writecap = ascii_or_none(d.get("rw_uri"))
 295.         self.readcap = ascii_or_none(d.get("ro_uri"))
 296.         self.mutable = d.get("mutable", False) # older nodes don't provide it
 297.         self.children_d = d["children"]
 298.         self.children = None
 299. 
 300.     def init_from_grid(self, writecap, readcap):
 301.         self.writecap = writecap
 302.         self.readcap = readcap
 303.         bestcap = writecap or readcap
 304.         url = self.nodeurl + "uri/%s" % urllib.quote(bestcap)
 305.         resp = do_http("GET", url + "?t=json")
 306.         assert resp.status == 200
 307.         parsed = simplejson.loads(resp.read())
 308.         nodetype, d = parsed
 309.         assert nodetype == "dirnode"
 310.         self.mutable = d.get("mutable", False) # older nodes don't provide it
 311.         self.children_d = d["children"]
 312.         self.children = None
 313. 
 314.     def just_created(self, writecap):
 315.         self.writecap = writecap
 316.         self.readcap = uri.from_string(writecap).get_readonly().to_string()
 317.         self.mutable = True
 318.         self.children_d = {}
 319.         self.children = {}
 320. 
 321.     def populate(self, recurse):
 322.         if self.children is not None:
 323.             return
 324.         self.children = {}
 325.         for i,(name, data) in enumerate(self.children_d.items()):
 326.             self.progressfunc("examining %d of %d" % (i, len(self.children_d)))
 327.             if data[0] == "filenode":
 328.                 mutable = data[1].get("mutable", False)
 329.                 writecap = ascii_or_none(data[1].get("rw_uri"))
 330.                 readcap = ascii_or_none(data[1].get("ro_uri"))
 331.                 url = None
 332.                 if self.writecap:
 333.                     url = self.nodeurl + "/".join(["uri",
 334.                                                    urllib.quote(self.writecap),
 335.                                                    urllib.quote(name)])
 336.                 self.children[name] = TahoeFileTarget(self.nodeurl, mutable,
 337.                                                       writecap, readcap, url)
 338.             else:
 339.                 assert data[0] == "dirnode"
 340.                 writecap = ascii_or_none(data[1].get("rw_uri"))
 341.                 readcap = ascii_or_none(data[1].get("ro_uri"))
 342.                 if writecap and writecap in self.cache:
 343.                     child = self.cache[writecap]
 344.                 elif readcap and readcap in self.cache:
 345.                     child = self.cache[readcap]
 346.                 else:
 347.                     child = TahoeDirectoryTarget(self.nodeurl, self.cache,
 348.                                                  self.progressfunc)
 349.                     child.init_from_grid(writecap, readcap)
 350.                     if writecap:
 351.                         self.cache[writecap] = child
 352.                     if readcap:
 353.                         self.cache[readcap] = child
 354.                     if recurse:
 355.                         child.populate(True)
 356.                 self.children[name] = child
 357. 
 358.     def get_child_target(self, name):
 359.         # return a new target for a named subdirectory of this dir
 360.         if self.children is None:
 361.             self.populate(False)
 362.         if name in self.children:
 363.             return self.children[name]
 364.         writecap = make_tahoe_subdirectory(self.nodeurl, self.writecap, name)
 365.         child = TahoeDirectoryTarget(self.nodeurl, self.cache,
 366.                                      self.progressfunc)
 367.         child.just_created(writecap)
 368.         self.children[name] = child
 369.         return child
 370. 
 371.     def put_file(self, name, inf):
 372.         url = self.nodeurl + "uri"
 373.         if not hasattr(inf, "seek"):
 374.             inf = inf.read()
 375.         filecap = PUT(url, inf)
 376.         # TODO: this always creates immutable files. We might want an option
 377.         # to always create mutable files, or to copy mutable files into new
 378.         # mutable files.
 379.         self.new_children[name] = filecap
 380. 
 381.     def put_uri(self, name, filecap):
 382.         self.new_children[name] = filecap
 383. 
 384.     def set_children(self):
 385.         if not self.new_children:
 386.             return
 387.         url = (self.nodeurl + "uri/" + urllib.quote(self.writecap)
 388.                + "?t=set_children")
 389.         set_data = {}
 390.         for (name, filecap) in self.new_children.items():
 391.             # it just so happens that ?t=set_children will accept both file
 392.             # read-caps and write-caps as ['rw_uri'], and will handle eithe
 393.             # correctly. So don't bother trying to figure out whether the one
 394.             # we have is read-only or read-write.
 395.             set_data[name] = ["filenode", {"rw_uri": filecap}]
 396.         body = simplejson.dumps(set_data)
 397.         POST(url, body)
 398. 
 399. class Copier:
 400. 
 401.     def do_copy(self, options, progressfunc=None):
 402.         if options['quiet']:
 403.             verbosity = 0
 404.         else:
 405.             verbosity = 2
 406. 
 407.         nodeurl = options['node-url']
 408.         if nodeurl[-1] != "/":
 409.             nodeurl += "/"
 410.         self.nodeurl = nodeurl
 411.         self.progressfunc = progressfunc
 412.         self.options = options
 413.         self.aliases = options.aliases
 414.         self.verbosity = verbosity
 415.         self.stdout = options.stdout
 416.         self.stderr = options.stderr
 417.         if options["verbose"] and not self.progressfunc:
 418.             def progress(message):
 419.                 print >>self.stderr, message
 420.             self.progressfunc = progress
 421.         self.cache = {}
 422.         source_specs = options.sources
 423.         destination_spec = options.destination
 424.         recursive = self.options["recursive"]
 425. 
 426.         target = self.get_target_info(destination_spec)
 427. 
 428.         try:
 429.             sources = [] # list of (name, source object)
 430.             for ss in source_specs:
 431.                 name, source = self.get_source_info(ss)
 432.                 sources.append( (name, source) )
 433.         except MissingSourceError, e:
 434.             self.to_stderr("No such file or directory %s" % e.args[0])
 435.             return 1
 436. 
 437.         have_source_dirs = bool([s for (name,s) in sources
 438.                                  if isinstance(s, (LocalDirectorySource,
 439.                                                    TahoeDirectorySource))])
 440. 
 441.         if have_source_dirs and not recursive:
 442.             self.to_stderr("cannot copy directories without --recursive")
 443.             return 1
 444. 
 445.         if isinstance(target, (LocalFileTarget, TahoeFileTarget)):
 446.             # cp STUFF foo.txt, where foo.txt already exists. This limits the
 447.             # possibilities considerably.
 448.             if len(sources) > 1:
 449.                 self.to_stderr("target '%s' is not a directory" % destination_spec)
 450.                 return 1
 451.             if have_source_dirs:
 452.                 self.to_stderr("cannot copy directory into a file")
 453.                 return 1
 454.             name, source = sources[0]
 455.             return self.copy_file(source, target)
 456. 
 457.         if isinstance(target, (LocalMissingTarget, TahoeMissingTarget)):
 458.             if recursive:
 459.                 return self.copy_to_directory(sources, target)
 460.             if len(sources) > 1:
 461.                 # if we have -r, we'll auto-create the target directory. Without
 462.                 # it, we'll only create a file.
 463.                 self.to_stderr("cannot copy multiple files into a file without -r")
 464.                 return 1
 465.             # cp file1 newfile
 466.             name, source = sources[0]
 467.             return self.copy_file(source, target)
 468. 
 469.         if isinstance(target, (LocalDirectoryTarget, TahoeDirectoryTarget)):
 470.             return self.copy_to_directory(sources, target)
 471. 
 472.         self.to_stderr("unknown target")
 473.         return 1
 474. 
 475.     def to_stderr(self, text):
 476.         print >>self.stderr, text
 477. 
 478.     def get_target_info(self, destination_spec):
 479.         rootcap, path = get_alias(self.aliases, destination_spec, None)
 480.         if rootcap == DefaultAliasMarker:
 481.             # no alias, so this is a local file
 482.             pathname = os.path.abspath(os.path.expanduser(path))
 483.             if not os.path.exists(pathname):
 484.                 t = LocalMissingTarget(pathname)
 485.             elif os.path.isdir(pathname):
 486.                 t = LocalDirectoryTarget(self.progress, pathname)
 487.             else:
 488.                 assert os.path.isfile(pathname), pathname
 489.                 t = LocalFileTarget(pathname) # non-empty
 490.         else:
 491.             # this is a tahoe object
 492.             url = self.nodeurl + "uri/%s" % urllib.quote(rootcap)
 493.             if path:
 494.                 url += "/" + escape_path(path)
 495.                 last_slash = path.rfind("/")
 496. 
 497.             resp = do_http("GET", url + "?t=json")
 498.             if resp.status == 404:
 499.                 # doesn't exist yet
 500.                 t = TahoeMissingTarget(url)
 501.             else:
 502.                 parsed = simplejson.loads(resp.read())
 503.                 nodetype, d = parsed
 504.                 if nodetype == "dirnode":
 505.                     t = TahoeDirectoryTarget(self.nodeurl, self.cache,
 506.                                              self.progress)
 507.                     t.init_from_parsed(parsed)
 508.                 else:
 509.                     writecap = ascii_or_none(d.get("rw_uri"))
 510.                     readcap = ascii_or_none(d.get("ro_uri"))
 511.                     mutable = d.get("mutable", False)
 512.                     t = TahoeFileTarget(self.nodeurl, mutable,
 513.                                         writecap, readcap, url)
 514.         return t
 515. 
 516.     def get_source_info(self, source_spec):
 517.         rootcap, path = get_alias(self.aliases, source_spec, None)
 518.         if rootcap == DefaultAliasMarker:
 519.             # no alias, so this is a local file
 520.             pathname = os.path.abspath(os.path.expanduser(path))
 521.             name = os.path.basename(pathname)
 522.             if not os.path.exists(pathname):
 523.                 raise MissingSourceError(source_spec)
 524.             if os.path.isdir(pathname):
 525.                 t = LocalDirectorySource(self.progress, pathname)
 526.             else:
 527.                 assert os.path.isfile(pathname)
 528.                 t = LocalFileSource(pathname) # non-empty
 529.         else:
 530.             # this is a tahoe object
 531.             url = self.nodeurl + "uri/%s" % urllib.quote(rootcap)
 532.             name = None
 533.             if path:
 534.                 url += "/" + escape_path(path)
 535.                 last_slash = path.rfind("/")
 536.                 name = path
 537.                 if last_slash:
 538.                     name = path[last_slash+1:]
 539. 
 540.             resp = do_http("GET", url + "?t=json")
 541.             if resp.status == 404:
 542.                 raise MissingSourceError(source_spec)
 543.             parsed = simplejson.loads(resp.read())
 544.             nodetype, d = parsed
 545.             if nodetype == "dirnode":
 546.                 t = TahoeDirectorySource(self.nodeurl, self.cache,
 547.                                          self.progress)
 548.                 t.init_from_parsed(parsed)
 549.             else:
 550.                 writecap = ascii_or_none(d.get("rw_uri"))
 551.                 readcap = ascii_or_none(d.get("ro_uri"))
 552.                 mutable = d.get("mutable", False) # older nodes don't provide it
 553.                 t = TahoeFileSource(self.nodeurl, mutable, writecap, readcap)
 554.         return name, t
 555. 
 556. 
 557.     def dump_graph(self, s, indent=" "):
 558.         for name, child in s.children.items():
 559.             print indent + name + ":" + str(child)
 560.             if isinstance(child, (LocalDirectorySource, TahoeDirectorySource)):
 561.                 self.dump_graph(child, indent+"  ")
 562. 
 563.     def copy_to_directory(self, source_infos, target):
 564.         # step one: build a recursive graph of the source tree. This returns
 565.         # a dictionary, with child names as keys, and values that are either
 566.         # Directory or File instances (local or tahoe).
 567.         source_dirs = self.build_graphs(source_infos)
 568.         source_files = [source for source in source_infos
 569.                         if isinstance(source[1], (LocalFileSource,
 570.                                                   TahoeFileSource))]
 571. 
 572.         #print "graphs"
 573.         #for s in source_dirs:
 574.         #    self.dump_graph(s)
 575. 
 576.         # step two: create the top-level target directory object
 577.         if isinstance(target, LocalMissingTarget):
 578.             os.makedirs(target.pathname)
 579.             target = LocalDirectoryTarget(self.progress, target.pathname)
 580.         elif isinstance(target, TahoeMissingTarget):
 581.             writecap = mkdir(target.url)
 582.             target = TahoeDirectoryTarget(self.nodeurl, self.cache,
 583.                                           self.progress)
 584.             target.just_created(writecap)
 585.         assert isinstance(target, (LocalDirectoryTarget, TahoeDirectoryTarget))
 586.         target.populate(False)
 587. 
 588.         # step three: find a target for each source node, creating
 589.         # directories as necessary. 'targetmap' is a dictionary that uses
 590.         # target Directory instances as keys, and has values of
 591.         # (name->sourceobject) dicts for all the files that need to wind up
 592.         # there.
 593. 
 594.         # sources are all LocalFile/LocalDirectory/TahoeFile/TahoeDirectory
 595.         # target is LocalDirectory/TahoeDirectory
 596. 
 597.         self.progress("attaching sources to targets, "
 598.                       "%d files / %d dirs in root" %
 599.                       (len(source_files), len(source_dirs)))
 600. 
 601.         self.targetmap = {}
 602.         self.files_to_copy = 0
 603. 
 604.         for (name,s) in source_files:
 605.             self.attach_to_target(s, name, target)
 606.             self.files_to_copy += 1
 607. 
 608.         for source in source_dirs:
 609.             self.assign_targets(source, target)
 610. 
 611.         self.progress("targets assigned, %s dirs, %s files" %
 612.                       (len(self.targetmap), self.files_to_copy))
 613. 
 614.         self.progress("starting copy, %d files, %d directories" %
 615.                       (self.files_to_copy, len(self.targetmap)))
 616.         self.files_copied = 0
 617.         self.targets_finished = 0
 618. 
 619.         # step four: walk through the list of targets. For each one, copy all
 620.         # the files. If the target is a TahoeDirectory, upload and create
 621.         # read-caps, then do a set_children to the target directory.
 622. 
 623.         for target in self.targetmap:
 624.             self.copy_files_to_target(self.targetmap[target], target)
 625.             self.targets_finished += 1
 626.             self.progress("%d/%d directories" %
 627.                           (self.targets_finished, len(self.targetmap)))
 628. 
 629.     def attach_to_target(self, source, name, target):
 630.         if target not in self.targetmap:
 631.             self.targetmap[target] = {}
 632.         self.targetmap[target][name] = source
 633.         self.files_to_copy += 1
 634. 
 635.     def assign_targets(self, source, target):
 636.         # copy everything in the source into the target
 637.         assert isinstance(source, (LocalDirectorySource, TahoeDirectorySource))
 638. 
 639.         for name, child in source.children.items():
 640.             if isinstance(child, (LocalDirectorySource, TahoeDirectorySource)):
 641.                 # we will need a target directory for this one
 642.                 subtarget = target.get_child_target(name)
 643.                 self.assign_targets(child, subtarget)
 644.             else:
 645.                 assert isinstance(child, (LocalFileSource, TahoeFileSource))
 646.                 self.attach_to_target(child, name, target)
 647. 
 648. 
 649. 
 650.     def copy_files_to_target(self, targetmap, target):
 651.         for name, source in targetmap.items():
 652.             assert isinstance(source, (LocalFileSource, TahoeFileSource))
 653.             self.copy_file_into(source, name, target)
 654.             self.files_copied += 1
 655.             self.progress("%d/%d files, %d/%d directories" %
 656.                           (self.files_copied, self.files_to_copy,
 657.                            self.targets_finished, len(self.targetmap)))
 658.         target.set_children()
 659. 
 660.     def need_to_copy_bytes(self, source, target):
 661.         if source.need_to_copy_bytes:
 662.             # mutable tahoe files, and local files
 663.             return True
 664.         if isinstance(target, (LocalFileTarget, LocalDirectoryTarget)):
 665.             return True
 666.         return False
 667. 
 668.     def copy_file(self, source, target):
 669.         assert isinstance(source, (LocalFileSource, TahoeFileSource))
 670.         assert isinstance(target, (LocalFileTarget, TahoeFileTarget,
 671.                                    LocalMissingTarget, TahoeMissingTarget))
 672.         if self.need_to_copy_bytes(source, target):
 673.             # if the target is a local directory, this will just write the
 674.             # bytes to disk. If it is a tahoe directory, it will upload the
 675.             # data, and stash the new filecap for a later set_children call.
 676.             f = source.open()
 677.             target.put_file(f)
 678.             return
 679.         # otherwise we're copying tahoe to tahoe, and using immutable files,
 680.         # so we can just make a link. TODO: this probably won't always work:
 681.         # need to enumerate the cases and analyze them.
 682.         target.put_uri(source.bestcap())
 683. 
 684.     def copy_file_into(self, source, name, target):
 685.         assert isinstance(source, (LocalFileSource, TahoeFileSource))
 686.         assert isinstance(target, (LocalDirectoryTarget, TahoeDirectoryTarget))
 687.         if self.need_to_copy_bytes(source, target):
 688.             # if the target is a local directory, this will just write the
 689.             # bytes to disk. If it is a tahoe directory, it will upload the
 690.             # data, and stash the new filecap for a later set_children call.
 691.             f = source.open()
 692.             target.put_file(name, f)
 693.             return
 694.         # otherwise we're copying tahoe to tahoe, and using immutable files,
 695.         # so we can just make a link
 696.         target.put_uri(name, source.bestcap())
 697. 
 698. 
 699.     def progress(self, message):
 700.         #print message
 701.         if self.progressfunc:
 702.             self.progressfunc(message)
 703. 
 704.     def build_graphs(self, source_infos):
 705.         graphs = []
 706.         for name,source in source_infos:
 707.             if isinstance(source, (LocalDirectorySource, TahoeDirectorySource)):
 708.                 source.populate(True)
 709.                 graphs.append(source)
 710.         return graphs
 711. 
 712. 
 713. def copy(options):
 714.     return Copier().do_copy(options)