From 30e3b512709a1868943dd8d24d4baf0ddac7a6d5 Mon Sep 17 00:00:00 2001 From: Richard Cartwright Date: Wed, 3 Jul 2019 09:47:33 +0100 Subject: [PATCH] feat: cloning, deleting and copy status on clips --- README.md | 15 +++++++++----- src/cxx/clone.cc | 52 ++++++++++++++++++++++++++++++++++++++++-------- src/cxx/clone.h | 6 +++++- src/index.ts | 15 ++++++++------ src/server.ts | 42 +++++++++++++++++++++++++++++++++----- 5 files changed, 105 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f0e8d8d..752fca6 100644 --- a/README.md +++ b/README.md @@ -343,20 +343,21 @@ The `:start` parameter is the first frame in the port's timeline to wipe from an ### Cloning clips -The Quantel systems have a mechanism to clone clips between servers, either within in the same zone or between servers in different zones (_inter-zone cloning_). Only the essence material that is missing from a particular disk pool is copied, meaning that a request to clone can be almost instantaneous where the material has already been duplicated. The Quantel gateway allows clones to be initiated and the subsequent copy progress of that or any other clone to be monitored. +The Quantel systems have a mechanism to clone clips between servers, either within in the same zone or between servers in different zones (_inter-zone cloning_). Only the source essence material that is missing from a particular destination disk pool is copied, meaning that a request to clone can be almost instantaneous where the material has already been duplicated. The Quantel gateway allows clones to be initiated and the subsequent copy progress of that or any other clone to be monitored. -To cause a clone, POST an object containing the source `zoneID` (number, not name), source `clipID` and destination `poolID` to `/:zoneID/copy`, as follows: +To cause a clone, POST an object containing the source `zoneID` (number, omit for a within-zone copy), source `clipID` and destination `poolID` to `/:zoneID/copy`, as follows: ```JSON { "zoneID": 1000, "clipID": 42, "poolID": 12, - "priority": 15 + "priority": 15, + "history": true } ``` -The `priority` is a number between `0` for low and `15` for high that indicates a relative priority for this requested clone wrt other current copy operations. The response is similar, with an additional property `copyID` that is the clip ID of the newly created clip at the destination. +The optional `priority` is a number between `0` for _low_ and `15` for _high_ that provides a relative priority for this requested clone wrt other current copy operations. Relevant for interzone cloning only, the optional `history` flag specifies whether the provenance of the clip should be carried along with the copy. The response is similar to the request, with an additional property `copyID` that is the clip ID of the newly created clip - or the clip ID of an existing copy - at the destination and `copyCreated` if a copy operation was required. To view the status of a single copy operation for destination clip `:copyID`, use path: @@ -382,7 +383,11 @@ Copies can be halted by deleting the destination clip. ### Deleting Clips -TO FOLLOW. +A clip can be deleted by sending a DELETE request to its path: + + /:zoneID/clip/:clipID + +Note that the clip metadata will persist in the database but the essence will be removed, setting the `Frames` field to `0`. ### Controlling the port diff --git a/src/cxx/clone.cc b/src/cxx/clone.cc index ea37ee5..372ae71 100644 --- a/src/cxx/clone.cc +++ b/src/cxx/clone.cc @@ -21,7 +21,7 @@ #include "clone.h" -// TODO leaving sync for now until requirements are clearer +// Deprecated ... cloneInterZone is now a general clone operation void cloneIfNeededExecute(napi_env env, void* data) { cloneIfNeededCarrier* c = (cloneIfNeededCarrier*) data; Quentin::ZonePortal::_ptr_type zp; @@ -141,11 +141,22 @@ napi_value cloneIfNeeded(napi_env env, napi_callback_info info) { void cloneInterZoneExecute(napi_env env, void* data) { cloneInterZoneCarrier* c = (cloneInterZoneCarrier*) data; Quentin::ZonePortal::_ptr_type zp; + CORBA::Boolean copyCreated; try { resolveZonePortalShared(c->isaIOR, &zp); - c->copyID = zp->cloneClipInterZone(c->zoneID, c->clipID, c->poolID, c->priority); + if (c->zoneID < 0) { // Local zone clone + // Note: Clip does not expire - that's what -1 means! + c->copyID = zp->cloneIfNeeded(c->clipID, c->poolID, 0, c->priority, -1, copyCreated); + c->copyCreated = copyCreated; + } else { + if (c->history) { + c->copyID = zp->cloneClipInterZone(c->zoneID, c->clipID, c->poolID, c->priority); + } else { + c->copyID = zp->cloneClipInterZoneWithoutHistory(c->zoneID, c->clipID, c->poolID, c->priority); + } + } } catch(CORBA::SystemException& ex) { NAPI_REJECT_SYSTEM_EXCEPTION(ex); @@ -171,15 +182,17 @@ void cloneInterZoneComplete(napi_env env, napi_status asyncStatus, void* data) { c->status = napi_create_object(env, &result); REJECT_STATUS; - c->status = napi_create_string_utf8(env, "CloneInterZoneResult", NAPI_AUTO_LENGTH, &prop); + c->status = napi_create_string_utf8(env, "CloneResult", NAPI_AUTO_LENGTH, &prop); REJECT_STATUS; c->status = napi_set_named_property(env, result, "type", prop); REJECT_STATUS; - c->status = napi_create_int32(env, c->zoneID , &prop); - REJECT_STATUS; - c->status = napi_set_named_property(env, result, "zoneID", prop); - REJECT_STATUS; + if (c->zoneID >= 0) { + c->status = napi_create_int32(env, c->zoneID , &prop); + REJECT_STATUS; + c->status = napi_set_named_property(env, result, "zoneID", prop); + REJECT_STATUS; + } c->status = napi_create_int32(env, c->clipID , &prop); REJECT_STATUS; @@ -196,6 +209,16 @@ void cloneInterZoneComplete(napi_env env, napi_status asyncStatus, void* data) { c->status = napi_set_named_property(env, result, "priority", prop); REJECT_STATUS; + c->status = napi_get_boolean(env, c->history, &prop); + REJECT_STATUS; + c->status = napi_set_named_property(env, result, "history", prop); + REJECT_STATUS; + + c->status = napi_get_boolean(env, c->copyCreated, &prop); + REJECT_STATUS; + c->status = napi_set_named_property(env, result, "copyCreated", prop); + REJECT_STATUS; + c->status = napi_create_int32(env, c->copyID, &prop); REJECT_STATUS; c->status = napi_set_named_property(env, result, "copyID", prop); @@ -252,8 +275,12 @@ napi_value cloneInterZone(napi_env env, napi_callback_info info) { options = argv[1]; c->status = napi_get_named_property(env, options, "zoneID", &prop); REJECT_RETURN; - c->status = napi_get_value_int32(env, prop, (int32_t*) &c->zoneID); + c->status = napi_typeof(env, prop, &type); REJECT_RETURN; + if (type == napi_number) { + c->status = napi_get_value_int32(env, prop, (int32_t*) &c->zoneID); + REJECT_RETURN; + } c->status = napi_get_named_property(env, options, "clipID", &prop); REJECT_RETURN; @@ -274,6 +301,15 @@ napi_value cloneInterZone(napi_env env, napi_callback_info info) { REJECT_RETURN; } + c->status = napi_get_named_property(env, options, "history", &prop); + REJECT_RETURN; + c->status = napi_typeof(env, prop, &type); + REJECT_RETURN; + if (type == napi_boolean) { + c->status = napi_get_value_bool(env, prop, &c->history); + REJECT_RETURN; + } + c->status = napi_create_string_utf8(env, "CloneInterZone", NAPI_AUTO_LENGTH, &resourceName); REJECT_RETURN; c->status = napi_create_async_work(env, nullptr, resourceName, cloneInterZoneExecute, diff --git a/src/cxx/clone.h b/src/cxx/clone.h index 3717e73..509338c 100644 --- a/src/cxx/clone.h +++ b/src/cxx/clone.h @@ -25,7 +25,9 @@ #include "qgw_util.h" #include +// Deprecated ... cloneInterZone is now a general clone operation napi_value cloneIfNeeded(napi_env env, napi_callback_info info); + napi_value cloneInterZone(napi_env env, napi_callback_info info); napi_value getCopyRemaining(napi_env env, napi_callback_info info); napi_value getCopiesRemaining(napi_env env, napi_callback_info info); @@ -45,11 +47,13 @@ void cloneInterZoneExecute(napi_env env, void* data); void cloneInterZoneComplete(napi_env env, napi_status asyncStatus, void* data); struct cloneInterZoneCarrier : carrier { - int32_t zoneID; // ZoneID of the remote zone where the source clip resides + int32_t zoneID = -1; // ZoneID of the remote zone where the source clip resides, negative for same zone int32_t clipID; // The clipID of the clip in the remote zone to be cloned int32_t poolID; // PoolID in the local destination zone where the clip it copied to int32_t priority = Quentin::Port::StandardPriority; // The priority of the transfer int32_t copyID; // Newly allocated clipID on the local destination zone + bool history = true; + bool copyCreated = true; ~cloneInterZoneCarrier() { } }; diff --git a/src/index.ts b/src/index.ts index 6fc7467..f045daa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -357,16 +357,18 @@ export namespace Quantel { compressionName: string } - export interface CloneInterZoneInfo { - zoneID: number // Source zone ID + export interface CloneInfo { + zoneID?: number // Source zone ID, omit for local zone clipID: number // Source clip ID poolID: number // Destination pool ID - priority?: number // Prioerity, between 0 (low) and 15 (high) - default is 8 (standard) + priority?: number // Priority, between 0 (low) and 15 (high) - default is 8 (standard) + history?: boolean // Should an interzone clone link to historical provinance - default is true } - export interface CloneInterZoneResult extends CloneInterZoneInfo { - type: 'CloneInterZoneResult' + export interface CloneResult extends CloneInfo { + type: 'CloneResult' copyID: number + copyCreated: boolean } export interface CopyProgress extends ClipRef { @@ -788,6 +790,7 @@ export namespace Quantel { Plan is to hide this behind the REST API */ + // Deprecated ... cloneInterZone is now a general clone operation export async function cloneIfNeeded (options: CloneRequest): Promise { try { await getISAReference() @@ -802,7 +805,7 @@ export namespace Quantel { } } - export async function cloneInterZone (options: CloneInterZoneInfo): Promise { + export async function cloneInterZone (options: CloneInfo): Promise { try { await getISAReference() return await quantel.cloneInterZone(await isaIOR, options) diff --git a/src/server.ts b/src/server.ts index 735133f..8a9cb09 100644 --- a/src/server.ts +++ b/src/server.ts @@ -107,7 +107,7 @@ router.get('/:zoneID/', async (ctx) => { let zones = await Quantel.listZones() let inTheZone = zones.find(z => z.zoneName === ctx.params.zoneID || z.zoneNumber.toString() === ctx.params.zoneID) if (inTheZone) { - ctx.body = [ 'server/', 'clip/', 'format/' ] + ctx.body = [ 'server/', 'clip/', 'format/', 'copy/' ] } else { ctx.status = 404 ctx.body = { @@ -182,15 +182,15 @@ router.get('/default/copy/:copyID', async (ctx) => { }) router.post('/default/copy', async (ctx) => { - let clone: Quantel.CloneInterZoneInfo = {} as Quantel.CloneInterZoneInfo + let clone: Quantel.CloneInfo = {} as Quantel.CloneInfo try { if (ctx.body && ctx.status === 400) return - clone = ctx.request.body as Quantel.CloneInterZoneInfo - if (isNaN(+clone.zoneID) || +clone.zoneID < 0) { + clone = ctx.request.body as Quantel.CloneInfo + if (clone.zoneID && (isNaN(+clone.zoneID) || +clone.zoneID < 0)) { ctx.status = 400 ctx.body = { status: 400, - message: 'Bad request. A clone request must use a positive integer for zone ID.', + message: 'Bad request. Where present, aclone request must use a positive integer for zone ID.', stack: '' } return @@ -223,6 +223,9 @@ router.post('/default/copy', async (ctx) => { } return } + if (clone.history) { + clone.history = true + } ctx.body = await Quantel.cloneInterZone(clone) } catch (err) { if (err.message.indexOf('BadIdent') >= 0) { @@ -385,6 +388,35 @@ router.get('/default/clip/:clipID', async (ctx) => { } }) +router.delete('/default/clip/:clipID', async (ctx) => { + if (isNaN(+ctx.params.clipID) || +ctx.params.clipID < 0) { + ctx.status = 400 + ctx.body = { + status: 400, + message: 'Bad request. Clip ID must be a positive number.', + stack: '' + } as JSONError + return + } + try { + ctx.body = { + deleted: await Quantel.deleteClip({ clipID: +ctx.params.clipID }) + } + } catch (err) { + if (err.message.indexOf('BadIdent') >= 0) { + ctx.status = 404 + ctx.body = { + status: 404, + message: `Not found. A clip with identifier '${ctx.params.clipID}' was not found.`, + stack: '' + } + } else { + throw err + } + } + +}) + router.get('/default/clip/:clipID/fragments', async (ctx) => { if (isNaN(+ctx.params.clipID) || +ctx.params.clipID < 0) { ctx.status = 400