@@ -118,31 +118,45 @@ def exit_if_cancelled() -> None:
118118 ...
119119 sys .exit (0 )
120120
121+ LOCK_FILE_PATH = os .path .join (tempfile .gettempdir (), 'kindle_epub_fixer.lock' )
122+ lock_acquired = False
123+
121124### LOCK FILES
122125# Creates a lock file unless one already exists meaning an instance of the script is
123126# already running, then the script is closed, the user is notified and the program
124127# exits with code 2
125- try :
126- lock = open (tempfile .gettempdir () + '/kindle_epub_fixer.lock' , 'x' )
127- lock .close ()
128- except FileExistsError :
129- print_and_log ("[cwa-kindle-epub-fixer] CANCELLING... kindle-epub-fixer was initiated but is already running" )
130- logger .info (f"\n CWA Kindle EPUB Fixer Service - Run Ended: { datetime .now ()} " )
131- sys .exit (2 )
128+ def acquire_lock ():
129+ global lock_acquired
130+ try :
131+ with open (LOCK_FILE_PATH , 'x' ):
132+ pass
133+ lock_acquired = True
134+ except FileExistsError :
135+ print_and_log ("[cwa-kindle-epub-fixer] CANCELLING... kindle-epub-fixer was initiated but is already running" )
136+ logger .info (f"\n CWA Kindle EPUB Fixer Service - Run Ended: { datetime .now ()} " )
137+ sys .exit (2 )
132138
133139# Defining function to delete the lock on script exit
134140def removeLock ():
141+ global lock_acquired
142+ if not lock_acquired :
143+ return
135144 try :
136- os .remove (tempfile . gettempdir () + '/kindle_epub_fixer.lock' )
145+ os .remove (LOCK_FILE_PATH )
137146 except FileNotFoundError :
138147 ...
148+ finally :
149+ lock_acquired = False
139150
140151# Will automatically run when the script exits
141152atexit .register (removeLock )
142153
143154
144155class EPUBFixer :
145156 def __init__ (self , manually_triggered :bool = False , current_position :str = None ):
157+ if not lock_acquired :
158+ acquire_lock ()
159+
146160 self .manually_triggered = manually_triggered
147161 self .current_position = current_position # string in the form of "n/n"
148162
@@ -311,24 +325,46 @@ def _update_html_charset(self, content: str, target_encoding: str) -> str:
311325 if charset .startswith ('utf-16' ):
312326 charset = 'utf-16'
313327
314- http_equiv_pattern = re .compile (
315- r'(<meta[^>]+http-equiv=["\']content-type["\'][^>]*content=["\'][^"\']*charset=)([^"\'>\s;]+)([^"\']*["\'][^>]*>)' ,
316- re .IGNORECASE
317- )
318- if http_equiv_pattern .search (content ):
319- return http_equiv_pattern .sub (rf"\1{ charset } \3" , content , count = 1 )
328+ http_equiv_meta = f'<meta http-equiv="Content-Type" content="text/html; charset={ charset } " />'
329+ meta_tag_pattern = re .compile (r'<meta\b[^>]*>' , re .IGNORECASE )
330+ http_equiv_pattern = re .compile (r'\bhttp-equiv\s*=\s*["\']content-type["\']' , re .IGNORECASE )
331+ content_attr_pattern = re .compile (r'\bcontent\s*=\s*(["\'])(.*?)\1' , re .IGNORECASE | re .DOTALL )
332+ charset_pattern = re .compile (r'(charset\s*=\s*)[^;\s"\']+' , re .IGNORECASE )
333+ replaced_http_equiv = False
334+
335+ def update_http_equiv (match : re .Match ) -> str :
336+ nonlocal replaced_http_equiv
337+ tag = match .group (0 )
338+ if replaced_http_equiv or not http_equiv_pattern .search (tag ):
339+ return tag
340+
341+ content_match = content_attr_pattern .search (tag )
342+ if not content_match or not charset_pattern .search (content_match .group (2 )):
343+ return tag
344+
345+ content_value = charset_pattern .sub (
346+ lambda charset_match : f"{ charset_match .group (1 )} { charset } " ,
347+ content_match .group (2 ),
348+ count = 1 ,
349+ )
350+ replaced_http_equiv = True
351+ return tag [:content_match .start (2 )] + content_value + tag [content_match .end (2 ):]
352+
353+ updated_content = meta_tag_pattern .sub (update_http_equiv , content )
354+ if replaced_http_equiv :
355+ return updated_content
320356
321- meta_charset_pattern = re .compile (r'<meta[^>]+charset=["\']?[^"\'>\s]+[^>]*>' , re .IGNORECASE )
357+ meta_charset_pattern = re .compile (r'<meta\b [^>]+charset=["\']?[^"\'>\s]+[^>]*>' , re .IGNORECASE )
322358 if meta_charset_pattern .search (content ):
323- return meta_charset_pattern .sub (f'<meta charset=" { charset } ">' , content , count = 1 )
359+ return meta_charset_pattern .sub (http_equiv_meta , content , count = 1 )
324360
325361 head_pattern = re .compile (r'<head[^>]*>' , re .IGNORECASE )
326362 match = head_pattern .search (content )
327363 if match :
328364 insert_at = match .end ()
329- return content [:insert_at ] + f"\n <meta charset= \" { charset } \" > " + content [insert_at :]
365+ return content [:insert_at ] + f"\n { http_equiv_meta } " + content [insert_at :]
330366
331- return f"<meta charset= \" { charset } \" > \n " + content
367+ return content
332368
333369 def _extract_book_info_from_path (self , file_path : str ) -> tuple [int | None , str ]:
334370 """Extract book ID and format from file path.
@@ -1152,6 +1188,8 @@ def get_all_epubs_in_library() -> list[str]:
11521188
11531189
11541190def main ():
1191+ acquire_lock ()
1192+
11551193 parser = argparse .ArgumentParser (
11561194 prog = 'kindle-epub-fixer' ,
11571195 description = 'Checks the encoding of a given EPUB file and automatically corrects any errors that could \
0 commit comments