rsync_remote.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2006-2013, Armin Rigo, Holger Krekel, Maciej Fijalkowski
  4. """
  5. def serve_rsync(channel):
  6. import os
  7. import stat
  8. import shutil
  9. from hashlib import md5
  10. destdir, options = channel.receive()
  11. modifiedfiles = []
  12. def remove(path):
  13. assert path.startswith(destdir)
  14. try:
  15. os.unlink(path)
  16. except OSError:
  17. # assume it's a dir
  18. shutil.rmtree(path, True)
  19. def receive_directory_structure(path, relcomponents):
  20. try:
  21. st = os.lstat(path)
  22. except OSError:
  23. st = None
  24. msg = channel.receive()
  25. if isinstance(msg, list):
  26. if st and not stat.S_ISDIR(st.st_mode):
  27. os.unlink(path)
  28. st = None
  29. if not st:
  30. os.makedirs(path)
  31. mode = msg.pop(0)
  32. if mode:
  33. os.chmod(path, mode)
  34. entrynames = {}
  35. for entryname in msg:
  36. destpath = os.path.join(path, entryname)
  37. receive_directory_structure(destpath, relcomponents + [entryname])
  38. entrynames[entryname] = True
  39. if options.get("delete"):
  40. for othername in os.listdir(path):
  41. if othername not in entrynames:
  42. otherpath = os.path.join(path, othername)
  43. remove(otherpath)
  44. elif msg is not None:
  45. assert isinstance(msg, tuple)
  46. checksum = None
  47. if st:
  48. if stat.S_ISREG(st.st_mode):
  49. msg_mode, msg_mtime, msg_size = msg
  50. if msg_size != st.st_size:
  51. pass
  52. elif msg_mtime != st.st_mtime:
  53. f = open(path, "rb")
  54. checksum = md5(f.read()).digest()
  55. f.close()
  56. elif msg_mode and msg_mode != st.st_mode:
  57. os.chmod(path, msg_mode)
  58. return
  59. else:
  60. return # already fine
  61. else:
  62. remove(path)
  63. channel.send(("send", (relcomponents, checksum)))
  64. modifiedfiles.append((path, msg))
  65. receive_directory_structure(destdir, [])
  66. STRICT_CHECK = False # seems most useful this way for py.test
  67. channel.send(("list_done", None))
  68. for path, (mode, time, size) in modifiedfiles:
  69. data = channel.receive()
  70. channel.send(("ack", path[len(destdir) + 1 :]))
  71. if data is not None:
  72. if STRICT_CHECK and len(data) != size:
  73. raise IOError("file modified during rsync: {!r}".format(path))
  74. f = open(path, "wb")
  75. f.write(data)
  76. f.close()
  77. try:
  78. if mode:
  79. os.chmod(path, mode)
  80. os.utime(path, (time, time))
  81. except OSError:
  82. pass
  83. del data
  84. channel.send(("links", None))
  85. msg = channel.receive()
  86. while msg != 42:
  87. # we get symlink
  88. _type, relpath, linkpoint = msg
  89. path = os.path.join(destdir, relpath)
  90. try:
  91. remove(path)
  92. except OSError:
  93. pass
  94. if _type == "linkbase":
  95. src = os.path.join(destdir, linkpoint)
  96. else:
  97. assert _type == "link", _type
  98. src = linkpoint
  99. os.symlink(src, path)
  100. msg = channel.receive()
  101. channel.send(("done", None))
  102. if __name__ == "__channelexec__":
  103. serve_rsync(channel) # noqa