Handle deeply nested OpChanges

Messages such as AddNotesResponse were not handled correctly.
This commit is contained in:
Abdo 2025-10-18 02:45:27 +03:00
parent 74c727e82c
commit 7dc2d29136
4 changed files with 41 additions and 15 deletions

View file

@ -78,6 +78,10 @@ message OpChangesOnly {
collection.OpChanges changes = 1; collection.OpChanges changes = 1;
} }
message NestedOpChanges {
OpChangesOnly changes = 1;
}
message OpChangesWithCount { message OpChangesWithCount {
OpChanges changes = 1; OpChanges changes = 1;
uint32 count = 2; uint32 count = 2;

View file

@ -35,6 +35,7 @@ Preferences = config_pb2.Preferences
UndoStatus = collection_pb2.UndoStatus UndoStatus = collection_pb2.UndoStatus
OpChanges = collection_pb2.OpChanges OpChanges = collection_pb2.OpChanges
OpChangesOnly = collection_pb2.OpChangesOnly OpChangesOnly = collection_pb2.OpChangesOnly
NestedOpChanges = collection_pb2.NestedOpChanges
OpChangesWithCount = collection_pb2.OpChangesWithCount OpChangesWithCount = collection_pb2.OpChangesWithCount
OpChangesWithId = collection_pb2.OpChangesWithId OpChangesWithId = collection_pb2.OpChangesWithId
OpChangesAfterUndo = collection_pb2.OpChangesAfterUndo OpChangesAfterUndo = collection_pb2.OpChangesAfterUndo

View file

@ -30,7 +30,13 @@ import aqt
import aqt.main import aqt.main
import aqt.operations import aqt.operations
from anki import frontend_pb2, generic_pb2, hooks from anki import frontend_pb2, generic_pb2, hooks
from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.collection import (
NestedOpChanges,
OpChanges,
OpChangesOnly,
Progress,
SearchNode,
)
from anki.decks import UpdateDeckConfigs from anki.decks import UpdateDeckConfigs
from anki.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest from anki.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest
from anki.utils import dev_mode, from_json_bytes, to_json_bytes from anki.utils import dev_mode, from_json_bytes, to_json_bytes
@ -1001,16 +1007,19 @@ def raw_backend_request(endpoint: str) -> Callable[[], bytes]:
output = getattr(aqt.mw.col._backend, f"{endpoint}_raw")(request.data) output = getattr(aqt.mw.col._backend, f"{endpoint}_raw")(request.data)
op_changes_type = int(request.headers.get("Anki-Op-Changes", "0")) op_changes_type = int(request.headers.get("Anki-Op-Changes", "0"))
if op_changes_type: if op_changes_type:
response: OpChanges | OpChangesOnly op_message_types = (OpChanges, OpChangesOnly, NestedOpChanges)
if op_changes_type == 1: try:
response = OpChanges() response = op_message_types[op_changes_type - 1]()
else: response.ParseFromString(output)
response = OpChangesOnly() changes: Any = response
response.ParseFromString(output) for _ in range(op_changes_type - 1):
changes = changes.changes
except IndexError:
raise ValueError(f"unhandled op changes level: {op_changes_type}")
def handle_on_main() -> None: def handle_on_main() -> None:
handler = aqt.mw.app.activeWindow() handler = aqt.mw.app.activeWindow()
on_op_finished(aqt.mw, response, handler) on_op_finished(aqt.mw, changes, handler)
aqt.mw.taskman.run_on_main(handle_on_main) aqt.mw.taskman.run_on_main(handle_on_main)

View file

@ -101,6 +101,7 @@ enum OpChangesType {
None = 0, None = 0,
OpChanges = 1, OpChanges = 1,
OpChangesOnly = 2, OpChangesOnly = 2,
NestedOpChanges = 3,
} }
struct MethodDetails { struct MethodDetails {
@ -117,7 +118,8 @@ impl MethodDetails {
let input_type = full_name_to_imported_reference(method.proto.input().full_name()); let input_type = full_name_to_imported_reference(method.proto.input().full_name());
let output_type = full_name_to_imported_reference(method.proto.output().full_name()); let output_type = full_name_to_imported_reference(method.proto.output().full_name());
let comments = method.comments.clone(); let comments = method.comments.clone();
let op_changes_type = get_op_changes_type(&method.proto.output(), true); let op_changes_type =
get_op_changes_type(&method.proto.output(), &method.proto.output(), 1);
Self { Self {
method_name: name, method_name: name,
input_type, input_type,
@ -128,16 +130,26 @@ impl MethodDetails {
} }
} }
fn get_op_changes_type(message: &MessageDescriptor, root: bool) -> OpChangesType { fn get_op_changes_type(
root_message: &MessageDescriptor,
message: &MessageDescriptor,
level: u8,
) -> OpChangesType {
if message.full_name() == "anki.collection.OpChanges" { if message.full_name() == "anki.collection.OpChanges" {
if root { match level {
OpChangesType::OpChanges 0 => OpChangesType::None,
} else { 1 => OpChangesType::OpChanges,
OpChangesType::OpChangesOnly 2 => OpChangesType::OpChangesOnly,
3 => OpChangesType::NestedOpChanges,
_ => panic!(
"unhandled op changes level for message {}: {}",
root_message.full_name(),
level
),
} }
} else if let Some(field) = message.get_field(1) { } else if let Some(field) = message.get_field(1) {
if let Some(field_message) = field.kind().as_message() { if let Some(field_message) = field.kind().as_message() {
get_op_changes_type(field_message, false) get_op_changes_type(root_message, field_message, level + 1)
} else { } else {
OpChangesType::None OpChangesType::None
} }