refactor: convert f-string logging to lazy %-style format (G004)
Sweep all 1676 G004 violations across 112 files, converting
`logger.<level>(f"…{x}…")` to `logger.<level>("…%s…", x)`.
Why: ruff rule G004 was added to pyproject.toml to enforce lazy
%-style logging — defers formatting until the log level is enabled
and lets structured log tooling match the unformatted template.
Conversion preserves rendered output byte-for-byte:
- `{x}` → `%s` + `x`
- `{x!r}` / `{x!s}` / `{x!a}` → `%r` / `%s` / `%a`
- Format specs (`{x:.2f}`, `{x:>10}`) → `%s` + `format(x, 'spec')`
(printf-style specs aren't 1:1 with Python format specs, so we
delegate to `format()` to keep identical output)
- Literal `%` → `%%`
- Concatenated f-strings (`f"a {x} " "b"`) flattened
- Trailing kwargs (`exc_info=True`) preserved
Verified:
- `uv run ruff check --select G004` → 0 violations
- `uv run ty check -- nextcloud_mcp_server` → passes
- `uv run pytest tests/unit/` → 1010 passed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a4e6125d28
commit
665cb9b1eb
@@ -167,7 +167,7 @@ class NextcloudClient:
|
||||
"""
|
||||
from ..auth import BearerAuth # noqa: PLC0415
|
||||
|
||||
logger.info(f"Creating NC Client for user '{username}' using OAuth token")
|
||||
logger.info("Creating NC Client for user '%s' using OAuth token", username)
|
||||
return cls(
|
||||
base_url=base_url,
|
||||
username=username,
|
||||
|
||||
@@ -43,7 +43,8 @@ def retry_on_429(func):
|
||||
# error we wait a couple of seconds and do a retry
|
||||
if e.response.status_code == codes.TOO_MANY_REQUESTS:
|
||||
logger.warning(
|
||||
f"429 Client Error: Too Many Requests, Number of attempts: {retries}"
|
||||
"429 Client Error: Too Many Requests, Number of attempts: %s",
|
||||
retries,
|
||||
)
|
||||
# Record retry metric (extract app name from args if available)
|
||||
if len(args) > 0 and hasattr(args[0], "app_name"):
|
||||
@@ -53,17 +54,26 @@ def retry_on_429(func):
|
||||
# 404 errors are often expected (e.g., checking if attachments exist)
|
||||
# Log as debug instead of warning
|
||||
logger.debug(
|
||||
f"HTTPStatusError {e.response.status_code}: {e}, Number of attempts: {retries}"
|
||||
"HTTPStatusError %s: %s, Number of attempts: %s",
|
||||
e.response.status_code,
|
||||
e,
|
||||
retries,
|
||||
)
|
||||
raise
|
||||
else:
|
||||
logger.warning(
|
||||
f"HTTPStatusError {e.response.status_code}: {e}, Number of attempts: {retries}"
|
||||
"HTTPStatusError %s: %s, Number of attempts: %s",
|
||||
e.response.status_code,
|
||||
e,
|
||||
retries,
|
||||
)
|
||||
raise
|
||||
except RequestError as e:
|
||||
logger.warning(
|
||||
f"RequestError {e.request.url}: {e}, Number of attempts: {retries}"
|
||||
"RequestError %s: %s, Number of attempts: %s",
|
||||
e.request.url,
|
||||
e,
|
||||
retries,
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -127,7 +137,7 @@ class BaseNextcloudClient(ABC):
|
||||
Response object
|
||||
"""
|
||||
url = self._resolve_url(url)
|
||||
logger.debug(f"Making {method} request to {url}")
|
||||
logger.debug("Making %s request to %s", method, url)
|
||||
|
||||
# Start timer for metrics
|
||||
start_time = time.time()
|
||||
|
||||
@@ -131,23 +131,32 @@ class CalendarClient:
|
||||
max_attempts: Maximum polling attempts (default: 40)
|
||||
initial_delay_ms: Initial delay between attempts in ms (default: 100ms)
|
||||
"""
|
||||
logger.info(f"Waiting for calendar '{calendar_name}' to propagate...")
|
||||
logger.info("Waiting for calendar '%s' to propagate...", calendar_name)
|
||||
delay_ms = initial_delay_ms
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
logger.debug(
|
||||
f"Attempt {attempt + 1}/{max_attempts} to find calendar '{calendar_name}'..."
|
||||
"Attempt %s/%s to find calendar '%s'...",
|
||||
attempt + 1,
|
||||
max_attempts,
|
||||
calendar_name,
|
||||
)
|
||||
calendars = await self.list_calendars()
|
||||
if any(cal["name"] == calendar_name for cal in calendars):
|
||||
logger.info(
|
||||
f"Calendar '{calendar_name}' became available after {attempt + 1} attempts"
|
||||
"Calendar '%s' became available after %s attempts",
|
||||
calendar_name,
|
||||
attempt + 1,
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Attempt {attempt + 1}/{max_attempts} to verify calendar '{calendar_name}' failed: {e}"
|
||||
"Attempt %s/%s to verify calendar '%s' failed: %s",
|
||||
attempt + 1,
|
||||
max_attempts,
|
||||
calendar_name,
|
||||
e,
|
||||
)
|
||||
|
||||
if attempt < max_attempts - 1:
|
||||
@@ -156,7 +165,9 @@ class CalendarClient:
|
||||
delay_ms = min(delay_ms * 2, 2000)
|
||||
|
||||
logger.error(
|
||||
f"Calendar '{calendar_name}' did not become available after {max_attempts} attempts."
|
||||
"Calendar '%s' did not become available after %s attempts.",
|
||||
calendar_name,
|
||||
max_attempts,
|
||||
)
|
||||
|
||||
# ============= Calendar Operations =============
|
||||
@@ -243,7 +254,7 @@ class CalendarClient:
|
||||
}
|
||||
)
|
||||
|
||||
logger.debug(f"Found {len(result)} calendars")
|
||||
logger.debug("Found %s calendars", len(result))
|
||||
return result
|
||||
|
||||
async def create_calendar(
|
||||
@@ -285,7 +296,7 @@ class CalendarClient:
|
||||
f"Failed to create calendar '{calendar_name}': HTTP {response.status}"
|
||||
)
|
||||
|
||||
logger.debug(f"Created calendar: {calendar_name}")
|
||||
logger.debug("Created calendar: %s", calendar_name)
|
||||
|
||||
# Wait for calendar to be queryable (Nextcloud eventual consistency)
|
||||
await self._wait_for_calendar_propagation(calendar_name)
|
||||
@@ -306,7 +317,7 @@ class CalendarClient:
|
||||
)
|
||||
await self._dav_client.delete(calendar_url)
|
||||
|
||||
logger.debug(f"Deleted calendar: {calendar_name}")
|
||||
logger.debug("Deleted calendar: %s", calendar_name)
|
||||
return {"status_code": 204}
|
||||
|
||||
# ============= Event Operations =============
|
||||
@@ -467,7 +478,7 @@ class CalendarClient:
|
||||
# caldav v3's _async_put raises PutError on HTTP failure
|
||||
event = await calendar.save_event(ical=ical_content) # type: ignore[misc] # dual-mode
|
||||
|
||||
logger.debug(f"Created event {event_uid}")
|
||||
logger.debug("Created event %s", event_uid)
|
||||
|
||||
return {
|
||||
"uid": event_uid,
|
||||
@@ -498,7 +509,7 @@ class CalendarClient:
|
||||
|
||||
await _maybe_await(event.save())
|
||||
|
||||
logger.debug(f"Updated event {event_uid}")
|
||||
logger.debug("Updated event %s", event_uid)
|
||||
return {
|
||||
"uid": event_uid,
|
||||
"href": str(event.url),
|
||||
@@ -515,10 +526,10 @@ class CalendarClient:
|
||||
calendar, event_uid, cdav.CompFilter("VEVENT")
|
||||
)
|
||||
await _maybe_await(event.delete())
|
||||
logger.debug(f"Deleted event {event_uid}")
|
||||
logger.debug("Deleted event %s", event_uid)
|
||||
return {"status_code": 204}
|
||||
except caldav_error.NotFoundError as e:
|
||||
logger.debug(f"Event {event_uid} not found: {e}")
|
||||
logger.debug("Event %s not found: %s", event_uid, e)
|
||||
return {"status_code": 404}
|
||||
|
||||
async def get_event(
|
||||
@@ -539,7 +550,7 @@ class CalendarClient:
|
||||
event_data["href"] = str(event.url)
|
||||
event_data["etag"] = ""
|
||||
|
||||
logger.debug(f"Retrieved event {event_uid}")
|
||||
logger.debug("Retrieved event %s", event_uid)
|
||||
return event_data, ""
|
||||
|
||||
async def search_events_across_calendars(
|
||||
@@ -573,14 +584,14 @@ class CalendarClient:
|
||||
all_events.extend(events)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error getting events from calendar {calendar['name']}: {e}"
|
||||
"Error getting events from calendar %s: %s", calendar["name"], e
|
||||
)
|
||||
continue
|
||||
|
||||
return all_events
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching events across calendars: {e}")
|
||||
logger.error("Error searching events across calendars: %s", e)
|
||||
raise
|
||||
|
||||
# ============= Todo/Task Operations (NEW) =============
|
||||
@@ -611,7 +622,7 @@ class CalendarClient:
|
||||
if not filters or self._todo_matches_filters(todo_dict, filters):
|
||||
result.append(todo_dict)
|
||||
|
||||
logger.debug(f"Found {len(result)} todos")
|
||||
logger.debug("Found %s todos", len(result))
|
||||
return result
|
||||
|
||||
async def create_todo(
|
||||
@@ -626,7 +637,7 @@ class CalendarClient:
|
||||
# caldav v3's _async_put raises PutError on HTTP failure
|
||||
todo = await calendar.save_todo(ical=ical_content) # type: ignore[misc] # dual-mode
|
||||
|
||||
logger.debug(f"Created todo {todo_uid}")
|
||||
logger.debug("Created todo %s", todo_uid)
|
||||
|
||||
return {
|
||||
"uid": todo_uid,
|
||||
@@ -653,7 +664,7 @@ class CalendarClient:
|
||||
await _maybe_await(todo.load(only_if_unloaded=True))
|
||||
|
||||
logger.debug(
|
||||
f"Loaded todo {todo_uid}, current data length: {len(todo.data)}" # type: ignore
|
||||
"Loaded todo %s, current data length: %s", todo_uid, len(todo.data)
|
||||
)
|
||||
|
||||
# Merge updates into existing iCal data
|
||||
@@ -662,14 +673,14 @@ class CalendarClient:
|
||||
todo_data,
|
||||
todo_uid,
|
||||
)
|
||||
logger.debug(f"Merged iCal data length: {len(updated_ical)}")
|
||||
logger.debug(f"Updated iCal content:\n{updated_ical}")
|
||||
logger.debug("Merged iCal data length: %s", len(updated_ical))
|
||||
logger.debug("Updated iCal content:\\n%s", updated_ical)
|
||||
|
||||
todo.data = updated_ical
|
||||
|
||||
await _maybe_await(todo.save())
|
||||
|
||||
logger.debug(f"Updated todo {todo_uid}")
|
||||
logger.debug("Updated todo %s", todo_uid)
|
||||
return {
|
||||
"uid": todo_uid,
|
||||
"href": str(todo.url),
|
||||
@@ -677,7 +688,7 @@ class CalendarClient:
|
||||
"status_code": 200,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating todo {todo_uid}: {e}", exc_info=True)
|
||||
logger.error("Error updating todo %s: %s", todo_uid, e, exc_info=True)
|
||||
raise
|
||||
|
||||
async def delete_todo(self, calendar_name: str, todo_uid: str) -> dict[str, Any]:
|
||||
@@ -689,10 +700,10 @@ class CalendarClient:
|
||||
calendar, todo_uid, cdav.CompFilter("VTODO")
|
||||
)
|
||||
await _maybe_await(todo.delete())
|
||||
logger.debug(f"Deleted todo {todo_uid}")
|
||||
logger.debug("Deleted todo %s", todo_uid)
|
||||
return {"status_code": 204}
|
||||
except caldav_error.NotFoundError as e:
|
||||
logger.debug(f"Todo {todo_uid} not found: {e}")
|
||||
logger.debug("Todo %s not found: %s", todo_uid, e)
|
||||
return {"status_code": 404}
|
||||
|
||||
async def search_todos_across_calendars(
|
||||
@@ -717,14 +728,14 @@ class CalendarClient:
|
||||
all_todos.extend(todos)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Error getting todos from calendar {calendar['name']}: {e}"
|
||||
"Error getting todos from calendar %s: %s", calendar["name"], e
|
||||
)
|
||||
continue
|
||||
|
||||
return all_todos
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching todos across calendars: {e}")
|
||||
logger.error("Error searching todos across calendars: %s", e)
|
||||
raise
|
||||
|
||||
# ============= Helper Methods - Event iCalendar =============
|
||||
@@ -935,7 +946,7 @@ class CalendarClient:
|
||||
return self._extract_vevent_data(component)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing iCalendar event: {e}")
|
||||
logger.error("Error parsing iCalendar event: %s", e)
|
||||
return None
|
||||
|
||||
def _merge_ical_properties(
|
||||
@@ -1060,7 +1071,7 @@ class CalendarClient:
|
||||
return cal.to_ical().decode("utf-8")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error merging iCal properties: {e}")
|
||||
logger.error("Error merging iCal properties: %s", e)
|
||||
return self._create_ical_event(event_data, event_uid)
|
||||
|
||||
# ============= Helper Methods - Todo iCalendar =============
|
||||
@@ -1184,7 +1195,7 @@ class CalendarClient:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing iCalendar todo: {e}")
|
||||
logger.error("Error parsing iCalendar todo: %s", e)
|
||||
return None
|
||||
|
||||
def _merge_ical_todo_properties(
|
||||
@@ -1193,7 +1204,7 @@ class CalendarClient:
|
||||
"""Merge new todo data into existing raw iCal while preserving all properties."""
|
||||
try:
|
||||
logger.debug(
|
||||
f"Merging todo properties for {todo_uid}: {list(todo_data.keys())}"
|
||||
"Merging todo properties for %s: %s", todo_uid, list(todo_data.keys())
|
||||
)
|
||||
cal = Calendar.from_ical(raw_ical)
|
||||
|
||||
@@ -1207,13 +1218,13 @@ class CalendarClient:
|
||||
if "status" in todo_data:
|
||||
status_value = todo_data["status"].upper()
|
||||
component["STATUS"] = status_value
|
||||
logger.debug(f"Set STATUS to {status_value}")
|
||||
logger.debug("Set STATUS to %s", status_value)
|
||||
if "priority" in todo_data:
|
||||
component["PRIORITY"] = todo_data["priority"]
|
||||
if "percent_complete" in todo_data:
|
||||
percent_value = todo_data["percent_complete"]
|
||||
component["PERCENT-COMPLETE"] = percent_value
|
||||
logger.debug(f"Set PERCENT-COMPLETE to {percent_value}")
|
||||
logger.debug("Set PERCENT-COMPLETE to %s", percent_value)
|
||||
|
||||
# Handle due date
|
||||
if "due" in todo_data:
|
||||
@@ -1221,7 +1232,7 @@ class CalendarClient:
|
||||
if due_str:
|
||||
due_dt = self._ensure_timezone_aware(due_str)
|
||||
component["DUE"] = vDDDTypes(due_dt)
|
||||
logger.debug(f"Set DUE to {due_dt}")
|
||||
logger.debug("Set DUE to %s", due_dt)
|
||||
|
||||
# Handle start date
|
||||
if "dtstart" in todo_data:
|
||||
@@ -1229,7 +1240,7 @@ class CalendarClient:
|
||||
if dtstart_str:
|
||||
dtstart_dt = self._ensure_timezone_aware(dtstart_str)
|
||||
component["DTSTART"] = vDDDTypes(dtstart_dt)
|
||||
logger.debug(f"Set DTSTART to {dtstart_dt}")
|
||||
logger.debug("Set DTSTART to %s", dtstart_dt)
|
||||
|
||||
# Handle completed date
|
||||
if "completed" in todo_data:
|
||||
@@ -1237,7 +1248,7 @@ class CalendarClient:
|
||||
if completed_str:
|
||||
completed_dt = self._ensure_timezone_aware(completed_str)
|
||||
component["COMPLETED"] = vDDDTypes(completed_dt)
|
||||
logger.debug(f"Set COMPLETED to {completed_dt}")
|
||||
logger.debug("Set COMPLETED to %s", completed_dt)
|
||||
|
||||
# Handle categories
|
||||
if "categories" in todo_data:
|
||||
@@ -1246,7 +1257,7 @@ class CalendarClient:
|
||||
component["CATEGORIES"] = [
|
||||
c.strip() for c in categories_str.split(",")
|
||||
]
|
||||
logger.debug(f"Set CATEGORIES to {categories_str}")
|
||||
logger.debug("Set CATEGORIES to %s", categories_str)
|
||||
|
||||
# Update timestamps
|
||||
now = dt.datetime.now(dt.UTC)
|
||||
@@ -1258,7 +1269,7 @@ class CalendarClient:
|
||||
return cal.to_ical().decode("utf-8")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error merging iCal todo properties: {e}", exc_info=True)
|
||||
logger.error("Error merging iCal todo properties: %s", e, exc_info=True)
|
||||
return self._create_ical_todo(todo_data, todo_uid)
|
||||
|
||||
# ============= Helper Methods - Filtering =============
|
||||
@@ -1290,7 +1301,7 @@ class CalendarClient:
|
||||
return categories_obj.to_ical().decode("utf-8")
|
||||
return str(categories_obj)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error extracting categories: {e}")
|
||||
logger.warning("Error extracting categories: %s", e)
|
||||
return str(categories_obj)
|
||||
|
||||
def _apply_event_filters(
|
||||
@@ -1437,7 +1448,7 @@ class CalendarClient:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in bulk update: {e}")
|
||||
logger.error("Error in bulk update: %s", e)
|
||||
raise
|
||||
|
||||
async def find_availability(
|
||||
|
||||
@@ -89,7 +89,7 @@ class ContactsClient(BaseNextcloudClient):
|
||||
}
|
||||
)
|
||||
|
||||
logger.debug(f"Found {len(addressbooks)} addressbooks")
|
||||
logger.debug("Found %s addressbooks", len(addressbooks))
|
||||
return addressbooks
|
||||
|
||||
async def create_addressbook(self, *, name: str, display_name: str):
|
||||
@@ -166,7 +166,7 @@ class ContactsClient(BaseNextcloudClient):
|
||||
except Exception:
|
||||
# Fall back to creating new vCard if we can't get existing
|
||||
logger.warning(
|
||||
f"Could not fetch existing vCard for {uid}, creating new"
|
||||
"Could not fetch existing vCard for %s, creating new", uid
|
||||
)
|
||||
raw_vcard_content = ""
|
||||
|
||||
@@ -283,7 +283,7 @@ class ContactsClient(BaseNextcloudClient):
|
||||
}
|
||||
)
|
||||
|
||||
logger.debug(f"Found {len(contacts)} contacts")
|
||||
logger.debug("Found %s contacts", len(contacts))
|
||||
return contacts
|
||||
|
||||
async def _get_raw_vcard(self, addressbook: str, uid: str) -> tuple[str, str]:
|
||||
@@ -296,7 +296,7 @@ class ContactsClient(BaseNextcloudClient):
|
||||
etag = response.headers.get("etag", "")
|
||||
return response.text, etag
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting raw vCard for {uid}: {e}")
|
||||
logger.error("Error getting raw vCard for %s: %s", uid, e)
|
||||
raise
|
||||
|
||||
def _merge_vcard_properties(
|
||||
@@ -428,7 +428,7 @@ class ContactsClient(BaseNextcloudClient):
|
||||
return "\n".join(updated_lines)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error merging vCard properties: {e}")
|
||||
logger.error("Error merging vCard properties: %s", e)
|
||||
# Fallback to creating basic vCard matching Nextcloud format
|
||||
basic_vcard = f"""BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
|
||||
@@ -130,7 +130,7 @@ class CookbookClient(BaseNextcloudClient):
|
||||
Returns:
|
||||
Full imported recipe data
|
||||
"""
|
||||
logger.info(f"Importing recipe from URL: {url}")
|
||||
logger.info("Importing recipe from URL: %s", url)
|
||||
response = await self._make_request(
|
||||
"POST",
|
||||
"/apps/cookbook/api/v1/import",
|
||||
|
||||
@@ -67,7 +67,7 @@ class GroupsClient(BaseNextcloudClient):
|
||||
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Created group: {groupid}")
|
||||
logger.info("Created group: %s", groupid)
|
||||
|
||||
@retry_on_429
|
||||
async def delete_group(self, groupid: str) -> None:
|
||||
@@ -85,7 +85,7 @@ class GroupsClient(BaseNextcloudClient):
|
||||
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Deleted group: {groupid}")
|
||||
logger.info("Deleted group: %s", groupid)
|
||||
|
||||
@retry_on_429
|
||||
async def get_group_members(self, groupid: str) -> List[str]:
|
||||
@@ -150,4 +150,4 @@ class GroupsClient(BaseNextcloudClient):
|
||||
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Updated group {groupid} displayname to: {displayname}")
|
||||
logger.info("Updated group %s displayname to: %s", groupid, displayname)
|
||||
|
||||
@@ -93,13 +93,14 @@ class NotesClient(BaseNextcloudClient):
|
||||
for note in response_data:
|
||||
note_id = note.get("id")
|
||||
if note_id is None:
|
||||
logger.warning(f"Skipping note without ID: {note}")
|
||||
logger.warning("Skipping note without ID: %s", note)
|
||||
continue
|
||||
|
||||
# Skip duplicates (API returns all IDs in last chunk for deletion detection)
|
||||
if note_id in seen_ids:
|
||||
logger.debug(
|
||||
f"Skipping duplicate note {note_id} (pruned version in last chunk)"
|
||||
"Skipping duplicate note %s (pruned version in last chunk)",
|
||||
note_id,
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -152,10 +153,10 @@ class NotesClient(BaseNextcloudClient):
|
||||
if category is not None:
|
||||
old_note = await self.get_note(note_id)
|
||||
old_category = old_note.get("category", "")
|
||||
logger.info(f"Current category for note {note_id}: '{old_category}'")
|
||||
logger.info("Current category for note %s: '%s'", note_id, old_category)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Could not fetch current note {note_id} details before update: {e}"
|
||||
"Could not fetch current note %s details before update: %s", note_id, e
|
||||
)
|
||||
old_note = None
|
||||
|
||||
@@ -169,7 +170,7 @@ class NotesClient(BaseNextcloudClient):
|
||||
body["category"] = category
|
||||
|
||||
logger.info(
|
||||
f"Attempting to update note {note_id} with etag {etag}. Body: {body}"
|
||||
"Attempting to update note %s with etag %s. Body: %s", note_id, etag, body
|
||||
)
|
||||
|
||||
response = await self._make_request(
|
||||
@@ -180,7 +181,7 @@ class NotesClient(BaseNextcloudClient):
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Update response for note {note_id}: Status {response.status_code}"
|
||||
"Update response for note %s: Status %s", note_id, response.status_code
|
||||
)
|
||||
updated_note = _expect_note_object(response.json(), operation="update_note")
|
||||
|
||||
@@ -191,7 +192,9 @@ class NotesClient(BaseNextcloudClient):
|
||||
and old_note.get("category", "") != category
|
||||
):
|
||||
logger.info(
|
||||
f"Category changed from '{old_note.get('category', '')}' to '{category}' - cleaning up old attachment directory"
|
||||
"Category changed from '%s' to '%s' - cleaning up old attachment directory",
|
||||
old_note.get("category", ""),
|
||||
category,
|
||||
)
|
||||
try:
|
||||
webdav_client = WebDAVClient(self._client, self.username)
|
||||
@@ -200,7 +203,9 @@ class NotesClient(BaseNextcloudClient):
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error cleaning up old attachment directory for note {note_id}: {e}"
|
||||
"Error cleaning up old attachment directory for note %s: %s",
|
||||
note_id,
|
||||
e,
|
||||
)
|
||||
|
||||
return updated_note
|
||||
@@ -220,20 +225,23 @@ class NotesClient(BaseNextcloudClient):
|
||||
potential_categories.append("") # Empty category
|
||||
|
||||
logger.info(
|
||||
f"Note {note_id} has category: '{category}', will check attachment directories in: {potential_categories}"
|
||||
"Note %s has category: '%s', will check attachment directories in: %s",
|
||||
note_id,
|
||||
category,
|
||||
potential_categories,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Could not fetch note {note_id} details before deletion: {e}"
|
||||
"Could not fetch note %s details before deletion: %s", note_id, e
|
||||
)
|
||||
potential_categories = ["", "Unknown"] # Try common categories
|
||||
|
||||
# Delete the note via API
|
||||
logger.info(f"Deleting note {note_id} via API")
|
||||
logger.info("Deleting note %s via API", note_id)
|
||||
response = await self._make_request(
|
||||
"DELETE", f"/apps/notes/api/v1/notes/{note_id}"
|
||||
)
|
||||
logger.info(f"Note {note_id} deleted successfully via API")
|
||||
logger.info("Note %s deleted successfully via API", note_id)
|
||||
json_response = response.json()
|
||||
|
||||
# Clean up attachment directories
|
||||
@@ -245,16 +253,16 @@ class NotesClient(BaseNextcloudClient):
|
||||
await webdav_client.cleanup_note_attachments(note_id, cat)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Failed to cleanup attachments for category '{cat}': {e}"
|
||||
"Failed to cleanup attachments for category '%s': %s", cat, e
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error during attachment cleanup: {e}")
|
||||
logger.warning("Error during attachment cleanup: %s", e)
|
||||
|
||||
return json_response
|
||||
|
||||
async def append_content(self, note_id: int, content: str) -> Dict[str, Any]:
|
||||
"""Append content to an existing note with a separator."""
|
||||
logger.info(f"Appending content to note {note_id}")
|
||||
logger.info("Appending content to note %s", note_id)
|
||||
|
||||
# Get current note
|
||||
current_note = await self.get_note(note_id)
|
||||
@@ -270,7 +278,9 @@ class NotesClient(BaseNextcloudClient):
|
||||
new_content = content # No separator needed for empty notes
|
||||
|
||||
logger.info(
|
||||
f"Combining existing content ({len(existing_content)} chars) with new content ({len(content)} chars)"
|
||||
"Combining existing content (%s chars) with new content (%s chars)",
|
||||
len(existing_content),
|
||||
len(content),
|
||||
)
|
||||
|
||||
# Update with combined content
|
||||
|
||||
@@ -72,8 +72,12 @@ class SharingClient(BaseNextcloudClient):
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Created share {share_data['id']}: {path} -> {share_with} "
|
||||
f"(type={share_type}, permissions={permissions})"
|
||||
"Created share %s: %s -> %s (type=%s, permissions=%s)",
|
||||
share_data["id"],
|
||||
path,
|
||||
share_with,
|
||||
share_type,
|
||||
permissions,
|
||||
)
|
||||
return share_data
|
||||
|
||||
@@ -99,7 +103,7 @@ class SharingClient(BaseNextcloudClient):
|
||||
f"OCS API error: {data['ocs']['meta'].get('message', 'Unknown error')}"
|
||||
)
|
||||
|
||||
logger.info(f"Deleted share {share_id}")
|
||||
logger.info("Deleted share %s", share_id)
|
||||
|
||||
@retry_on_429
|
||||
async def get_share(self, share_id: int) -> dict[str, Any]:
|
||||
@@ -206,5 +210,5 @@ class SharingClient(BaseNextcloudClient):
|
||||
f"OCS API error: {result['ocs']['meta'].get('message', 'Unknown error')}"
|
||||
)
|
||||
|
||||
logger.info(f"Updated share {share_id}")
|
||||
logger.info("Updated share %s", share_id)
|
||||
return result["ocs"]["data"]
|
||||
|
||||
@@ -28,7 +28,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
path_with_slash = path
|
||||
|
||||
webdav_path = f"{self._get_webdav_base_path()}/{path_with_slash.lstrip('/')}"
|
||||
logger.debug(f"Deleting WebDAV resource: {webdav_path}")
|
||||
logger.debug("Deleting WebDAV resource: %s", webdav_path)
|
||||
|
||||
headers = {"OCS-APIRequest": "true"}
|
||||
try:
|
||||
@@ -39,28 +39,30 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
"PROPFIND", webdav_path, headers=propfind_headers
|
||||
)
|
||||
logger.debug(
|
||||
f"Resource exists check status: {propfind_resp.status_code}"
|
||||
"Resource exists check status: %s", propfind_resp.status_code
|
||||
)
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.debug(f"Resource '{path}' doesn't exist, no deletion needed")
|
||||
logger.debug(
|
||||
"Resource '%s' doesn't exist, no deletion needed", path
|
||||
)
|
||||
return {"status_code": 404}
|
||||
# For other errors, continue with deletion attempt
|
||||
|
||||
# Proceed with deletion
|
||||
response = await self._make_request("DELETE", webdav_path, headers=headers)
|
||||
logger.debug(f"Successfully deleted WebDAV resource '{path}'")
|
||||
logger.debug("Successfully deleted WebDAV resource '%s'", path)
|
||||
return {"status_code": response.status_code}
|
||||
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.debug(f"Resource '{path}' not found, no deletion needed")
|
||||
logger.debug("Resource '%s' not found, no deletion needed", path)
|
||||
return {"status_code": 404}
|
||||
else:
|
||||
logger.error(f"HTTP error deleting WebDAV resource '{path}': {e}")
|
||||
logger.error("HTTP error deleting WebDAV resource '%s': %s", path, e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error deleting WebDAV resource '{path}': {e}")
|
||||
logger.error("Unexpected error deleting WebDAV resource '%s': %s", path, e)
|
||||
raise e
|
||||
|
||||
async def cleanup_old_attachment_directory(
|
||||
@@ -72,13 +74,15 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
f"Notes/{old_category_path_part}.attachments.{note_id}/"
|
||||
)
|
||||
|
||||
logger.debug(f"Cleaning up old attachment directory: {old_attachment_dir_path}")
|
||||
logger.debug(
|
||||
"Cleaning up old attachment directory: %s", old_attachment_dir_path
|
||||
)
|
||||
try:
|
||||
delete_result = await self.delete_resource(path=old_attachment_dir_path)
|
||||
logger.debug(f"Cleanup result: {delete_result}")
|
||||
logger.debug("Cleanup result: %s", delete_result)
|
||||
return delete_result
|
||||
except Exception as e:
|
||||
logger.error(f"Error during cleanup of old attachment directory: {e}")
|
||||
logger.error("Error during cleanup of old attachment directory: %s", e)
|
||||
raise e
|
||||
|
||||
async def cleanup_note_attachments(
|
||||
@@ -89,14 +93,14 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
attachment_dir_path = f"Notes/{cat_path_part}.attachments.{note_id}/"
|
||||
|
||||
logger.debug(
|
||||
f"Cleaning up attachments for note {note_id} in category '{category}'"
|
||||
"Cleaning up attachments for note %s in category '%s'", note_id, category
|
||||
)
|
||||
try:
|
||||
delete_result = await self.delete_resource(path=attachment_dir_path)
|
||||
logger.debug(f"Cleanup result for note {note_id}: {delete_result}")
|
||||
logger.debug("Cleanup result for note %s: %s", note_id, delete_result)
|
||||
return delete_result
|
||||
except Exception as e:
|
||||
logger.error(f"Failed cleaning up attachments for note {note_id}: {e}")
|
||||
logger.error("Failed cleaning up attachments for note %s: %s", note_id, e)
|
||||
raise e
|
||||
|
||||
async def add_note_attachment(
|
||||
@@ -118,7 +122,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
parent_dir_path = f"{webdav_base}/{parent_dir_webdav_rel_path}"
|
||||
attachment_path = f"{parent_dir_path}/{filename}"
|
||||
|
||||
logger.debug(f"Uploading attachment '{filename}' for note {note_id}")
|
||||
logger.debug("Uploading attachment '%s' for note %s", filename, note_id)
|
||||
|
||||
if not mime_type:
|
||||
mime_type, _ = mimetypes.guess_type(filename)
|
||||
@@ -143,7 +147,8 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
)
|
||||
elif notes_dir_response.status_code >= 400:
|
||||
logger.error(
|
||||
f"Error accessing WebDAV Notes directory: {notes_dir_response.status_code}"
|
||||
"Error accessing WebDAV Notes directory: %s",
|
||||
notes_dir_response.status_code,
|
||||
)
|
||||
notes_dir_response.raise_for_status()
|
||||
|
||||
@@ -156,7 +161,8 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
# MKCOL should return 201 Created or 405 Method Not Allowed (if directory already exists)
|
||||
if mkcol_response.status_code not in [201, 405]:
|
||||
logger.error(
|
||||
f"Unexpected status code {mkcol_response.status_code} when creating attachments directory"
|
||||
"Unexpected status code %s when creating attachments directory",
|
||||
mkcol_response.status_code,
|
||||
)
|
||||
mkcol_response.raise_for_status()
|
||||
|
||||
@@ -166,18 +172,24 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.debug(
|
||||
f"Successfully uploaded attachment '{filename}' to note {note_id}"
|
||||
"Successfully uploaded attachment '%s' to note %s", filename, note_id
|
||||
)
|
||||
return {"status_code": response.status_code}
|
||||
|
||||
except HTTPStatusError as e:
|
||||
logger.error(
|
||||
f"HTTP error uploading attachment '{filename}' to note {note_id}: {e}"
|
||||
"HTTP error uploading attachment '%s' to note %s: %s",
|
||||
filename,
|
||||
note_id,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unexpected error uploading attachment '{filename}' to note {note_id}: {e}"
|
||||
"Unexpected error uploading attachment '%s' to note %s: %s",
|
||||
filename,
|
||||
note_id,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
|
||||
@@ -190,7 +202,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
attachment_dir_segment = f".attachments.{note_id}"
|
||||
attachment_path = f"{webdav_base}/Notes/{category_path_part}{attachment_dir_segment}/{filename}"
|
||||
|
||||
logger.debug(f"Fetching attachment '{filename}' for note {note_id}")
|
||||
logger.debug("Fetching attachment '%s' for note %s", filename, note_id)
|
||||
|
||||
try:
|
||||
response = await self._make_request("GET", attachment_path)
|
||||
@@ -200,21 +212,29 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
mime_type = response.headers.get("content-type", "application/octet-stream")
|
||||
|
||||
logger.debug(
|
||||
f"Successfully fetched attachment '{filename}' ({len(content)} bytes)"
|
||||
"Successfully fetched attachment '%s' (%s bytes)",
|
||||
filename,
|
||||
len(content),
|
||||
)
|
||||
return content, mime_type
|
||||
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.debug(f"Attachment '{filename}' not found for note {note_id}")
|
||||
logger.debug("Attachment '%s' not found for note %s", filename, note_id)
|
||||
else:
|
||||
logger.error(
|
||||
f"HTTP error fetching attachment '{filename}' for note {note_id}: {e}"
|
||||
"HTTP error fetching attachment '%s' for note %s: %s",
|
||||
filename,
|
||||
note_id,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unexpected error fetching attachment '{filename}' for note {note_id}: {e}"
|
||||
"Unexpected error fetching attachment '%s' for note %s: %s",
|
||||
filename,
|
||||
note_id,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
|
||||
@@ -224,7 +244,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
if not webdav_path.endswith("/"):
|
||||
webdav_path += "/"
|
||||
|
||||
logger.debug(f"Listing directory: {path}")
|
||||
logger.debug("Listing directory: %s", path)
|
||||
|
||||
propfind_body = """<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:">
|
||||
@@ -308,21 +328,21 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
}
|
||||
)
|
||||
|
||||
logger.debug(f"Found {len(items)} items in directory: {path}")
|
||||
logger.debug("Found %s items in directory: %s", len(items), path)
|
||||
return items
|
||||
|
||||
except HTTPStatusError as e:
|
||||
logger.error(f"HTTP error listing directory '{webdav_path}': {e}")
|
||||
logger.error("HTTP error listing directory '%s': %s", webdav_path, e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error listing directory '{webdav_path}': {e}")
|
||||
logger.error("Unexpected error listing directory '%s': %s", webdav_path, e)
|
||||
raise e
|
||||
|
||||
async def read_file(self, path: str) -> Tuple[bytes, str]:
|
||||
"""Read a file's content via WebDAV GET."""
|
||||
webdav_path = f"{self._get_webdav_base_path()}/{path.lstrip('/')}"
|
||||
|
||||
logger.debug(f"Reading file: {path}")
|
||||
logger.debug("Reading file: %s", path)
|
||||
|
||||
try:
|
||||
response = await self._make_request("GET", webdav_path)
|
||||
@@ -333,14 +353,14 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
"content-type", "application/octet-stream"
|
||||
)
|
||||
|
||||
logger.debug(f"Successfully read file '{path}' ({len(content)} bytes)")
|
||||
logger.debug("Successfully read file '%s' (%s bytes)", path, len(content))
|
||||
return content, content_type
|
||||
|
||||
except HTTPStatusError as e:
|
||||
logger.error(f"HTTP error reading file '{path}': {e}")
|
||||
logger.error("HTTP error reading file '%s': %s", path, e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error reading file '{path}': {e}")
|
||||
logger.error("Unexpected error reading file '%s': %s", path, e)
|
||||
raise e
|
||||
|
||||
async def write_file(
|
||||
@@ -349,7 +369,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
"""Write content to a file via WebDAV PUT."""
|
||||
webdav_path = f"{self._get_webdav_base_path()}/{path.lstrip('/')}"
|
||||
|
||||
logger.debug(f"Writing file: {path}")
|
||||
logger.debug("Writing file: %s", path)
|
||||
|
||||
if not content_type:
|
||||
content_type, _ = mimetypes.guess_type(path)
|
||||
@@ -364,14 +384,14 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
logger.debug(f"Successfully wrote file '{path}'")
|
||||
logger.debug("Successfully wrote file '%s'", path)
|
||||
return {"status_code": response.status_code}
|
||||
|
||||
except HTTPStatusError as e:
|
||||
logger.error(f"HTTP error writing file '{path}': {e}")
|
||||
logger.error("HTTP error writing file '%s': %s", path, e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error writing file '{path}': {e}")
|
||||
logger.error("Unexpected error writing file '%s': %s", path, e)
|
||||
raise e
|
||||
|
||||
async def create_directory(
|
||||
@@ -382,7 +402,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
if not webdav_path.endswith("/"):
|
||||
webdav_path += "/"
|
||||
|
||||
logger.debug(f"Creating directory: {path}")
|
||||
logger.debug("Creating directory: %s", path)
|
||||
|
||||
headers = {"OCS-APIRequest": "true"}
|
||||
|
||||
@@ -390,13 +410,13 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
response = await self._make_request("MKCOL", webdav_path, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
logger.debug(f"Successfully created directory '{path}'")
|
||||
logger.debug("Successfully created directory '%s'", path)
|
||||
return {"status_code": response.status_code}
|
||||
|
||||
except HTTPStatusError as e:
|
||||
# Method Not Allowed - directory already exists
|
||||
if e.response.status_code == 405:
|
||||
logger.debug(f"Directory '{path}' already exists")
|
||||
logger.debug("Directory '%s' already exists", path)
|
||||
return {"status_code": 405, "message": "Directory already exists"}
|
||||
|
||||
# File Conflict - parent directory does not exist
|
||||
@@ -406,20 +426,21 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
if len(path_parts) > 1:
|
||||
parent_dir = "/".join(path_parts[:-1])
|
||||
logger.debug(
|
||||
f"Parent directory '{parent_dir}' doesn't exist, creating recursively"
|
||||
"Parent directory '%s' doesn't exist, creating recursively",
|
||||
parent_dir,
|
||||
)
|
||||
await self.create_directory(parent_dir, recursive)
|
||||
# Now try to create the original directory again
|
||||
return await self.create_directory(path, recursive)
|
||||
else:
|
||||
# This shouldn't happen for single-level directories under root
|
||||
logger.error(f"409 conflict for single-level directory '{path}'")
|
||||
logger.error("409 conflict for single-level directory '%s'", path)
|
||||
raise e
|
||||
|
||||
logger.error(f"HTTP error creating directory '{path}': {e}")
|
||||
logger.error("HTTP error creating directory '%s': %s", path, e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error creating directory '{path}': {e}")
|
||||
logger.error("Unexpected error creating directory '%s': %s", path, e)
|
||||
raise e
|
||||
|
||||
async def move_resource(
|
||||
@@ -446,7 +467,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
elif not source_path.endswith("/") and destination_path.endswith("/"):
|
||||
source_webdav_path += "/"
|
||||
|
||||
logger.debug(f"Moving resource from '{source_path}' to '{destination_path}'")
|
||||
logger.debug("Moving resource from '%s' to '%s'", source_path, destination_path)
|
||||
|
||||
headers = {
|
||||
"OCS-APIRequest": "true",
|
||||
@@ -461,17 +482,20 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
response.raise_for_status()
|
||||
|
||||
logger.debug(
|
||||
f"Successfully moved resource from '{source_path}' to '{destination_path}'"
|
||||
"Successfully moved resource from '%s' to '%s'",
|
||||
source_path,
|
||||
destination_path,
|
||||
)
|
||||
return {"status_code": response.status_code}
|
||||
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.debug(f"Source resource '{source_path}' not found")
|
||||
logger.debug("Source resource '%s' not found", source_path)
|
||||
return {"status_code": 404, "message": "Source resource not found"}
|
||||
elif e.response.status_code == 412:
|
||||
logger.debug(
|
||||
f"Destination '{destination_path}' already exists and overwrite is false"
|
||||
"Destination '%s' already exists and overwrite is false",
|
||||
destination_path,
|
||||
)
|
||||
return {
|
||||
"status_code": 412,
|
||||
@@ -479,7 +503,8 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
}
|
||||
elif e.response.status_code == 409:
|
||||
logger.debug(
|
||||
f"Parent directory of destination '{destination_path}' doesn't exist"
|
||||
"Parent directory of destination '%s' doesn't exist",
|
||||
destination_path,
|
||||
)
|
||||
return {
|
||||
"status_code": 409,
|
||||
@@ -487,12 +512,18 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
}
|
||||
else:
|
||||
logger.error(
|
||||
f"HTTP error moving resource from '{source_path}' to '{destination_path}': {e}"
|
||||
"HTTP error moving resource from '%s' to '%s': %s",
|
||||
source_path,
|
||||
destination_path,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unexpected error moving resource from '{source_path}' to '{destination_path}': {e}"
|
||||
"Unexpected error moving resource from '%s' to '%s': %s",
|
||||
source_path,
|
||||
destination_path,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
|
||||
@@ -520,7 +551,9 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
elif not source_path.endswith("/") and destination_path.endswith("/"):
|
||||
source_webdav_path += "/"
|
||||
|
||||
logger.debug(f"Copying resource from '{source_path}' to '{destination_path}'")
|
||||
logger.debug(
|
||||
"Copying resource from '%s' to '%s'", source_path, destination_path
|
||||
)
|
||||
|
||||
headers = {
|
||||
"OCS-APIRequest": "true",
|
||||
@@ -535,17 +568,20 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
response.raise_for_status()
|
||||
|
||||
logger.debug(
|
||||
f"Successfully copied resource from '{source_path}' to '{destination_path}'"
|
||||
"Successfully copied resource from '%s' to '%s'",
|
||||
source_path,
|
||||
destination_path,
|
||||
)
|
||||
return {"status_code": response.status_code}
|
||||
|
||||
except HTTPStatusError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.debug(f"Source resource '{source_path}' not found")
|
||||
logger.debug("Source resource '%s' not found", source_path)
|
||||
return {"status_code": 404, "message": "Source resource not found"}
|
||||
elif e.response.status_code == 412:
|
||||
logger.debug(
|
||||
f"Destination '{destination_path}' already exists and overwrite is false"
|
||||
"Destination '%s' already exists and overwrite is false",
|
||||
destination_path,
|
||||
)
|
||||
return {
|
||||
"status_code": 412,
|
||||
@@ -553,7 +589,8 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
}
|
||||
elif e.response.status_code == 409:
|
||||
logger.debug(
|
||||
f"Parent directory of destination '{destination_path}' doesn't exist"
|
||||
"Parent directory of destination '%s' doesn't exist",
|
||||
destination_path,
|
||||
)
|
||||
return {
|
||||
"status_code": 409,
|
||||
@@ -561,12 +598,18 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
}
|
||||
else:
|
||||
logger.error(
|
||||
f"HTTP error copying resource from '{source_path}' to '{destination_path}': {e}"
|
||||
"HTTP error copying resource from '%s' to '%s': %s",
|
||||
source_path,
|
||||
destination_path,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unexpected error copying resource from '{source_path}' to '{destination_path}': {e}"
|
||||
"Unexpected error copying resource from '%s' to '%s': %s",
|
||||
source_path,
|
||||
destination_path,
|
||||
e,
|
||||
)
|
||||
raise e
|
||||
|
||||
@@ -615,7 +658,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
|
||||
headers = {"Content-Type": "text/xml", "OCS-APIRequest": "true"}
|
||||
|
||||
logger.debug(f"Searching files in scope: {scope}")
|
||||
logger.debug("Searching files in scope: %s", scope)
|
||||
|
||||
try:
|
||||
response = await self._make_request(
|
||||
@@ -626,14 +669,14 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
# Parse the XML response
|
||||
results = self._parse_search_response(response.content, scope)
|
||||
|
||||
logger.debug(f"Search returned {len(results)} results")
|
||||
logger.debug("Search returned %s results", len(results))
|
||||
return results
|
||||
|
||||
except HTTPStatusError as e:
|
||||
logger.error(f"HTTP error during search: {e}")
|
||||
logger.error("HTTP error during search: %s", e)
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during search: {e}")
|
||||
logger.error("Unexpected error during search: %s", e)
|
||||
raise e
|
||||
|
||||
def _build_search_xml(
|
||||
@@ -1129,7 +1172,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
"file_id": file_id,
|
||||
}
|
||||
|
||||
logger.debug(f"Retrieved file info for ID {file_id}: {name}")
|
||||
logger.debug("Retrieved file info for ID %s: %s", file_id, name)
|
||||
return file_info
|
||||
|
||||
async def get_tag_by_name(self, tag_name: str) -> dict[str, Any] | None:
|
||||
@@ -1449,7 +1492,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
"is_directory": is_directory,
|
||||
}
|
||||
|
||||
logger.debug(f"Got file info for '{path}': id={file_info['id']}")
|
||||
logger.debug("Got file info for '%s': id=%s", path, file_info["id"])
|
||||
return file_info
|
||||
|
||||
async def create_tag(
|
||||
@@ -1500,7 +1543,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
"userAssignable": user_assignable,
|
||||
}
|
||||
|
||||
logger.info(f"Created tag '{name}' with ID {tag_info['id']}")
|
||||
logger.info("Created tag '%s' with ID %s", name, tag_info["id"])
|
||||
return tag_info
|
||||
|
||||
async def get_or_create_tag(
|
||||
@@ -1522,7 +1565,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
# First try to get existing tag
|
||||
existing_tag = await self.get_tag_by_name(name)
|
||||
if existing_tag:
|
||||
logger.debug(f"Tag '{name}' already exists with ID {existing_tag['id']}")
|
||||
logger.debug("Tag '%s' already exists with ID %s", name, existing_tag["id"])
|
||||
return existing_tag
|
||||
|
||||
# Create new tag
|
||||
@@ -1558,7 +1601,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
|
||||
# 201 = Created (new assignment), 409 = Conflict (already assigned)
|
||||
if response.status_code in (201, 409):
|
||||
logger.info(f"Tagged file {file_id} with tag {tag_id}")
|
||||
logger.info("Tagged file %s with tag %s", file_id, tag_id)
|
||||
return True
|
||||
|
||||
response.raise_for_status()
|
||||
@@ -1584,7 +1627,7 @@ class WebDAVClient(BaseNextcloudClient):
|
||||
|
||||
# 204 = No Content (removed), 404 = Not Found (wasn't assigned)
|
||||
if response.status_code in (204, 404):
|
||||
logger.info(f"Removed tag {tag_id} from file {file_id}")
|
||||
logger.info("Removed tag %s from file %s", tag_id, file_id)
|
||||
return True
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
Reference in New Issue
Block a user