DumpUpdater.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env python3
  2. # Contest Management System - http://cms-dev.github.io/
  3. # Copyright © 2013 Luca Wehrstedt <luca.wehrstedt@gmail.com>
  4. # Copyright © 2013-2015 Giovanni Mascellani <mascellani@poisson.phc.unipi.it>
  5. # Copyright © 2014 William Di Luigi <williamdiluigi@gmail.com>
  6. # Copyright © 2016 Stefano Maggiolo <s.maggiolo@gmail.com>
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. """A script to update a dump created by CMS.
  21. This script updates a dump (i.e. a set of exported contest data, as
  22. created by DumpExporter) of the Contest Management System from any
  23. of the old supported versions to the current one.
  24. """
  25. # We enable monkey patching to make many libraries gevent-friendly
  26. # (for instance, urllib3, used by requests)
  27. import gevent.monkey
  28. gevent.monkey.patch_all() # noqa
  29. import argparse
  30. import json
  31. import logging
  32. import os
  33. import shutil
  34. import sys
  35. from cms import utf8_decoder
  36. from cms.db import version as model_version
  37. from cmscommon.archive import Archive
  38. logger = logging.getLogger(__name__)
  39. def main():
  40. parser = argparse.ArgumentParser(
  41. description="Updater of CMS contest dumps.")
  42. parser.add_argument(
  43. "-V", "--to-version", action="store", type=int, default=-1,
  44. help="Update to given version number")
  45. parser.add_argument(
  46. "path", action="store", type=utf8_decoder,
  47. help="location of the dump or of the 'contest.json' file")
  48. args = parser.parse_args()
  49. path = args.path
  50. to_version = args.to_version
  51. if to_version == -1:
  52. to_version = model_version
  53. if not os.path.exists(path):
  54. logger.critical("The given path doesn't exist")
  55. return 1
  56. archive = None
  57. if Archive.is_supported(path):
  58. archive = Archive(path)
  59. path = archive.unpack()
  60. file_names = os.listdir(path)
  61. if len(file_names) != 1:
  62. logger.critical("Cannot find a root directory in the "
  63. "archive.")
  64. archive.cleanup()
  65. return 1
  66. path = os.path.join(path, file_names[0])
  67. if not path.endswith("contest.json"):
  68. path = os.path.join(path, "contest.json")
  69. if not os.path.exists(path):
  70. logger.critical(
  71. "The given path doesn't contain a contest dump in a format "
  72. "CMS is able to understand.")
  73. return 1
  74. with open(path, 'rb') as fin:
  75. data = json.load(fin)
  76. # If no "_version" field is found we assume it's a v1.0
  77. # export (before the new dump format was introduced).
  78. dump_version = data.get("_version", 0)
  79. if dump_version == to_version:
  80. logger.info(
  81. "The dump you're trying to update is already stored using "
  82. "the target format (which is version %d).", dump_version)
  83. return 1
  84. elif dump_version > model_version:
  85. logger.critical(
  86. "The dump you're trying to update is stored using data model "
  87. "version %d, which is more recent than the one supported by "
  88. "this version of CMS (version %d). You probably need to "
  89. "update CMS to handle it.", dump_version, model_version)
  90. return 1
  91. elif to_version > model_version:
  92. logger.critical(
  93. "The target data model (version %d) you're trying to update "
  94. "to is too recent for this version of CMS (which supports up "
  95. "to version %d). You probably need to update CMS to handle "
  96. "it.", to_version, model_version)
  97. return 1
  98. elif dump_version > to_version:
  99. logger.critical(
  100. "Backward updating (from version %d to version %d) is not "
  101. "supported.", dump_version, to_version)
  102. return 1
  103. for version in range(dump_version, to_version):
  104. # Update from version to version+1
  105. updater = __import__(
  106. "cmscontrib.updaters.update_%d" % (version + 1),
  107. globals(), locals(), ["Updater"]).Updater(data)
  108. data = updater.run()
  109. data["_version"] = version + 1
  110. assert data["_version"] == to_version
  111. with open(path, 'wt', encoding="utf-8") as fout:
  112. json.dump(data, fout, indent=4, sort_keys=True)
  113. if archive is not None:
  114. # Keep the old archive, just rename it
  115. shutil.move(archive.path, archive.path + ".bak")
  116. archive.repack(os.path.abspath(archive.path))
  117. archive.cleanup()
  118. return 0
  119. if __name__ == "__main__":
  120. sys.exit(main())