5656from .const import (
5757 ATTR_ADDITIONAL_LOCATIONS ,
5858 ATTR_BACKGROUND ,
59+ ATTR_LOCATION_ATTRIBUTES ,
5960 ATTR_LOCATIONS ,
60- ATTR_PROTECTED_LOCATIONS ,
6161 ATTR_SIZE_BYTES ,
6262 CONTENT_TYPE_TAR ,
6363)
6767
6868ALL_ADDONS_FLAG = "ALL"
6969
70+ LOCATION_LOCAL = ".local"
71+
7072RE_SLUGIFY_NAME = re .compile (r"[^A-Za-z0-9]+" )
7173RE_BACKUP_FILENAME = re .compile (r"^[^\\\/]+\.tar$" )
7274
@@ -82,20 +84,31 @@ def _ensure_list(item: Any) -> list:
8284 return item
8385
8486
87+ def _convert_local_location (item : str | None ) -> str | None :
88+ """Convert local location value."""
89+ if item in {LOCATION_LOCAL , "" }:
90+ return None
91+ return item
92+
93+
8594# pylint: disable=no-value-for-parameter
95+ SCHEMA_FOLDERS = vol .All ([vol .In (_ALL_FOLDERS )], vol .Unique ())
96+ SCHEMA_LOCATION = vol .All (vol .Maybe (str ), _convert_local_location )
97+ SCHEMA_LOCATION_LIST = vol .All (_ensure_list , [SCHEMA_LOCATION ], vol .Unique ())
98+
8699SCHEMA_RESTORE_FULL = vol .Schema (
87100 {
88101 vol .Optional (ATTR_PASSWORD ): vol .Maybe (str ),
89102 vol .Optional (ATTR_BACKGROUND , default = False ): vol .Boolean (),
90- vol .Optional (ATTR_LOCATION ): vol . Maybe ( str ) ,
103+ vol .Optional (ATTR_LOCATION ): SCHEMA_LOCATION ,
91104 }
92105)
93106
94107SCHEMA_RESTORE_PARTIAL = SCHEMA_RESTORE_FULL .extend (
95108 {
96109 vol .Optional (ATTR_HOMEASSISTANT ): vol .Boolean (),
97110 vol .Optional (ATTR_ADDONS ): vol .All ([str ], vol .Unique ()),
98- vol .Optional (ATTR_FOLDERS ): vol . All ([ vol . In ( _ALL_FOLDERS )], vol . Unique ()) ,
111+ vol .Optional (ATTR_FOLDERS ): SCHEMA_FOLDERS ,
99112 }
100113)
101114
@@ -105,9 +118,7 @@ def _ensure_list(item: Any) -> list:
105118 vol .Optional (ATTR_FILENAME ): vol .Match (RE_BACKUP_FILENAME ),
106119 vol .Optional (ATTR_PASSWORD ): vol .Maybe (str ),
107120 vol .Optional (ATTR_COMPRESSED ): vol .Maybe (vol .Boolean ()),
108- vol .Optional (ATTR_LOCATION ): vol .All (
109- _ensure_list , [vol .Maybe (str )], vol .Unique ()
110- ),
121+ vol .Optional (ATTR_LOCATION ): SCHEMA_LOCATION_LIST ,
111122 vol .Optional (ATTR_HOMEASSISTANT_EXCLUDE_DATABASE ): vol .Boolean (),
112123 vol .Optional (ATTR_BACKGROUND , default = False ): vol .Boolean (),
113124 vol .Optional (ATTR_EXTRA ): dict ,
@@ -119,30 +130,14 @@ def _ensure_list(item: Any) -> list:
119130 vol .Optional (ATTR_ADDONS ): vol .Or (
120131 ALL_ADDONS_FLAG , vol .All ([str ], vol .Unique ())
121132 ),
122- vol .Optional (ATTR_FOLDERS ): vol . All ([ vol . In ( _ALL_FOLDERS )], vol . Unique ()) ,
133+ vol .Optional (ATTR_FOLDERS ): SCHEMA_FOLDERS ,
123134 vol .Optional (ATTR_HOMEASSISTANT ): vol .Boolean (),
124135 }
125136)
126137
127- SCHEMA_OPTIONS = vol .Schema (
128- {
129- vol .Optional (ATTR_DAYS_UNTIL_STALE ): days_until_stale ,
130- }
131- )
132-
133- SCHEMA_FREEZE = vol .Schema (
134- {
135- vol .Optional (ATTR_TIMEOUT ): vol .All (int , vol .Range (min = 1 )),
136- }
137- )
138-
139- SCHEMA_REMOVE = vol .Schema (
140- {
141- vol .Optional (ATTR_LOCATION ): vol .All (
142- _ensure_list , [vol .Maybe (str )], vol .Unique ()
143- ),
144- }
145- )
138+ SCHEMA_OPTIONS = vol .Schema ({vol .Optional (ATTR_DAYS_UNTIL_STALE ): days_until_stale })
139+ SCHEMA_FREEZE = vol .Schema ({vol .Optional (ATTR_TIMEOUT ): vol .All (int , vol .Range (min = 1 ))})
140+ SCHEMA_REMOVE = vol .Schema ({vol .Optional (ATTR_LOCATION ): SCHEMA_LOCATION_LIST })
146141
147142
148143class APIBackups (CoreSysAttributes ):
@@ -155,6 +150,16 @@ def _extract_slug(self, request):
155150 raise APINotFound ("Backup does not exist" )
156151 return backup
157152
153+ def _make_location_attributes (self , backup : Backup ) -> dict [str , dict [str , Any ]]:
154+ """Make location attributes dictionary."""
155+ return {
156+ loc if loc else LOCATION_LOCAL : {
157+ ATTR_PROTECTED : backup .all_locations [loc ][ATTR_PROTECTED ],
158+ ATTR_SIZE_BYTES : backup .location_size (loc ),
159+ }
160+ for loc in backup .locations
161+ }
162+
158163 def _list_backups (self ):
159164 """Return list of backups."""
160165 return [
@@ -168,11 +173,7 @@ def _list_backups(self):
168173 ATTR_LOCATION : backup .location ,
169174 ATTR_LOCATIONS : backup .locations ,
170175 ATTR_PROTECTED : backup .protected ,
171- ATTR_PROTECTED_LOCATIONS : [
172- loc
173- for loc in backup .locations
174- if backup .all_locations [loc ][ATTR_PROTECTED ]
175- ],
176+ ATTR_LOCATION_ATTRIBUTES : self ._make_location_attributes (backup ),
176177 ATTR_COMPRESSED : backup .compressed ,
177178 ATTR_CONTENT : {
178179 ATTR_HOMEASSISTANT : backup .homeassistant_version is not None ,
@@ -244,11 +245,7 @@ async def backup_info(self, request):
244245 ATTR_SIZE_BYTES : backup .size_bytes ,
245246 ATTR_COMPRESSED : backup .compressed ,
246247 ATTR_PROTECTED : backup .protected ,
247- ATTR_PROTECTED_LOCATIONS : [
248- loc
249- for loc in backup .locations
250- if backup .all_locations [loc ][ATTR_PROTECTED ]
251- ],
248+ ATTR_LOCATION_ATTRIBUTES : self ._make_location_attributes (backup ),
252249 ATTR_SUPERVISOR_VERSION : backup .supervisor_version ,
253250 ATTR_HOMEASSISTANT : backup .homeassistant_version ,
254251 ATTR_LOCATION : backup .location ,
@@ -467,7 +464,9 @@ async def download(self, request: web.Request):
467464 """Download a backup file."""
468465 backup = self ._extract_slug (request )
469466 # Query will give us '' for /backups, convert value to None
470- location = request .query .get (ATTR_LOCATION , backup .location ) or None
467+ location = _convert_local_location (
468+ request .query .get (ATTR_LOCATION , backup .location )
469+ )
471470 self ._validate_cloud_backup_location (request , location )
472471 if location not in backup .all_locations :
473472 raise APIError (f"Backup { backup .slug } is not in location { location } " )
@@ -496,7 +495,9 @@ async def upload(self, request: web.Request):
496495 self ._validate_cloud_backup_location (request , location_names )
497496 # Convert empty string to None if necessary
498497 locations = [
499- self ._location_to_mount (location ) if location else None
498+ self ._location_to_mount (location )
499+ if _convert_local_location (location )
500+ else None
500501 for location in location_names
501502 ]
502503 location = locations .pop (0 )
0 commit comments