Skip to content

Commit 26f0c35

Browse files
authored
fix: to_archive and task threads (#38)
* feat: extendable builder Adobe SDK extends the builder for compliance signing. * fix: to_archive and task threads fixes to_archive work with buffers as well as files. Settings work on Neon/Node tasks * chore: add changeset * fix: Fix return type of resourceToAsset * Update trustmark version
1 parent 058903f commit 26f0c35

File tree

13 files changed

+178
-30
lines changed

13 files changed

+178
-30
lines changed

.changeset/nice-dingos-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@contentauth/c2pa-node": patch
3+
---
4+
5+
Fix settings on tasks. Fix Builder toArchive

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ toml = "0.8"
3434
thiserror = "1.0.61"
3535
tokio = { version = "1.43.0", features = ["rt-multi-thread"] }
3636
tokio-util = "0.7.13"
37-
trustmark = "0.2"
37+
trustmark = "0.2.2"
3838
rand = "0.8.5"

js-src/Builder.spec.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { isActionsAssertion } from "./assertions.js";
2929
import { CallbackSigner, LocalSigner } from "./Signer.js";
3030
import { Reader } from "./Reader.js";
3131
import { Builder } from "./Builder.js";
32+
import { loadC2paSettings, resetSettings } from "./Settings.js";
3233

3334
const tempDir = path.join(__dirname, "tmp");
3435

@@ -277,6 +278,81 @@ describe("Builder", () => {
277278
expect(activeManifest?.title).toBe("Test_Manifest");
278279
});
279280

281+
it("should populate buffer when archiving to buffer", async () => {
282+
const archive: DestinationBufferAsset = {
283+
buffer: null,
284+
};
285+
await builder.toArchive(archive);
286+
expect(archive.buffer).not.toBeNull();
287+
expect(archive.buffer!.length).toBeGreaterThan(0);
288+
});
289+
290+
it("should write archive to file", async () => {
291+
const archivePath = path.join(tempDir, "archive_file_test.zip");
292+
const archive = { path: archivePath };
293+
await builder.toArchive(archive);
294+
295+
// Verify file was created and has content
296+
expect(await fs.pathExists(archivePath)).toBe(true);
297+
const stats = await fs.stat(archivePath);
298+
expect(stats.size).toBeGreaterThan(0);
299+
});
300+
301+
it("should construct reader directly from builder archive buffer", async () => {
302+
// Enable c2pa archive format
303+
loadC2paSettings(
304+
JSON.stringify({
305+
builder: {
306+
generate_c2pa_archive: true,
307+
},
308+
verify: {
309+
verify_after_reading: false,
310+
},
311+
}),
312+
);
313+
314+
try {
315+
// Create a builder
316+
const simpleManifestDefinition = {
317+
claim_generator_info: [
318+
{
319+
name: "c2pa_test",
320+
version: "1.0.0",
321+
},
322+
],
323+
title: "Test_Manifest",
324+
format: "image/jpeg",
325+
assertions: [],
326+
resources: { resources: {} },
327+
};
328+
329+
const testBuilder = Builder.withJson(simpleManifestDefinition);
330+
331+
// Add an ingredient
332+
await testBuilder.addIngredient(parent_json, source);
333+
334+
// Create an archive from the builder written to a buffer
335+
const archive: DestinationBufferAsset = {
336+
buffer: null,
337+
};
338+
await testBuilder.toArchive(archive);
339+
340+
// Verify buffer was populated
341+
expect(archive.buffer).not.toBeNull();
342+
expect(archive.buffer!.length).toBeGreaterThan(0);
343+
344+
// Construct a reader from the builder archive with mime-type "application/c2pa"
345+
const reader = await Reader.fromAsset({
346+
buffer: archive.buffer! as Buffer,
347+
mimeType: "application/c2pa",
348+
});
349+
expect(reader).not.toBeNull();
350+
} finally {
351+
// Reset settings to defaults
352+
resetSettings();
353+
}
354+
});
355+
280356
it("should sign data with callback to file", async () => {
281357
const dest: FileAsset = {
282358
path: path.join(tempDir, "callback_signed.jpg"),

js-src/Builder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import type {
3535
import { IdentityAssertionSigner } from "./IdentityAssertion.js";
3636

3737
export class Builder implements BuilderInterface {
38-
private constructor(private builder: NeonBuilderHandle) {}
38+
constructor(private builder: NeonBuilderHandle) {}
3939

4040
static new(): Builder {
4141
const builder: NeonBuilderHandle = getNeonBinary().builderNew();
@@ -235,4 +235,8 @@ export class Builder implements BuilderInterface {
235235
value,
236236
);
237237
}
238+
239+
getHandle(): NeonBuilderHandle {
240+
return this.builder;
241+
}
238242
}

js-src/Reader.spec.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ describe("Reader", () => {
172172
});
173173

174174
it("should write to a file", async () => {
175-
let bytesWritten = 0;
176175
const outputPath = path.join(tempDir, "thumbnail.jpg");
177176
const reader = await Reader.fromAsset({
178177
path: "./tests/fixtures/CA.jpg",
@@ -181,12 +180,12 @@ describe("Reader", () => {
181180
const activeManifest = reader!.getActive();
182181
const uri = activeManifest?.thumbnail?.identifier;
183182

184-
if (uri !== undefined) {
185-
bytesWritten = await reader!.resourceToAsset(uri, {
186-
path: outputPath,
187-
});
188-
}
189-
expect(bytesWritten).toBe(49690);
183+
expect(uri).toBeDefined();
184+
const bytesWritten = await reader!.resourceToAsset(uri!, {
185+
path: outputPath,
186+
});
187+
188+
expect(bytesWritten.bytes_written).toBe(49690);
190189
expect(fs.existsSync(outputPath));
191190
});
192191

js-src/Reader.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { getNeonBinary } from "./binary.js";
1717
import type {
1818
DestinationAsset,
1919
ReaderInterface,
20+
ResourceAsset,
2021
SourceAsset,
2122
NeonReaderHandle,
2223
} from "./types.d.ts";
@@ -36,7 +37,7 @@ export class Reader implements ReaderInterface {
3637
return getNeonBinary().readerIsEmbedded.call(this.reader);
3738
}
3839

39-
async resourceToAsset(uri: string, asset: DestinationAsset): Promise<number> {
40+
async resourceToAsset(uri: string, asset: DestinationAsset): Promise<ResourceAsset> {
4041
return getNeonBinary().readerResourceToAsset.call(this.reader, uri, asset);
4142
}
4243

js-src/types.d.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ export type SourceAsset = SourceBufferAsset | FileAsset;
102102
*/
103103
export type DestinationAsset = DestinationBufferAsset | FileAsset;
104104

105+
/**
106+
* The return type of resourceToAsset.
107+
* When the asset is a file, returns the number of bytes written.
108+
* When the asset is a buffer, returns an object with the buffer and bytes written.
109+
*/
110+
export type ResourceAsset = { buffer: Buffer; bytes_written: number };
111+
105112
/**
106113
* A signer that uses a local certificate and private key to sign data
107114
*/
@@ -315,6 +322,11 @@ export interface BuilderInterface {
315322
* @returns The manifest definition
316323
*/
317324
updateManifestProperty(property: string, value: string | ClaimVersion): void;
325+
326+
/**
327+
* Get the internal handle for use with Neon bindings
328+
*/
329+
getHandle(): NeonBuilderHandle;
318330
}
319331

320332
export interface ReaderInterface {
@@ -336,9 +348,14 @@ export interface ReaderInterface {
336348
/**
337349
* Write a resource to a buffer or file
338350
* @param uri The URI of the resource
339-
* @param filePath The path to the file
351+
* @param output The destination asset (file or buffer)
352+
* @returns When output is a file, returns the number of bytes written.
353+
* When output is a buffer, returns an object with the buffer and bytes written.
340354
*/
341-
resourceToAsset(uri: string, output: DestinationAsset): Promise<number>;
355+
resourceToAsset(
356+
uri: string,
357+
output: DestinationAsset,
358+
): Promise<ResourceAsset>;
342359

343360
/**
344361
* Get the internal handle for use with Neon bindings

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub enum Error {
5151
#[error("Settings handling failed: {0}")]
5252
Settings(String),
5353

54+
#[error("Reading failed: {0}")]
55+
Reading(String),
56+
5457
#[error("Signing failed: {0}")]
5558
Signing(String),
5659

src/neon_builder.rs

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,25 +235,47 @@ impl NeonBuilder {
235235

236236
pub fn to_archive(mut cx: FunctionContext) -> JsResult<JsPromise> {
237237
let this = cx.this::<JsBox<Self>>()?;
238-
let dest = cx
239-
.argument::<JsObject>(0)
240-
.and_then(|obj| parse_asset(&mut cx, obj))?;
238+
let dest_obj = cx.argument::<JsObject>(0)?;
239+
let dest = parse_asset(&mut cx, dest_obj)?;
240+
let is_buffer = dest.name() == "destination_buffer";
241241
let builder = Arc::clone(&this.builder);
242+
let dest_obj_root: Arc<Root<JsObject>> = Arc::new(Root::new(&mut cx, &dest_obj));
242243

243244
let promise = cx
244245
.task(move || {
245246
// Block on acquiring the async mutex lock
247+
// Settings are automatically applied when runtime() is called
246248
let rt = runtime();
247249
let mut builder = rt.block_on(async { builder.lock().await });
248250

249-
dest.write_stream().and_then(|dest_stream| {
250-
builder.to_archive(dest_stream)?;
251-
Ok(())
251+
dest.write_stream().and_then(|mut dest_stream| {
252+
builder.to_archive(&mut dest_stream)?;
253+
if is_buffer {
254+
let mut archive_data = Vec::new();
255+
dest_stream
256+
.rewind()
257+
.map_err(|e| Error::Asset(format!("Failed to rewind stream: {e}")))?;
258+
dest_stream
259+
.read_to_end(&mut archive_data)
260+
.map_err(|e| Error::Asset(format!("Failed to read stream: {e}")))?;
261+
Ok(Some(archive_data))
262+
} else {
263+
Ok(None)
264+
}
252265
})
253266
})
254-
.promise(move |mut cx, result: Result<(), Error>| match result {
255-
Ok(_) => Ok(cx.undefined()),
256-
Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)),
267+
.promise(move |mut cx, result: Result<Option<Vec<u8>>, Error>| {
268+
match result {
269+
Ok(Some(archive_data)) => {
270+
// If the output is a buffer, populate it with the archive data
271+
let buffer = JsBuffer::from_slice(&mut cx, &archive_data)?;
272+
let dest_obj = dest_obj_root.to_inner(&mut cx);
273+
dest_obj.set(&mut cx, "buffer", buffer)?;
274+
Ok(cx.undefined())
275+
}
276+
Ok(None) => Ok(cx.undefined()),
277+
Err(err) => as_js_error(&mut cx, err).and_then(|err| cx.throw(err)),
278+
}
257279
});
258280
Ok(promise)
259281
}

0 commit comments

Comments
 (0)