ssync.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #!/usr/bin/env python3
  2. import re
  3. import os
  4. import sys
  5. import shutil
  6. from configparser import ConfigParser
  7. from termcolor import colored
  8. import colorama
  9. CHECKFILE = "slidegen-checkfile.txt"
  10. CACHEFILE = "slidegen-cachefile.txt"
  11. NULNAME = ""
  12. if os.name == "nt":
  13. NULNAME = "NUL"
  14. else:
  15. NULNAME = "/dev/null"
  16. def error_msg(msg: str):
  17. print(colored("[*] Error: {}".format(msg), "red"))
  18. sys.exit(1)
  19. def log(message: str):
  20. print(colored("[*] {}".format(message), "green"))
  21. class Ssync:
  22. def __init__(self):
  23. self.parse_config()
  24. def parse_config(self):
  25. config_parser = ConfigParser()
  26. config_parser.read("config.ini")
  27. try:
  28. self.rclone_remote_dir = config_parser["RCLONE"]["remote_dir"]
  29. self.rclone_local_dir = config_parser["RCLONE"]["local_dir"]
  30. self.slidegen_exe_path = config_parser["SLIDEGEN"]["exe_path"]
  31. self.slidegen_cache_dir = config_parser["SLIDEGEN"]["cache_dir"]
  32. self.obs_slides_dir = config_parser["OBS"]["slides_dir"]
  33. self.obs_target_subdir = config_parser["OBS"]["target_subdir"]
  34. self.obs_min_subdirs = int(config_parser["OBS"]["min_subdirs"])
  35. except KeyError:
  36. error_msg("configuration file 'config.ini' could not be parsed")
  37. log("configuration initialised")
  38. def sync_slide_repo(self):
  39. log("syncing with remote slide repository...")
  40. os.system(
  41. "rclone sync -v {} {}".format(
  42. self.rclone_remote_dir, self.rclone_local_dir
  43. )
  44. )
  45. def clear_obs_slides_dir(self):
  46. log("clearing obs slides directory...")
  47. for filename in os.listdir(self.obs_slides_dir):
  48. file_path = os.path.join(self.obs_slides_dir, filename)
  49. try:
  50. if os.path.isfile(file_path) or os.path.islink(file_path):
  51. os.unlink(file_path)
  52. elif os.path.isdir(file_path):
  53. shutil.rmtree(file_path)
  54. except Exception as error:
  55. error_msg(
  56. "Failed to delete %s. Reason: %s" % (file_path, error)
  57. )
  58. def create_minimum_subdirs(self, count: int):
  59. if count >= self.obs_min_subdirs:
  60. return
  61. for number in range(count, self.obs_min_subdirs + 1):
  62. dirname = os.path.join(
  63. self.obs_slides_dir, self.obs_target_subdir + " " + str(number)
  64. )
  65. os.mkdir(dirname)
  66. def slide_selection_iterator(self):
  67. iterator_prompt = "Exit now? [y/N]: "
  68. structure_prompt = (
  69. "Choose song structure (leave blank for full song)"
  70. + " eg. [1,R,2,R] / [1-4]: "
  71. )
  72. file_list_str = ""
  73. for file in os.listdir(self.rclone_local_dir):
  74. file_list_str += file + "\n"
  75. file_list_str = file_list_str[:-1]
  76. tempfile_str = ".chosen-tempfile"
  77. index = 0
  78. while True:
  79. index += 1
  80. input_song_prompt = "[{} {}] ".format(self.obs_target_subdir, index)
  81. prompt_answer = str(input(input_song_prompt + iterator_prompt))
  82. if prompt_answer.lower() == "y":
  83. self.create_minimum_subdirs(index)
  84. break
  85. file_list_str = file_list_str.replace("\n", "\\n")
  86. os.system(
  87. 'printf "{}" | fzf > {}'.format(file_list_str, tempfile_str)
  88. )
  89. with open(
  90. tempfile_str, encoding="utf-8", mode="r"
  91. ) as tempfile_file_opener:
  92. chosen_song_file = tempfile_file_opener.read()[:-1].strip()
  93. if len(chosen_song_file) == 0:
  94. log("no slides chosen, skipping...")
  95. else:
  96. structure_prompt_answer = input(
  97. input_song_prompt + structure_prompt
  98. )
  99. log(
  100. "generating slides '{}' to '{} {}'...".format(
  101. chosen_song_file, self.obs_target_subdir, index
  102. )
  103. )
  104. src_dir = os.path.join(self.rclone_local_dir, chosen_song_file)
  105. dest_dir = os.path.join(
  106. self.slidegen_cache_dir,
  107. self.obs_target_subdir + " " + str(index),
  108. )
  109. os.mkdir(dest_dir)
  110. os.system(
  111. 'python3 "{}" "{}" "{}" "{}"'.format(
  112. self.slidegen_exe_path,
  113. src_dir,
  114. dest_dir,
  115. structure_prompt_answer,
  116. )
  117. )
  118. if os.path.isfile(tempfile_str):
  119. os.remove(tempfile_str)
  120. def cachefiles_found(self):
  121. return os.path.isfile(
  122. os.path.join(self.slidegen_cache_dir, CHECKFILE)
  123. ) and os.path.isfile(os.path.join(self.slidegen_cache_dir, CACHEFILE))
  124. def syncing_needed(self) -> bool:
  125. if not self.cachefiles_found():
  126. return True
  127. log("checking for remote changes...")
  128. os.system(
  129. 'rclone md5sum {} --checkfile {} > {} 2> {}'.format(
  130. self.rclone_remote_dir,
  131. os.path.join(self.slidegen_cache_dir, CHECKFILE),
  132. NULNAME,
  133. os.path.join(self.slidegen_cache_dir, CACHEFILE),
  134. )
  135. )
  136. with open(
  137. os.path.join(self.slidegen_cache_dir, CACHEFILE),
  138. mode="r",
  139. encoding="utf-8",
  140. ) as cachefile_reader:
  141. cachefile_content = cachefile_reader.readlines()
  142. for line in cachefile_content:
  143. if re.search(": ([0-9])+ differences found$", line):
  144. diffs = int(
  145. line[line.rfind(":") + 1 : line.find("differences")]
  146. )
  147. return bool(diffs)
  148. return False
  149. def save_new_checkfile(self):
  150. log("saving new checkfile...")
  151. os.system(
  152. 'rclone md5sum {} > "{}"'.format(
  153. self.rclone_remote_dir,
  154. os.path.join(self.slidegen_cache_dir, CHECKFILE),
  155. )
  156. )
  157. if not os.path.isfile(os.path.join(self.slidegen_cache_dir, CACHEFILE)):
  158. shutil.copyfile(
  159. os.path.join(self.slidegen_cache_dir, CHECKFILE),
  160. os.path.join(self.slidegen_cache_dir, CACHEFILE),
  161. )
  162. def execute(self):
  163. if self.syncing_needed():
  164. self.sync_slide_repo()
  165. self.save_new_checkfile()
  166. self.clear_obs_slides_dir()
  167. self.slide_selection_iterator()
  168. def main():
  169. colorama.init()
  170. ssync = Ssync()
  171. ssync.execute()
  172. if __name__ == "__main__":
  173. main()