from abc import ABC, abstractmethod from ..const import (DRIVE_FOLDER_URL_FORMAT, ERROR_BACKUP_FOLDER_INACCESSIBLE, ERROR_BACKUP_FOLDER_MISSING, ERROR_BAD_PASSWORD_KEY, ERROR_CREDS_EXPIRED, ERROR_DRIVE_FULL, ERROR_EXISTING_FOLDER, ERROR_GOOGLE_CONNECT, ERROR_GOOGLE_CRED_PROCESS, ERROR_GOOGLE_DNS, ERROR_GOOGLE_INTERNAL, ERROR_GOOGLE_SESSION, ERROR_GOOGLE_TIMEOUT, ERROR_HA_DELETE_ERROR, ERROR_INVALID_CONFIG, ERROR_LOGIC, ERROR_LOW_SPACE, ERROR_MULTIPLE_DELETES, ERROR_NO_BACKUP, ERROR_NOT_UPLOADABLE, ERROR_PLEASE_WAIT, ERROR_PROTOCOL, ERROR_BACKUP_IN_PROGRESS, ERROR_UPLOAD_FAILED, LOG_IN_TO_DRIVE, SUPERVISOR_PERMISSION, ERROR_GOOGLE_UNEXPECTED, ERROR_SUPERVISOR_TIMEOUT, ERROR_SUPERVISOR_UNEXPECTED, ERROR_SUPERVISOR_FILE_SYSTEM) def ensureKey(key, target, name): if key not in target: raise ProtocolError(key, name, target) return target[key] class KnownError(Exception, ABC): @abstractmethod def message(self) -> str: pass @abstractmethod def code(self) -> str: pass def httpStatus(self) -> int: return 500 def data(self): return {} def retrySoon(self): return True class KnownTransient(KnownError): pass class SimulatedError(KnownError): def __init__(self, code=None): self._code = code def code(self): return self._code def message(self): return "Gave code " + str(self._code) class LogicError(KnownError): def __init__(self, message=None): self._message = message def message(self): return self._message def code(self): return ERROR_LOGIC class ProtocolError(KnownError): def __init__(self, parameter=None, object_name=None, debug_object=None): self._parameter = parameter self._object_name = object_name self._debug_object = debug_object def message(self): if self._object_name: return "Required key '{0}' was missing from {1}".format(self._parameter, self._object_name) else: return self._parameter def code(self): return ERROR_PROTOCOL class BackupInProgress(KnownError): def message(self): return "A backup is already in progress" def code(self): return ERROR_BACKUP_IN_PROGRESS class BackupPasswordKeyInvalid(KnownError): def message(self): return "Couldn't find your backup password in your secrets file. Please check your settings." def code(self): return ERROR_BAD_PASSWORD_KEY def retrySoon(self): return False class UploadFailed(KnownError): def message(self): return "Backup upload failed. Please check the supervisor logs for details." def code(self): return ERROR_UPLOAD_FAILED class GoogleCredentialsExpired(KnownError): def message(self): return "Your Google Drive credentials have expired. Please reauthorize with Google Drive through the Web UI." def code(self): return ERROR_CREDS_EXPIRED def retrySoon(self): return False class NoBackup(KnownError): def message(self): return "The backup doesn't exist anymore" def code(self): return ERROR_NO_BACKUP class NotUploadable(KnownError): def message(self): return "This backup can't be uploaded to Home Assistant yet" def code(self): return ERROR_NOT_UPLOADABLE class PleaseWait(KnownError): def message(self): return "Please wait until the sync is finished." def code(self): return ERROR_PLEASE_WAIT class InvalidConfigurationValue(KnownError): def __init__(self, key=None, current=None): self.key = key self.current = current def message(self): return "'{0}' isn't a valid value for {1}".format(str(self.current), str(self.key)) def code(self): return ERROR_INVALID_CONFIG # UI Handler Done and updated class DeleteMutlipleBackupsError(KnownError): def __init__(self, delete_sources=None): self.delete_sources = delete_sources def message(self): return "The add-on has been configured to delete more than one older backups. Please confirm this by visiting the add-on's web UI or by setting the config option 'confirm_multiple_deletes'=false in your add-on configuration." def code(self): return ERROR_MULTIPLE_DELETES def data(self): return self.delete_sources def retrySoon(self): return False class DriveQuotaExceeded(KnownError): def message(self): return "Google Drive is out of space" def code(self): return ERROR_DRIVE_FULL def retrySoon(self): return False class GoogleDnsFailure(KnownError): def message(self): return "Unable to resolve host www.googleapis.com" def code(self): return ERROR_GOOGLE_DNS class GoogleCantConnect(KnownError): def message(self): return "Unable to connect to www.googleapis.com" def code(self): return ERROR_GOOGLE_CONNECT class GoogleInternalError(KnownTransient): def message(self): return "Google Drive returned an internal error (HTTP: 5XX)" def code(self): return ERROR_GOOGLE_INTERNAL class GoogleTimeoutError(KnownError): def message(self): return "Timed out while trying to reach Google Drive" def code(self): return ERROR_GOOGLE_TIMEOUT @classmethod def factory(cls): return GoogleTimeoutError() class GoogleRateLimitError(KnownTransient): def message(self): return "The addon has made too many requests to Google Drive, and will back off" def code(self): return "google_rate_limit" class GoogleSessionError(KnownError): def message(self): return "Upload session with Google Drive expired. The upload could not complete." def code(self): return ERROR_GOOGLE_SESSION class HomeAssistantDeleteError(KnownError): def message(self): return "Home Assistant refused to delete the backup." def code(self): return ERROR_HA_DELETE_ERROR class ExistingBackupFolderError(KnownError): def __init__(self, existing_id: str = None, existing_name: str = None): self.existing_id = existing_id self.existing_name = existing_name def message(self): return "A backup folder already exists. Please visit the add-on Web UI to select where to backup." def code(self): return ERROR_EXISTING_FOLDER def data(self): return { "existing_url#href": DRIVE_FOLDER_URL_FORMAT.format(self.existing_id), "existing_name": self.existing_name } def retrySoon(self): return False class BackupFolderMissingError(KnownError): def message(self): return "Please visit the add-on Web UI to select where to backup." def code(self): return ERROR_BACKUP_FOLDER_MISSING def retrySoon(self): return False class BackupFolderInaccessible(KnownError): def __init__(self, existing_id: str = None): self.existing_id = existing_id def message(self): return "The choosen backup folder has become inaccessible. Please visit the addon web UI to select a backup folder." def data(self): return { "existing_url#href": DRIVE_FOLDER_URL_FORMAT.format(self.existing_id) } def code(self): return ERROR_BACKUP_FOLDER_INACCESSIBLE class GoogleDrivePermissionDenied(KnownError): def message(self): return "Google Drive denied the request due to permissions." def code(self): return "google_drive_permissions" class LowSpaceError(KnownError): def __init__(self, pct_used=None, space_remaining=None): self.pct_used = pct_used self.space_remaining = space_remaining def message(self): return "Your backup folder is low on disk space. Backups can't be created until space is available." def code(self): return ERROR_LOW_SPACE def data(self): return { "pct_used": self.pct_used, "space_remaining": self.space_remaining } class SupervisorConnectionError(KnownError): def message(self): return "The addon couldn't connect to the supervisor. Backups can't continue until the supervisor is responding." def code(self): return "supervisor_connection" class UserCancelledError(KnownError): def message(self): return "Sync was cancelled by you" def code(self): return "cancelled" def retrySoon(self): return False class CredRefreshGoogleError(KnownError): def __init__(self, from_google=None): self.from_google = from_google def message(self): return "Couldn't refresh your credentials with Google because: '{}'".format(self.from_google) def code(self): return "token_refresh_google_error" def data(self): return { "from_google": self.from_google } class CredRefreshMyError(KnownError): def __init__(self, reason: str = None): self.reason = reason def message(self): return "Couldn't refresh Google Drive credentials because: {}".format(self.reason) def code(self): return "token_refresh_my_error" def data(self): return { "reason": self.reason } class LogInToGoogleDriveError(KnownError): def message(self): return "Please visit drive.google.com to activate your Google Drive account." def code(self): return LOG_IN_TO_DRIVE def retrySoon(self): return False class SupervisorPermissionError(KnownError): def message(self): return "The supervisor is rejecting requests from the addon. Please visit the web-UI for guidance" def code(self): return SUPERVISOR_PERMISSION def retrySoon(self): return True class GoogleUnexpectedError(KnownError): def message(self): return "Google gave an unexpected response" def code(self): return ERROR_GOOGLE_UNEXPECTED @classmethod def factory(cls): return GoogleUnexpectedError() class SupervisorTimeoutError(KnownError): def message(self): return "A request to the supervisor timed out" def code(self): return ERROR_SUPERVISOR_TIMEOUT @classmethod def factory(cls): return SupervisorTimeoutError() class SupervisorUnexpectedError(KnownError): def message(self): return "The supervisor gave an unexpected response" def code(self): return ERROR_SUPERVISOR_UNEXPECTED @classmethod def factory(cls): return SupervisorUnexpectedError() class SupervisorFileSystemError(KnownError): def message(self): return "The host file system is read-only. Please restart Home Assistant and verify you have enough free space." def code(self): return ERROR_SUPERVISOR_FILE_SYSTEM class GoogleCredGenerateError(KnownError): def __init__(self, message): self._msg = message def message(self): return self._msg def code(self): return ERROR_GOOGLE_CRED_PROCESS