Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
05f793b
Fix use-after-free in LibAV frame resize buffer management
znelson Apr 2, 2026
04ab284
Fix double initialization of MPVRenderController
znelson Apr 2, 2026
baac191
Fix NSURL category methods using absoluteString instead of path
znelson Apr 2, 2026
bc30333
Fix sumAllVideoDurationForUI: always returning 0
znelson Apr 2, 2026
7869683
Fix asyncInitAndAdd:uiCompletion: dispatching to wrong thread
znelson Apr 2, 2026
52830a1
Fix crash when fetching non-existent object by identifier
znelson Apr 2, 2026
0da893f
Fix errant glClear after mpv render that blanked video frames
znelson Apr 2, 2026
6e82486
Fix missing KVO observer removal in media player views
znelson Apr 2, 2026
dd71481
Retain CGL context returned from copyCGLContextForPixelFormat:
znelson Apr 2, 2026
7589ceb
Fix retain cycle in CTI view frame render completion block
znelson Apr 2, 2026
8fdb273
Fix ArchivedView nib lookup using wrong bundle for subclasses
znelson Apr 2, 2026
e91def5
Fix toolbar showing no selected item on first preferences show
znelson Apr 2, 2026
c2b4bee
Remove incorrect setFrameFromString: call in preferences window
znelson Apr 2, 2026
c34b5dc
Fix memory leak of mpv version property string
znelson Apr 2, 2026
9462168
Fix CFString leaks in OpenGL proc address lookup
znelson Apr 2, 2026
637f576
Fix CGColor leak in peek view border color setup
znelson Apr 2, 2026
eaa03c1
Fix tableNames using static variable shared across all instances
znelson Apr 2, 2026
e1a7146
Add NULL check in zen_mpv_to_nsstring
znelson Apr 2, 2026
362d140
Add NULL check after mpv_create failure
znelson Apr 2, 2026
b321aa4
Guard pthread_cond_wait against spurious wakeups in render loop
znelson Apr 2, 2026
9dbefbd
Fix NULL deref and signed-to-unsigned cast in durationMicroseconds
znelson Apr 2, 2026
81df3f5
Fix breadth-first traversal producing incorrect order for deep trees
znelson Apr 2, 2026
74cec7d
Fix off-by-one in countChildrenAndDescendants
znelson Apr 2, 2026
8195854
Fix MultiToken activate always returning YES
znelson Apr 2, 2026
9aea3d2
Handle mpv_render_context_create failure
znelson Apr 2, 2026
4f18c1d
Remove unsafe_unretained in mpv async dispatch callbacks
znelson Apr 2, 2026
9f353c7
Fix render loop deadlock when no frame is ready to draw
znelson Apr 2, 2026
fd4ce78
Add NULL check for codec and stream in LibAV render init
znelson Apr 2, 2026
8107e90
Fix AVFrame leak in resizeRawFrame when source size is zero
znelson Apr 2, 2026
72ffeb8
Fix setPreferenceViewControllers: appending instead of replacing
znelson Apr 2, 2026
9c3c93a
Add NULL guard for outVolumeUUID in zen_volumeName
znelson Apr 2, 2026
010e12b
Replace assert with NSCAssert in SecRandomCopyBytes error check
znelson Apr 2, 2026
0dafe44
Implement NSCopying on ZENNode to fix copyChildren crash
znelson Apr 2, 2026
a4741ba
Fix cache/DB inconsistency by moving cache ops inside transactions
znelson Apr 2, 2026
4978a4f
Check getResourceValue return values in zen_volumeInfoForUUID
znelson Apr 2, 2026
4078e81
Check FMDatabase open return value in database queue
znelson Apr 2, 2026
2246213
Check cancellation token in vacuumAsync
znelson Apr 2, 2026
eadf903
Declare NSString notification constants as const
znelson Apr 2, 2026
ce5d897
Use copy attribute for NSString property in DatabaseQueue
znelson Apr 2, 2026
0b60e3b
Rename initCommon to setupCommon to avoid ARC init method family
znelson Apr 2, 2026
9ef1c25
Warning fix: showsBaselineSeparator is deprecated
znelson Apr 2, 2026
3a009ed
Warning fix: cast to CGBitmapInfo
znelson Apr 2, 2026
22972bf
Return copy of children array to prevent mutation during enumeration
znelson Apr 2, 2026
2b0667b
Use self instead of defaultManager in NSFileManager category
znelson Apr 2, 2026
1ae0afd
Use strong outlet for top-level nib rootView in ArchivedView
znelson Apr 2, 2026
ba68913
Use explicit request type enum for frame render disambiguation
znelson Apr 2, 2026
9a9e6a6
Merge remote-tracking branch 'origin/main' into bug-fixes
znelson Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CoreZen/Cache/ObjectCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ @interface ZENObjectCache ()
- (instancetype)initWeakObjectCache;
- (instancetype)initStrongObjectCache;

- (void)initCommon:(NSString *)queueLabel;
- (void)setupCommon:(NSString *)queueLabel;

@end

Expand All @@ -28,7 +28,7 @@ - (instancetype)init {
return [self initWeakObjectCache];
}

- (void)initCommon:(NSString *)queueLabel {
- (void)setupCommon:(NSString *)queueLabel {
static atomic_uint_fast64_t nextIdentifier = 0;
uint64_t cacheID = atomic_fetch_add(&nextIdentifier, 1);

Expand All @@ -41,7 +41,7 @@ - (instancetype)initWeakObjectCache {
if (self) {
_mapTable = [NSMapTable strongToWeakObjectsMapTable];

[self initCommon:@"ZENWeakObjectCache"];
[self setupCommon:@"ZENWeakObjectCache"];
}
return self;
}
Expand All @@ -55,7 +55,7 @@ - (instancetype)initStrongObjectCache {
if (self) {
_mapTable = [NSMapTable strongToStrongObjectsMapTable];

[self initCommon:@"ZENStrongObjectCache"];
[self setupCommon:@"ZENStrongObjectCache"];
}
return self;
}
Expand Down
2 changes: 1 addition & 1 deletion CoreZen/Categories/NSFileManager+CoreZen.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ - (NSURL *)zen_findOrCreateURLForDirectory:(NSSearchPathDirectory)searchPathDire
create:(BOOL)create
error:(NSError **)outError {
// Search for the directory
NSURL *url = [NSFileManager.defaultManager URLForDirectory:searchPathDirectory inDomain:domain appropriateForURL:nil create:create error:outError];
NSURL *url = [self URLForDirectory:searchPathDirectory inDomain:domain appropriateForURL:nil create:create error:outError];
if (!url) {
return nil;
}
Expand Down
2 changes: 1 addition & 1 deletion CoreZen/Categories/NSNumber+CoreZen.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ @implementation NSNumber (CoreZen)
+ (NSInteger)zen_randomInteger {
int64_t randomBytes;
int result = SecRandomCopyBytes(kSecRandomDefault, 8, &randomBytes);
assert(result == errSecSuccess);
NSCAssert(result == errSecSuccess, @"SecRandomCopyBytes failed with status %d", result);
return randomBytes;
}

Expand Down
18 changes: 11 additions & 7 deletions CoreZen/Categories/NSURL+CoreZen.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ - (BOOL)zen_volumeName:(NSString **)outVolumeName
if ([self getResourceValue:outVolumeName forKey:NSURLVolumeLocalizedNameKey error:outError] &&
[self getResourceValue:outVolumeURL forKey:NSURLVolumeURLKey error:outError] &&
[self getResourceValue:&volumeUUID forKey:NSURLVolumeUUIDStringKey error:outError]) {
*outVolumeUUID = [[NSUUID alloc] initWithUUIDString:volumeUUID];
if (outVolumeUUID) {
*outVolumeUUID = [[NSUUID alloc] initWithUUIDString:volumeUUID];
}
return YES;
}
return NO;
Expand Down Expand Up @@ -45,18 +47,20 @@ + (BOOL)zen_volumeInfoForUUID:(NSUUID *)volumeUUID
if ([volumeURL getResourceValue:&uuid forKey:NSURLVolumeUUIDStringKey error:&error] &&
[uuid isEqual:volumeUUIDString]) {

[volumeURL getResourceValue:outVolumeName forKey:NSURLVolumeLocalizedNameKey error:&error];
[volumeURL getResourceValue:outVolumeURL forKey:NSURLVolumeURLKey error:&error];
return YES;
if ([volumeURL getResourceValue:outVolumeName forKey:NSURLVolumeLocalizedNameKey error:&error] &&
[volumeURL getResourceValue:outVolumeURL forKey:NSURLVolumeURLKey error:&error]) {
return YES;
}
return NO;
}
}
return NO;
}

- (NSString *)zen_relativePathToURL:(NSURL *)url {

NSString *fullPath = [url absoluteString];
NSString *basePath = [self absoluteString];
NSString *fullPath = [url path];
NSString *basePath = [self path];
if ([fullPath hasPrefix:basePath]) {
NSString *relativePath = [fullPath substringFromIndex:basePath.length];
return relativePath;
Expand All @@ -66,7 +70,7 @@ - (NSString *)zen_relativePathToURL:(NSURL *)url {

- (NSUInteger)zen_fileSize {
NSError *error;
NSDictionary *attributes = [NSFileManager.defaultManager attributesOfItemAtPath:self.absoluteString error:&error];
NSDictionary *attributes = [NSFileManager.defaultManager attributesOfItemAtPath:self.path error:&error];
if (attributes) {
return attributes.fileSize;
}
Expand Down
15 changes: 10 additions & 5 deletions CoreZen/Database/DatabaseQueue.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ - (instancetype)initWithURL:(NSURL *)URL;
- (FMDatabase *)threadDatabase;

@property (nonatomic, strong, readonly) NSURL *databaseURL;
@property (nonatomic, strong, readonly) NSString *databaseKey;
@property (nonatomic, copy, readonly) NSString *databaseKey;
@property (nonatomic, strong, readonly) ZENWorkQueue *workQueue;

@end;
Expand Down Expand Up @@ -105,7 +105,10 @@ - (FMDatabase *)threadDatabase {
database = [FMDatabase databaseWithPath:self.databaseKey];
}

[database open];
if (![database open]) {
NSLog(@"ERROR: Failed to open database at %@", self.databaseURL);
return nil;
}
[database executeUpdate:@"PRAGMA synchronous = 1;"];
[database setShouldCacheStatements:YES];

Expand Down Expand Up @@ -164,9 +167,11 @@ - (void)fetchSync:(ZENDatabaseBlock)fetchBlock {

- (void)vacuumAsync {
[self.workQueue async:^(ZENWorkQueueToken *canceled) {
@autoreleasepool {
FMDatabase *database = self.threadDatabase;
[database executeUpdate:@"VACUUM;"];
if (!canceled.canceled) {
@autoreleasepool {
FMDatabase *database = self.threadDatabase;
[database executeUpdate:@"VACUUM;"];
}
}
}];
}
Expand Down
9 changes: 3 additions & 6 deletions CoreZen/Database/DatabaseSchema.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,9 @@ - (void)initializeDatabase:(FMDatabase *)database {
}

- (NSArray *)tableNames {
static NSMutableArray *tableNames = nil;
if (!tableNames) {
tableNames = [NSMutableArray array];
for (Class<ZENDatabaseTable> tableClass in self.tableClasses) {
[tableNames addObject:[tableClass tableName]];
}
NSMutableArray *tableNames = [NSMutableArray array];
for (Class<ZENDatabaseTable> tableClass in self.tableClasses) {
[tableNames addObject:[tableClass tableName]];
}
return tableNames;
}
Expand Down
8 changes: 4 additions & 4 deletions CoreZen/Domain/ObjectRepository.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ typedef enum : NSUInteger {
@end


extern NSString *ZENObjectRepositoryNotificationObjectKey;
extern NSString * const ZENObjectRepositoryNotificationObjectKey;

extern NSString *ZENObjectRepositoryObjectAddedNotification;
extern NSString *ZENObjectRepositoryObjectUpdatedNotification;
extern NSString *ZENObjectRepositoryObjectDeletedNotification;
extern NSString * const ZENObjectRepositoryObjectAddedNotification;
extern NSString * const ZENObjectRepositoryObjectUpdatedNotification;
extern NSString * const ZENObjectRepositoryObjectDeletedNotification;
30 changes: 18 additions & 12 deletions CoreZen/Domain/ObjectRepository.m
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ - (void)sumAllVideoDuration:(ZENAsyncCountCompletionBlock)countBlock {
- (void)sumAllVideoDurationForUI:(ZENAsyncCountCompletionBlock)countBlock {
// Bounce to main thread
[self sumAllVideoDuration:^(NSUInteger count) {
ZENCallAsyncCountCompletionBlockOnMainThread(countBlock, 0);
ZENCallAsyncCountCompletionBlockOnMainThread(countBlock, count);
}];
}

Expand Down Expand Up @@ -134,6 +134,10 @@ - (void)fetchObjectByIdentifier:(ZENIdentifier)identifier
// and we only ever create and cache objects on this queue. If there was a race, only one instance
// can win and cache the object. The other instance would have a cache hit just before here.
object = [self fetchObjectByIdentifier:identifier database:database];
if (!object) {
ZENCallFetchResultsBlockOnThreadPool(resultsBlock, @[]);
return;
}
[self.cache cacheObject:object];

// Async initialize object
Expand Down Expand Up @@ -198,10 +202,11 @@ - (void)addObject:(ZENDomainObject *)domainObject
ZENObjectRepositoryNotificationObjectKey: domainObject
};

[self.cache cacheObject:domainObject];
[self.queue transactionAsync:^(FMDatabase *database) {
[self.table insertDTO:domainObject.basicDTO database:database];
ZENDeliverNotificationOnMainThread(ZENObjectRepositoryObjectAddedNotification, self, notificationData);
if ([self.table insertDTO:domainObject.basicDTO database:database]) {
[self.cache cacheObject:domainObject];
ZENDeliverNotificationOnMainThread(ZENObjectRepositoryObjectAddedNotification, self, notificationData);
}
ZENCallAsyncContinueBlockOnThreadPool(completion);
}];
}
Expand All @@ -224,7 +229,7 @@ - (void) asyncInitAndAdd:(ZENDomainObject *)domainObject
- (void) asyncInitAndAdd:(ZENDomainObject *)domainObject
uiCompletion:(ZENAsyncContinueBlock)completion {
[self asyncInitAndAdd:domainObject completion:^{
ZENCallAsyncContinueBlockOnThreadPool(completion);
ZENCallAsyncContinueBlockOnMainThread(completion);
}];
}

Expand Down Expand Up @@ -259,10 +264,11 @@ - (void)deleteObject:(ZENDomainObject *)domainObject
ZENObjectRepositoryNotificationObjectKey: domainObject
};

[self.cache removeObject:domainObject.identifier];
[self.queue transactionAsync:^(FMDatabase *database) {
[self.table deleteByIdentifier:domainObject.identifier database:database];
ZENDeliverNotificationOnMainThread(ZENObjectRepositoryObjectDeletedNotification, self, notificationData);
if ([self.table deleteByIdentifier:domainObject.identifier database:database]) {
[self.cache removeObject:domainObject.identifier];
ZENDeliverNotificationOnMainThread(ZENObjectRepositoryObjectDeletedNotification, self, notificationData);
}
ZENCallAsyncContinueBlockOnThreadPool(completion);
}];
}
Expand Down Expand Up @@ -292,7 +298,7 @@ - (void)zen_removeObserver:(id)observer {

@end

NSString *ZENObjectRepositoryNotificationObjectKey = @"ZENObjectRepositoryNotificationObject";
NSString *ZENObjectRepositoryObjectAddedNotification = @"ZENObjectRepositoryObjectAddedNotification";
NSString *ZENObjectRepositoryObjectUpdatedNotification = @"ZENObjectRepositoryObjectUpdatedNotification";
NSString *ZENObjectRepositoryObjectDeletedNotification = @"ZENObjectRepositoryObjectDeletedNotification";
NSString * const ZENObjectRepositoryNotificationObjectKey = @"ZENObjectRepositoryNotificationObject";
NSString * const ZENObjectRepositoryObjectAddedNotification = @"ZENObjectRepositoryObjectAddedNotification";
NSString * const ZENObjectRepositoryObjectUpdatedNotification = @"ZENObjectRepositoryObjectUpdatedNotification";
NSString * const ZENObjectRepositoryObjectDeletedNotification = @"ZENObjectRepositoryObjectDeletedNotification";
6 changes: 6 additions & 0 deletions CoreZen/Media/FrameRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@
typedef void (^ZENRenderFrameResultsBlock)(ZENRenderedFrame *frame);
typedef void (^ZENRenderFramesResultsBlock)(NSArray<ZENRenderedFrame *> *frames);

typedef NS_ENUM(NSInteger, ZENFrameRequestType) {
ZENFrameRequestTypeSeconds,
ZENFrameRequestTypePercentage
};

// Rendered frame result
@interface ZENRenderedFrame : NSObject

// The rendered video frame image
@property (nonatomic, strong) NSImage *image;

// The requested position of the frame
@property (nonatomic) ZENFrameRequestType requestType;
@property (nonatomic) int64_t requestedTimestamp;
@property (nonatomic) double requestedSeconds;
@property (nonatomic) double requestedPercentage;
Expand Down
2 changes: 2 additions & 0 deletions CoreZen/Media/FrameRenderer.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ - (ZENWorkQueueToken *)renderFrameAtSeconds:(double)seconds
completion:(ZENRenderFrameResultsBlock)completion {

ZENRenderedFrame *frame = [ZENRenderedFrame new];
frame.requestType = ZENFrameRequestTypeSeconds;
frame.requestedSeconds = seconds;

return [self.frameRenderController renderFrame:frame size:NSMakeSize(width, height) completion:completion];
Expand All @@ -53,6 +54,7 @@ - (ZENWorkQueueToken *)renderFrameAtPercentage:(double)percentage
completion:(ZENRenderFrameResultsBlock)completion {

ZENRenderedFrame *frame = [ZENRenderedFrame new];
frame.requestType = ZENFrameRequestTypePercentage;
frame.requestedPercentage = percentage;

return [self.frameRenderController renderFrame:frame size:NSMakeSize(width, height) completion:completion];
Expand Down
5 changes: 4 additions & 1 deletion CoreZen/Media/LibAV/LibAVInfoController.m
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ - (ZENLibAVRenderController *)frameRenderController {

- (NSUInteger)durationMicroseconds {
NSAssert(AV_TIME_BASE == 1000000, @"Expected libav AV_TIME_BASE 1000000 for microseconds, instead = %d", AV_TIME_BASE);
return _formatContext->duration;
if (!_formatContext || _formatContext->duration < 0) {
return 0;
}
return (NSUInteger)_formatContext->duration;
}

- (double)durationSeconds {
Expand Down
Loading
Loading