ssync.py 6.5 KB

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