Foundation: Add CPProgress and cancellable item provider loading

Partial implementation, gaps and impossibilities noted internally as TODO comments.

* CPProgress.j: Introduce CPProgress, modelled on NSProgress.
  - Count units of work, with fraction completed, finished and indeterminate states.
  - Compose progress trees through explicit children and the current progress object.
  - Cancel, pause and resume, with handlers, propagating to children.
  - Announce unit-count changes through key-value observing across the tree.

* CPError.j: Add CPUserCancelledError.

* CPItemProvider.j:
  - Return a CPProgress from each load, counting bytes read for files and aborting the read on cancellation.
  - Pass file contents to CPData as a binary string rather than a byte array.
  - Implement -canLoadObjectOfClass: and -loadObjectOfClass:completionHandler:.
  - Add the CPString (CPItemProvider) reading category.

* CPString+Encoding.j: Decode data held as a binary string through a Uint8Array, without building a byte array.

* Foundation.j: Import CPProgress.j.

* CPProgressTest.j: Add unit tests covering counts, children, the current progress object, cancellation, pausing, observation and user information.

* CPItemProviderTest.j: Add unit tests covering read progress, cancellation, completion and string loading.
This commit is contained in:
David Richardson committed 2026-11-02 15:29:21 -06:00
1 parent 40c5c466c1
commit 021e9935aa
7 files changed
+1275 -34

No files matched your search

+42 -5
View File
@@ -117,7 +117,7 @@ var CPStringEncodingDecodeWithTextDecoder = function(aLabel, bytes, isLossy)
try
{
return new TextDecoder(aLabel, { fatal: !isLossy }).decode(new Uint8Array(bytes));
return new TextDecoder(aLabel, { fatal: !isLossy }).decode(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes));
}
catch (anException)
{
@@ -142,7 +142,7 @@ var CPStringEncodingDecodeASCII = function(bytes, isLossy)
if (!isLossy)
return nil;
bytes = bytes.map(function(aByte) { return aByte > 0x7F ? CPStringEncodingReplacementCharacter : aByte; });
bytes = Array.prototype.map.call(bytes, function(aByte) { return aByte > 0x7F ? CPStringEncodingReplacementCharacter : aByte; });
break;
}
@@ -705,6 +705,38 @@ var CPStringEncodingIsAvailable = function(aDescriptor)
}
};
/*
Returns the bytes of data as an array of bytes. Data that holds a binary string, such as
file contents from CPItemProvider, is converted to a Uint8Array, which takes one byte for
each byte. Asking CPData for its bytes would build a plain array of JavaScript numbers, many
times the size, because CFData stores bytes that way, and keep it in the data.
*/
var CPStringEncodingBytesOfData = function(someData)
{
if (!someData)
return [];
if (someData._bytes)
return someData._bytes;
var string = someData.rawString(),
count = string.length,
bytes = new Uint8Array(count);
for (var i = 0; i < count; i++)
bytes[i] = string.charCodeAt(i) & 0xFF;
return bytes;
};
/*
Returns the bytes from anIndex to the end, without copying when the bytes are a Uint8Array.
*/
var CPStringEncodingBytesFromIndex = function(bytes, anIndex)
{
return bytes.subarray ? bytes.subarray(anIndex) : bytes.slice(anIndex);
};
var CPStringEncodingDecode = function(bytes, anEncoding, isLossy)
{
var descriptor = CPStringEncodingDescriptors[anEncoding];
@@ -807,7 +839,12 @@ var CPStringEncodingDecode = function(bytes, anEncoding, isLossy)
*/
- (id)initWithData:(CPData)someData encoding:(CPStringEncoding)anEncoding
{
return [self initWithBytes:someData ? [someData bytes] : [] length:someData ? [someData length] : 0 encoding:anEncoding];
var decoded = CPStringEncodingDecode(CPStringEncodingBytesOfData(someData), anEncoding, NO);
if (decoded === nil)
return nil;
return [self initWithString:decoded];
}
/*!
@@ -856,7 +893,7 @@ var CPStringEncodingDecode = function(bytes, anEncoding, isLossy)
*/
+ (CPStringEncoding)stringEncodingForData:(CPData)someData encodingOptions:(CPDictionary)someOptions convertedString:(CPStringRef)aStringRef usedLossyConversion:(BOOLRef)aLossyRef
{
var bytes = someData ? [someData bytes] : [],
var bytes = CPStringEncodingBytesOfData(someData),
suggested = [someOptions objectForKey:CPStringEncodingDetectionSuggestedEncodingsKey] || [],
usesOnlySuggested = !![someOptions objectForKey:CPStringEncodingDetectionUseOnlySuggestedEncodingsKey],
allowLossy = [someOptions objectForKey:CPStringEncodingDetectionAllowLossyKey],
@@ -887,7 +924,7 @@ var CPStringEncodingDecode = function(bytes, anEncoding, isLossy)
if (bytes.length >= mark.length && mark.every(function(aByte, anIndex) { return bytes[anIndex] === aByte; }))
{
var decoded = CPStringEncodingDecode(bytes.slice(mark.length), byteOrderMarks[i][1], NO);
var decoded = CPStringEncodingDecode(CPStringEncodingBytesFromIndex(bytes, mark.length), byteOrderMarks[i][1], NO);
if (decoded !== nil)
return result(byteOrderMarks[i][1], decoded, NO);