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;
}
message NestedOpChanges {
OpChangesOnly changes = 1;
}
message OpChangesWithCount {
OpChanges changes = 1;
uint32 count = 2;

View file

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

View file

@ -30,7 +30,13 @@ import aqt
import aqt.main
import aqt.operations
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.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest
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)
op_changes_type = int(request.headers.get("Anki-Op-Changes", "0"))
if op_changes_type:
response: OpChanges | OpChangesOnly
if op_changes_type == 1:
response = OpChanges()
else:
response = OpChangesOnly()
response.ParseFromString(output)
op_message_types = (OpChanges, OpChangesOnly, NestedOpChanges)
try:
response = op_message_types[op_changes_type - 1]()
response.ParseFromString(output)
changes: Any = response
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:
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)

View file

@ -101,6 +101,7 @@ enum OpChangesType {
None = 0,
OpChanges = 1,
OpChangesOnly = 2,
NestedOpChanges = 3,
}
struct MethodDetails {
@ -117,7 +118,8 @@ impl MethodDetails {
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 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 {
method_name: name,
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 root {
OpChangesType::OpChanges
} else {
OpChangesType::OpChangesOnly
match level {
0 => OpChangesType::None,
1 => OpChangesType::OpChanges,
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) {
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 {
OpChangesType::None
}