@@ -1599,8 +1599,89 @@ def mount_userdata_ro(self):
15991599 xbmc .log (f"Error mounting userdata as read-only: { str (e )} " , xbmc .LOGERROR )
16001600 return False
16011601
1602+ def mount_addons_rw (self ):
1603+ """Mount addons directory in read-write mode"""
1604+ try :
1605+ # Get the actual path for addons
1606+ addons_path = os .path .join (self .kodi_home , 'addons' )
1607+
1608+ # Check if addons directory is already writable
1609+ test_file = os .path .join (addons_path , '.write_test' )
1610+ try :
1611+ os .makedirs (addons_path , exist_ok = True )
1612+ with open (test_file , 'w' ) as f :
1613+ f .write ('test' )
1614+ os .remove (test_file )
1615+ xbmc .log ("Addons directory is already writable" , xbmc .LOGINFO )
1616+ return True
1617+ except (IOError , PermissionError , OSError ):
1618+ xbmc .log ("Addons directory is not writable, attempting to remount" , xbmc .LOGINFO )
1619+
1620+ # Find the mount point that contains addons
1621+ mount_info = subprocess .run (['mount' ], capture_output = True , text = True , check = True )
1622+ mount_lines = mount_info .stdout .splitlines ()
1623+
1624+ addons_mount = None
1625+ for line in mount_lines :
1626+ parts = line .split ()
1627+ if len (parts ) >= 3 and addons_path .startswith (parts [2 ]):
1628+ addons_mount = parts [2 ]
1629+ break
1630+
1631+ if addons_mount :
1632+ xbmc .log (f"Mounting { addons_mount } as read-write" , xbmc .LOGINFO )
1633+ subprocess .run (['mount' , '-o' , 'remount,rw' , addons_mount ], check = True )
1634+
1635+ # Verify it's now writable
1636+ try :
1637+ os .makedirs (addons_path , exist_ok = True )
1638+ with open (test_file , 'w' ) as f :
1639+ f .write ('test' )
1640+ os .remove (test_file )
1641+ xbmc .log ("Verified addons directory is now writable" , xbmc .LOGINFO )
1642+ return True
1643+ except (IOError , PermissionError , OSError ):
1644+ xbmc .log ("Addons directory is still not writable after remount" , xbmc .LOGERROR )
1645+ return False
1646+ else :
1647+ xbmc .log (f"Could not find mount point for addons: { addons_path } " , xbmc .LOGERROR )
1648+ return False
1649+
1650+ except Exception as e :
1651+ xbmc .log (f"Error mounting addons as read-write: { str (e )} " , xbmc .LOGERROR )
1652+ return False
1653+
1654+ def mount_addons_ro (self ):
1655+ """Mount addons directory back in read-only mode"""
1656+ try :
1657+ # Get the actual path for addons
1658+ addons_path = os .path .join (self .kodi_home , 'addons' )
1659+
1660+ # Find the mount point that contains addons
1661+ mount_info = subprocess .run (['mount' ], capture_output = True , text = True , check = True )
1662+ mount_lines = mount_info .stdout .splitlines ()
1663+
1664+ addons_mount = None
1665+ for line in mount_lines :
1666+ parts = line .split ()
1667+ if len (parts ) >= 3 and addons_path .startswith (parts [2 ]):
1668+ addons_mount = parts [2 ]
1669+ break
1670+
1671+ if addons_mount :
1672+ xbmc .log (f"Remounting { addons_mount } as read-only" , xbmc .LOGINFO )
1673+ subprocess .run (['mount' , '-o' , 'remount,ro' , addons_mount ], check = True )
1674+ return True
1675+ else :
1676+ xbmc .log (f"Could not find mount point for addons: { addons_path } " , xbmc .LOGERROR )
1677+ return False
1678+
1679+ except Exception as e :
1680+ xbmc .log (f"Error mounting addons as read-only: { str (e )} " , xbmc .LOGERROR )
1681+ return False
1682+
16021683 def restore_file (self , zip_file , file_info , extract_path ):
1603- """Restore a single file with special handling for config.txt and userdata """
1684+ """Restore a single file with special handling for config.txt, userdata, and addons """
16041685 try :
16051686 # Handle configuration files that need /flash to be writable
16061687 if extract_path == '/flash/config.txt' or extract_path .startswith ('/flash/' ):
@@ -1693,6 +1774,55 @@ def restore_file(self, zip_file, file_info, extract_path):
16931774
16941775 return True , None
16951776
1777+ # Handle addons files
1778+ elif extract_path .startswith (os .path .join (self .kodi_home , 'addons' )):
1779+ xbmc .log (f"Preparing to restore addon file: { extract_path } " , xbmc .LOGINFO )
1780+
1781+ # Mount addons directory in read-write mode
1782+ if not self .mount_addons_rw ():
1783+ xbmc .log ("Failed to mount addons directory in read-write mode" , xbmc .LOGERROR )
1784+ return False , "Failed to mount addons directory in read-write mode"
1785+
1786+ xbmc .log ("Addons directory mounted in read-write mode" , xbmc .LOGINFO )
1787+ restore_success = False
1788+
1789+ try :
1790+ # Ensure the directory exists
1791+ os .makedirs (os .path .dirname (extract_path ), exist_ok = True )
1792+
1793+ # Extract the file
1794+ with zip_file .open (file_info ) as source , open (extract_path , 'wb' ) as target :
1795+ shutil .copyfileobj (source , target )
1796+
1797+ xbmc .log (f"Addon file extracted successfully: { extract_path } " , xbmc .LOGINFO )
1798+
1799+ # Ensure proper permissions (644 for files, 755 for directories)
1800+ if os .path .isdir (extract_path ):
1801+ os .chmod (extract_path , 0o755 )
1802+ else :
1803+ os .chmod (extract_path , 0o644 )
1804+
1805+ restore_success = True
1806+ except Exception as e :
1807+ xbmc .log (f"Error during addon file restore: { str (e )} " , xbmc .LOGERROR )
1808+ raise e
1809+ finally :
1810+ # Always try to remount as read-only
1811+ xbmc .log ("Attempting to remount addons directory as read-only" , xbmc .LOGINFO )
1812+ if not self .mount_addons_ro ():
1813+ error_msg = "Warning: Failed to remount addons directory as read-only"
1814+ xbmc .log (error_msg , xbmc .LOGWARNING )
1815+ # If restore was successful but remount failed, still warn the user
1816+ if restore_success :
1817+ self .notify (error_msg )
1818+ else :
1819+ xbmc .log ("Addons directory remounted as read-only" , xbmc .LOGINFO )
1820+
1821+ if not restore_success :
1822+ return False , f"Failed to restore { os .path .basename (extract_path )} "
1823+
1824+ return True , None
1825+
16961826 else :
16971827 # Normal file extraction
16981828 # Ensure the directory exists
@@ -1865,8 +1995,17 @@ def restore_backup(self, backup_file=None):
18651995 if file_info .filename .startswith ('userdata/' ):
18661996 # Handle userdata paths correctly
18671997 extract_path = os .path .join (self .kodi_userdata , os .path .relpath (file_info .filename , 'userdata' ))
1998+ elif file_info .filename .startswith ('addons/' ):
1999+ # Handle addons paths correctly
2000+ extract_path = os .path .join (self .kodi_home , file_info .filename )
2001+ elif file_info .filename .startswith ('flash/' ):
2002+ # Handle flash paths correctly
2003+ extract_path = os .path .join ('/' , file_info .filename )
2004+ elif file_info .filename .startswith ('repo/' ):
2005+ # Handle repository paths correctly (repositories are in addons directory)
2006+ extract_path = os .path .join (self .kodi_home , 'addons' , os .path .relpath (file_info .filename , 'repo' ))
18682007 else :
1869- # Handle all other files
2008+ # Handle all other files (assume they're relative to root)
18702009 extract_path = os .path .join ('/' , file_info .filename )
18712010
18722011 # Restore the file with special handling for config.txt
0 commit comments