Fix undo of methods returning OpChanges directly not being handled correctly

This commit is contained in:
Abdo 2025-10-15 00:45:52 +03:00
parent d0eb61acdb
commit 9b28ed87f2
3 changed files with 34 additions and 20 deletions

View file

@ -999,7 +999,12 @@ def raw_backend_request(endpoint: str) -> Callable[[], bytes]:
def wrapped() -> bytes: def wrapped() -> bytes:
output = getattr(aqt.mw.col._backend, f"{endpoint}_raw")(request.data) output = getattr(aqt.mw.col._backend, f"{endpoint}_raw")(request.data)
if "Has-Op-Changes" in request.headers: 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 = OpChangesOnly()
response.ParseFromString(output) response.ParseFromString(output)

View file

@ -74,15 +74,16 @@ fn write_ts_method(
input_type, input_type,
output_type, output_type,
comments, comments,
has_op_changes, op_changes_type,
}: &MethodDetails, }: &MethodDetails,
out: &mut String, out: &mut String,
) { ) {
let op_changes_type = *op_changes_type as u8;
let comments = format_comments(comments); let comments = format_comments(comments);
writeln!( writeln!(
out, out,
r#"{comments}export async function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}> {{ r#"{comments}export async function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}> {{
return await postProto("{method_name}", new {input_type}(input), {output_type}, options, {has_op_changes}); return await postProto("{method_name}", new {input_type}(input), {output_type}, options, {op_changes_type});
}}"# }}"#
).unwrap() ).unwrap()
} }
@ -94,12 +95,20 @@ fn format_comments(comments: &Option<String>) -> String {
.unwrap_or_default() .unwrap_or_default()
} }
#[derive(Clone, Copy)]
#[repr(u8)]
enum OpChangesType {
None = 0,
OpChanges = 1,
OpChangesOnly = 2,
}
struct MethodDetails { struct MethodDetails {
method_name: String, method_name: String,
input_type: String, input_type: String,
output_type: String, output_type: String,
comments: Option<String>, comments: Option<String>,
has_op_changes: bool, op_changes_type: OpChangesType,
} }
impl MethodDetails { impl MethodDetails {
@ -108,28 +117,32 @@ 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 has_op_changes = has_op_changes(&method.proto.output()); let op_changes_type = get_op_changes_type(&method.proto.output(), true);
Self { Self {
method_name: name, method_name: name,
input_type, input_type,
output_type, output_type,
comments, comments,
has_op_changes, op_changes_type,
} }
} }
} }
fn has_op_changes(message: &MessageDescriptor) -> bool { fn get_op_changes_type(message: &MessageDescriptor, root: bool) -> OpChangesType {
if message.full_name() == "anki.collection.OpChanges" { if message.full_name() == "anki.collection.OpChanges" {
true if root {
OpChangesType::OpChanges
} else {
OpChangesType::OpChangesOnly
}
} 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() {
has_op_changes(field_message) get_op_changes_type(field_message, false)
} else { } else {
false OpChangesType::None
} }
} else { } else {
false OpChangesType::None
} }
} }

View file

@ -11,12 +11,12 @@ export async function postProto<T>(
input: { toBinary(): Uint8Array; getType(): { typeName: string } }, input: { toBinary(): Uint8Array; getType(): { typeName: string } },
outputType: { fromBinary(arr: Uint8Array): T }, outputType: { fromBinary(arr: Uint8Array): T },
options: PostProtoOptions = {}, options: PostProtoOptions = {},
hasOpChanges = false, opChangesType = 0,
): Promise<T> { ): Promise<T> {
try { try {
const inputBytes = input.toBinary(); const inputBytes = input.toBinary();
const path = `/_anki/${method}`; const path = `/_anki/${method}`;
const outputBytes = await postProtoInner(path, inputBytes, hasOpChanges); const outputBytes = await postProtoInner(path, inputBytes, opChangesType);
return outputType.fromBinary(outputBytes); return outputType.fromBinary(outputBytes);
} catch (err) { } catch (err) {
const { alertOnError = true } = options; const { alertOnError = true } = options;
@ -27,14 +27,10 @@ export async function postProto<T>(
} }
} }
async function postProtoInner(url: string, body: Uint8Array, hasOpChanges: boolean): Promise<Uint8Array> { async function postProtoInner(url: string, body: Uint8Array, opChangesType: number): Promise<Uint8Array> {
const headers = { "Content-Type": "application/binary" };
if (hasOpChanges) {
headers["Has-Op-Changes"] = "1";
}
const result = await fetch(url, { const result = await fetch(url, {
method: "POST", method: "POST",
headers, headers: { "Content-Type": "application/binary", "Anki-Op-Changes": opChangesType.toString() },
body, body,
}); });
if (!result.ok) { if (!result.ok) {