88import argparse
99import signal
1010import json
11+ import shutil
1112import settings
1213
1314from pathlib import Path
1920parser .add_argument ("--add-id" , help = "Add a TMDBid to watch." , type = int )
2021parser .add_argument ("--skip-hash" , help = "Skip the file hash check." , action = "store_true" )
2122parser .add_argument ("--no-delete" , help = "Don't delete the original file." , action = "store_true" )
23+ parser .add_argument ("--no-save" , help = "Don't save." , action = "store_true" )
2224parser .add_argument ("--force-state-change" , help = "Skip the file hash check." , type = int )
2325parser .add_argument ("-c" , "--config" , help = "Path to the config file." , type = str )
2426args = parser .parse_args ()
@@ -82,9 +84,9 @@ def update_state():
8284 found = False
8385 unwatch = []
8486 if len (settings .watch ) == 0 :
85- logger .info ("Watch queue empty." )
87+ logger .debug ("Watch queue empty." )
8688 return
87- logger .info ("Looking for state changes..." )
89+ logger .debug ("Looking for state changes..." )
8890 for id in settings .watch :
8991 movie = settings .radarr .get_movie (id )
9092 if len (movie ) == 1 :
@@ -99,7 +101,7 @@ def update_state():
99101 for i in unwatch :
100102 settings .watch .remove (i )
101103 if not found :
102- logger .info ("No state changes found." )
104+ logger .debug ("No state changes found." )
103105
104106def blake (file ):
105107 with open (file , "rb" ) as f :
@@ -108,7 +110,7 @@ def blake(file):
108110 file_hash .update (chunk )
109111 return file_hash .hexdigest ()
110112
111- def move_torrent (t , movie ):
113+ def move_torrent (t , movie , rename ):
112114 # Removing before hash check to prevent threads from analyzing the same file
113115 if movie ['tmdbId' ] in settings .changed :
114116 settings .changed .remove (movie ['tmdbId' ])
@@ -126,15 +128,46 @@ def move_torrent(t, movie):
126128 logger .info (f"{ lib_file } and { download_file } hashes are identical." )
127129 else :
128130 logger .info ("'--skip-hash' set, skipping hash check." )
129- logger .info (f"Moving torrent({ t ['hash' ]} ) from { t ['content_path' ]} to { new_path } " )
131+ if rename :
132+ logger .debug ("This torrent is in a folder and requires a rename before moving." )
133+ rename_path = os .path .dirname (new_path )
134+ logger .debug (f"Renaming { ntpath .basename (os .path .dirname (t ['content_path' ]))} to { ntpath .basename (rename_path )} " )
135+ try :
136+ settings .client .torrents_rename_folder (t ['hash' ], ntpath .basename (os .path .dirname (t ['content_path' ])), ntpath .basename (rename_path ))
137+ # Let the client rename the folder
138+ time .sleep (5 )
139+ logger .info (f"Torrent moved, deleting old directory({ os .path .dirname (t ['content_path' ])} )" )
140+ # Failsafe
141+ if os .path .dirname (t ['content_path' ]) != cfg .read_config ("torrent_library_directory" ) and os .path .dirname (t ['content_path' ]) != cfg .read_config ("radarr_library_directory" ) and os .path .dirname (t ['content_path' ]) != cfg .read_config ("torrent_download_directory" ):
142+ try :
143+ shutil .rmtree (os .path .dirname (t ['content_path' ]))
144+ except Exception as ex :
145+ logger .error (f"Error deleting old directory: { ex } " )
146+ logger .debug (f"Error deleting old directory({ os .path .dirname (t ['content_path' ])} ): { ex } " )
147+ else :
148+ logger .error (f"Hitting failsafe to prevent library deletion -> { os .path .dirname (t ['content_path' ])} " )
149+ t ['content_path' ] = os .path .join (cfg .read_config ("torrent_download_directory" ), ntpath .basename (rename_path ))
150+ except Exception as ee :
151+ logger .error (f"Error renaming torrent: { ee } " )
152+ logger .debug (f"Error renaming torrent | { t ['content_path' ]} , { rename_path } , { new_path } : { ee } " )
130153 try :
131154 og_path = t ['content_path' ]
132- t .set_location (location = os .path .dirname (new_path ))
155+ if rename :
156+ logger .info (f"Moving torrent({ t ['hash' ]} ) from { t ['content_path' ]} to { os .path .dirname (new_path )} " )
157+ logger .debug ("Renamed -> set_location" )
158+ t .set_location (location = cfg .read_config ("torrent_library_directory" ))
159+ else :
160+ logger .info (f"Moving torrent({ t ['hash' ]} ) from { t ['content_path' ]} to { new_path } " )
161+ t .set_location (location = os .path .dirname (new_path ))
133162 logger .info (f"{ movie ['title' ]} moved. Removing from watch queue." )
134163 if movie ['tmdbId' ] in settings .changed :
135164 settings .changed .remove (movie ['tmdbId' ])
136165 logger .info (f"Queueing { t ['name' ]} for deletion" )
166+ if rename :
167+ # Renamed files only have a folder, adding a fake file to prevent dirname going up too much.
168+ og_path = os .path .join (og_path , "fakefile.mkv" )
137169 if {"torrent" : t , "original_path" : og_path } not in settings .to_delete :
170+ logger .debug ({"torrent" : t , "original_path" : og_path })
138171 settings .to_delete .append ({"torrent" : t , "original_path" : og_path })
139172 except Exception as e :
140173 logger .error (f"Could not move torrent: { t ['name' ]} . { e } " )
@@ -146,20 +179,26 @@ def match_and_move_torrents():
146179 found = False
147180 torrents = settings .client .torrents_info (category = cfg .read_config ("torrent_category" ))
148181 if len (settings .changed ) == 0 :
149- logger .info ("Change queue empty." )
182+ logger .debug ("Change queue empty." )
150183 return
151184 if len (torrents ) > 0 :
152- logger .info ("Looking for torrent file match..." )
185+ logger .debug ("Looking for torrent file match..." )
153186 for id in settings .changed :
154187 movie = settings .radarr .get_movie (id )
155188 if len (movie ) == 1 :
156189 movie = movie [0 ]
157- logger .info (f"Looking for movie: { movie ['title' ]} ({ movie ['movieFile' ]['relativePath' ]} )" )
190+ logger .debug (f"Looking for movie: { movie ['title' ]} ({ movie ['movieFile' ]['relativePath' ]} )" )
158191 for t in torrents :
159- if ntpath .basename (t ['content_path' ]) == movie ['movieFile' ]['relativePath' ]:
160- found = True
161- logger .info (f"Found a match with torrent: { t ['name' ]} ." )
162- move_torrent (t , movie )
192+ for f in settings .client .torrents_files (torrent_hash = t ['hash' ]):
193+ if movie ['movieFile' ]['relativePath' ] == ntpath .basename (f ['name' ]):
194+ found = True
195+ rename_needed = False
196+ logger .info (f"Found a match for { movie ['title' ]} with torrent: { t ['name' ]} ." )
197+ t ['content_path' ] = os .path .join (t ['content_path' ],ntpath .basename (f ['name' ]))
198+ if ntpath .basename (f ['name' ]) != f ['name' ]:
199+ rename_needed = True
200+ move_torrent (t , movie , rename_needed )
201+ break
163202 if not found and len (torrents ) > 0 :
164203 logger .warning (f"{ len (settings .changed )} changes in Radarr but no match found in your torrent client." )
165204
@@ -168,28 +207,34 @@ def check_and_delete():
168207 for torrent in settings .to_delete :
169208 status = settings .client .torrents_info (torrent_hashes = torrent ["torrent" ]['hash' ])[0 ]
170209 if status ['state' ] not in ["error" , "checkingUP" , "moving" , "unknown" ]:
171- logger .info (f"{ status ['name' ]} 's state is { status ['state' ]} , deleting: { torrent ['original_path' ]} " )
210+ logger .info (f"{ status ['name' ]} 's state is ' { status ['state' ]} ' , deleting: { os . path . dirname ( torrent ['original_path' ]) } " )
172211 if not args .no_delete :
173212 try :
174- os .remove (torrent ['original_path' ])
213+ # Failsafe
214+ if os .path .dirname (torrent ['original_path' ]) != cfg .read_config ("torrent_library_directory" ) and os .path .dirname (torrent ['original_path' ]) != cfg .read_config ("radarr_library_directory" ) and os .path .dirname (torrent ['original_path' ]) != cfg .read_config ("torrent_download_directory" ):
215+ shutil .rmtree (os .path .dirname (torrent ['original_path' ]))
216+ else :
217+ logger .error (f"Hitting failsafe to prevent library deletion -> { os .path .dirname (torrent ['original_path' ])} " )
175218 if torrent not in deleted :
176219 deleted .append (torrent )
177- logger .info (f"{ torrent ['original_path' ]} deleted." )
220+ logger .info (f"{ os . path . dirname ( torrent ['original_path' ]) } deleted." )
178221 except Exception as e :
179- logger .error (f"Error deleting { torrent ['original_path' ]} , { e } " )
222+ logger .error (f"Error deleting { os . path . dirname ( torrent ['original_path' ]) } , { e } " )
180223 else :
181224 logger .info ("'--no-delete' set, skipping deletion." )
182225 if torrent not in deleted :
183226 deleted .append (torrent )
184227 else :
185- logger .info (f"{ status ['name' ]} 's state is { status ['state' ]} , not ready for deletion." )
228+ logger .debug (f"{ status ['name' ]} 's state is ' { status ['state' ]} ' , not ready for deletion." )
186229 for i in deleted :
187230 settings .to_delete .remove (i )
188231
189232def save ():
233+ if args .no_save :
234+ return
190235 savepath = os .path .dirname (settings .config_file )
191236 try :
192- logger .info ("Saving..." )
237+ logger .debug ("Saving..." )
193238 watch = open (os .path .join (savepath , "watch.json" ), "w" )
194239 changed = open (os .path .join (savepath , "changed.json" ), "w" )
195240 to_delete = open (os .path .join (savepath , "to_delete.json" ), "w" )
@@ -199,7 +244,7 @@ def save():
199244 watch .close ()
200245 changed .close ()
201246 to_delete .close ()
202- logger .info ("Saved." )
247+ logger .debug ("Saved." )
203248 except Exception as e :
204249 logger .error (f"Error saving data. { e } " )
205250
@@ -227,7 +272,7 @@ def load():
227272 except Exception as e :
228273 logger .error (f"Error loading data. { e } " )
229274
230- def clean_shutdown (force = False ):
275+ def clean_shutdown (force = True ):
231276 save ()
232277 sched .shutdown (wait = not force )
233278 logger .info ("Goodbye." )
0 commit comments