/* HBQueueJobItem.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBQueueJobItem.h"
#import "HBCodingUtilities.h"
#import "HBAttributedStringAdditions.h"
static NSDateFormatter *_dateFormatter = nil;
static NSNumberFormatter *_numberFormatter = nil;
static NSByteCountFormatter *_byteFormatter = nil;
static NSDictionary *detailAttr;
static NSDictionary *detailBoldAttr;
static NSDictionary *shortHeightAttr;
@interface HBQueueJobItem ()
@property (nonatomic) NSTimeInterval encodeDuration;
@property (nonatomic) NSTimeInterval pauseDuration;
@property (nonatomic, nullable) NSDate *pausedDate;
@property (nonatomic, nullable) NSDate *resumedDate;
@property (nonatomic) double avgFps;
@property (nonatomic) NSUInteger fileSize;
@property (nonatomic) NSUInteger sourceFileSize;
@property (nonatomic, readwrite, nullable) NSAttributedString *attributedStatistics;
@end
@implementation HBQueueJobItem
+ (void)initialize
{
if (self == [HBQueueJobItem class]) {
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateStyle = NSDateFormatterLongStyle;
_dateFormatter.timeStyle = NSDateFormatterLongStyle;
_numberFormatter = [[NSNumberFormatter alloc] init];
_numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
_numberFormatter.maximumFractionDigits = 2;
_byteFormatter = [[NSByteCountFormatter alloc] init];
CGFloat indent = 100;
NSBundle *bundle = [NSBundle bundleForClass:[HBQueueJobItem class]];
NSString *currentLocalization = bundle.preferredLocalizations.firstObject;
if ([currentLocalization hasPrefix:@"de"])
{
indent = 120;
}
else if ([currentLocalization hasPrefix:@"fr"] || [currentLocalization hasPrefix:@"pt"])
{
indent = 114;
}
// Attributes
NSMutableParagraphStyle *ps = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
ps.headIndent = indent;
ps.paragraphSpacing = 1.0;
ps.tabStops = @[[[NSTextTab alloc] initWithType:NSRightTabStopType location:indent - 2],
[[NSTextTab alloc] initWithType:NSLeftTabStopType location:indent]];
detailAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:NSFont.smallSystemFontSize],
NSParagraphStyleAttributeName: ps,
NSForegroundColorAttributeName: NSColor.labelColor};
detailBoldAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:NSFont.smallSystemFontSize],
NSParagraphStyleAttributeName: ps,
NSForegroundColorAttributeName: NSColor.labelColor};
shortHeightAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:6.0]};
}
}
@synthesize job = _job;
@synthesize attributedDescription = _attributedDescription;
- (instancetype)initWithJob:(HBJob *)job
{
self = [super init];
if (self)
{
_job = job;
}
return self;
}
#pragma mark - Properties
- (void)setState:(HBQueueItemState)state
{
_state = state;
if (state == HBQueueItemStateReady)
{
[self resetStatistics];
self.activityLogURL = nil;
}
}
- (NSString *)title
{
return _job.destinationFileName;
}
- (BOOL)hasFileRepresentation
{
return YES;
}
- (NSImage *)image
{
return [NSImage imageNamed:@"JobSmall"];;
}
- (NSURL *)fileURL
{
return _job.fileURL;
}
- (NSString *)destinationFileName
{
return _job.destinationFileName;
}
- (NSURL *)destinationFolderURL
{
return _job.destinationFolderURL;
}
- (NSURL *)destinationURL
{
return _job.destinationURL;
}
- (NSAttributedString *)attributedDescription
{
if (_attributedDescription == nil) {
_attributedDescription = _job.attributedDescription;
}
return _attributedDescription;
}
- (NSAttributedString *)attributedStatistics
{
if (self.endedDate == nil && self.startedDate == nil)
{
return nil;
}
if (_attributedStatistics == nil)
{
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init];
if (self.startedDate)
{
[attrString appendString:@"\t" withAttributes:detailAttr];
[attrString appendString:NSLocalizedString(@"Started at:", @"Job statistics") withAttributes:detailBoldAttr];
[attrString appendString:@" \t" withAttributes:detailAttr];
[attrString appendString:[_dateFormatter stringFromDate:self.startedDate] withAttributes:detailAttr];
}
if (self.startedDate && self.endedDate)
{
[attrString appendString:@"\n\t" withAttributes:detailAttr];
[attrString appendString:NSLocalizedString(@"Ended at:", @"Job statistics") withAttributes:detailBoldAttr];
[attrString appendString:@" \t" withAttributes:detailAttr];
[attrString appendString:[_dateFormatter stringFromDate:self.endedDate] withAttributes:detailAttr];
[attrString appendString:@"\n\n" withAttributes:shortHeightAttr];
[attrString appendString:@"\t" withAttributes:detailAttr];
[attrString appendString:NSLocalizedString(@"Run time:", @"Job statistics") withAttributes:detailBoldAttr];
[attrString appendString:@" \t" withAttributes:detailAttr];
uint64_t encodeDuration = (uint64_t)self.encodeDuration;
[attrString appendString:[NSString stringWithFormat:@"%02lld:%02lld:%02lld", encodeDuration / 3600, (encodeDuration/ 60) % 60, encodeDuration % 60] withAttributes:detailAttr];
[attrString appendString:@"\n\t" withAttributes:detailAttr];
[attrString appendString:NSLocalizedString(@"Paused time:", @"Job statistics") withAttributes:detailBoldAttr];
[attrString appendString:@" \t" withAttributes:detailAttr];
uint64_t pauseDuration = (uint64_t)self.pauseDuration;
[attrString appendString:[NSString stringWithFormat:@"%02lld:%02lld:%02lld", pauseDuration / 3600, (pauseDuration/ 60) % 60, pauseDuration % 60] withAttributes:detailAttr];
if (self.avgFps > 0)
{
[attrString appendString:@"\n\t" withAttributes:detailAttr];
[attrString appendString:NSLocalizedString(@"Average speed:", @"Job statistics") withAttributes:detailBoldAttr];
[attrString appendString:@" \t" withAttributes:detailAttr];
NSString *formattedAvgFps = [_numberFormatter stringFromNumber:@(self.avgFps)];
[attrString appendString:[NSString stringWithFormat:NSLocalizedString(@"%@ fps", @"Job statistics"), formattedAvgFps] withAttributes:detailAttr];
}
[attrString appendString:@"\n\n" withAttributes:shortHeightAttr];
[attrString appendString:@"\t" withAttributes:detailAttr];
[attrString appendString:NSLocalizedString(@"Size:", @"Job statistics") withAttributes:detailBoldAttr];
[attrString appendString:@" \t" withAttributes:detailAttr];
[attrString appendString:[_byteFormatter stringFromByteCount:self.fileSize] withAttributes:detailAttr];
if (self.job.isStream && self.sourceFileSize > 0 && self.fileSize > 0)
{
double difference = 100.f / self.sourceFileSize * self.fileSize;
NSString *formattedDifference = [_numberFormatter stringFromNumber:@(difference)];
[attrString appendString:[NSString stringWithFormat:NSLocalizedString(@" (%@ %% of the source file)", @"Job statistics"), formattedDifference] withAttributes:detailAttr];
}
}
_attributedStatistics = attrString;
}
return _attributedStatistics;
}
#pragma mark - Statistics
- (void)resetStatistics
{
self.pausedDate = nil;
self.resumedDate = nil;
self.startedDate = nil;
self.endedDate = nil;
self.encodeDuration = 0;
self.pauseDuration = 0;
self.avgFps = 0;
self.fileSize = 0;
self.attributedStatistics = nil;
}
- (void)pausedAtDate:(NSDate *)date
{
self.pausedDate = date;
}
- (void)resumedAtDate:(NSDate *)date
{
self.resumedDate = date;
self.pauseDuration += [self.resumedDate timeIntervalSinceDate:self.pausedDate];
self.pausedDate = nil;
}
- (void)setStartedDate:(NSDate *)startedDate
{
_startedDate = startedDate;
self.attributedStatistics = nil;
}
- (void)setEndedDate:(NSDate *)endedDate
{
_endedDate = endedDate;
if (endedDate && self.pausedDate)
{
[self resumedAtDate:endedDate];
}
if (endedDate && self.startedDate)
{
self.encodeDuration = [self.endedDate timeIntervalSinceDate:self.startedDate];
self.encodeDuration -= self.pauseDuration;
}
NSDictionary *values;
[self.destinationURL removeCachedResourceValueForKey:NSURLFileSizeKey];
values = [self.destinationURL resourceValuesForKeys:@[NSURLFileSizeKey] error:NULL];
self.fileSize = [values[NSURLFileSizeKey] integerValue];
[self.fileURL removeCachedResourceValueForKey:NSURLFileSizeKey];
values = [self.fileURL resourceValuesForKeys:@[NSURLFileSizeKey] error:NULL];
self.sourceFileSize = [values[NSURLFileSizeKey] integerValue];
self.attributedStatistics = nil;
}
- (void)setDoneWithResult:(HBCoreResult)result
{
self.avgFps = result.avgFps;
switch (result.code) {
case HBCoreResultCodeDone:
self.state = HBQueueItemStateCompleted;
break;
case HBCoreResultCodeCanceled:
self.state = HBQueueItemStateCanceled;
break;
default:
self.state = HBQueueItemStateFailed;
break;
}
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding
{
return YES;
}
static NSString *versionKey = @"HBQueueItemVersion";
- (void)encodeWithCoder:(nonnull NSCoder *)coder
{
[coder encodeInt:1 forKey:versionKey];
encodeInteger(_state);
encodeObject(_job);
encodeObject(_activityLogURL);
encodeDouble(_encodeDuration);
encodeDouble(_pauseDuration);
encodeDouble(_avgFps);
encodeObject(_startedDate);
encodeObject(_endedDate);
encodeInteger(_fileSize);
encodeInteger(_sourceFileSize);
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)decoder
{
int version = [decoder decodeIntForKey:versionKey];
if (version == 1 && (self = [super init]))
{
decodeInteger(_state); if (_state < HBQueueItemStateReady || _state > HBQueueItemStateRescanning) { goto fail; }
decodeObjectOrFail(_job, HBJob);
decodeObject(_activityLogURL, NSURL);
decodeDouble(_encodeDuration);
decodeDouble(_pauseDuration);
decodeDouble(_avgFps);
decodeObject(_startedDate, NSDate);
decodeObject(_endedDate, NSDate);
decodeInteger(_fileSize);
decodeInteger(_sourceFileSize);
return self;
}
fail:
return nil;
}
@end