Source/TNLTemporaryFile.m (169 lines of code) (raw):
//
// TNLTemporaryFile.m
// TwitterNetworkLayer
//
// Created on 7/18/14.
// Copyright © 2020 Twitter. All rights reserved.
//
#import "TNL_Project.h"
#import "TNLTemporaryFile_Project.h"
NS_ASSUME_NONNULL_BEGIN
@implementation TNLTemporaryFile
{
BOOL _exists;
FILE *_file;
}
+ (nullable instancetype)temporaryFileWithExistingFilePath:(NSString *)path
error:(out NSError **)error
{
TNLTemporaryFile *file = [[TNLTemporaryFile alloc] init];
if (![file consumeExistingFile:path error:error]) {
file = nil;
}
return file;
}
- (instancetype)init
{
if (self = [super init]) {
_path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
}
return self;
}
- (void)dealloc
{
[self close:NULL];
if (_exists) {
[[NSFileManager defaultManager] removeItemAtPath:_path error:NULL];
}
}
- (BOOL)isOpen
{
return !!_file;
}
- (BOOL)consumeExistingFile:(NSString *)path error:(out NSError **)error
{
NSError *theError = nil;
if (_exists) {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:EEXIST
userInfo:@{ @"existing" : _path, @"source" : path ?: [NSNull null] }];
} else {
if ([[NSFileManager defaultManager] moveItemAtPath:path toPath:_path error:&theError]) {
_exists = YES;
} else {
TNLAssert(theError != nil);
}
}
if (error) {
*error = theError;
}
return !theError;
}
- (BOOL)close:(out NSError **)error
{
NSError *theError = nil;
if (_file) {
if (0 == fclose(_file)) {
_file = NULL;
} else {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
}
if (error) {
*error = theError;
}
return !theError;
}
- (BOOL)open:(out NSError **)error
{
NSError *theError = nil;
if (!_file) {
_file = fopen(_path.UTF8String, "a");
if (!_file) {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
} else {
_exists = YES;
}
}
if (error) {
*error = theError;
}
return !theError;
}
- (BOOL)appendData:(NSData *)data error:(out NSError **)error
{
NSError *theError = nil;
if (_file) {
NSUInteger length = data.length;
NSUInteger written = fwrite(data.bytes, 1, length, _file);
if (length != written) {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:ferror(_file)
userInfo:nil];
}
} else {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:ENOENT
userInfo:nil];
}
if (error) {
*error = theError;
}
return !theError;
}
- (BOOL)moveToPath:(NSString *)path error:(out NSError **)error
{
NSError *theError = nil;
if (!_file) {
if ([[NSFileManager defaultManager] moveItemAtPath:_path toPath:path error:&theError]) {
_exists = NO;
}
} else {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:EBUSY
userInfo:@{ @"message" : @"the file is still open for writing, please close the file before moving it." }];
}
if (error) {
*error = theError;
}
return !theError;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@ : %p, open=%@, exists=%@, path='%@'>", NSStringFromClass([self class]), self, self.isOpen ? @"YES" : @"NO", _exists ? @"YES" : @"NO", _path];
}
@end
@implementation TNLExpiredTemporaryFile
- (instancetype)initWithFilePath:(nullable NSString *)path
{
if (self = [super init]) {
_path = [path copy];
}
return self;
}
- (instancetype)init
{
return [self initWithFilePath:nil];
}
- (BOOL)moveToPath:(NSString *)path error:(out NSError **)error
{
NSError *theError = nil;
if (_path) {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:ENOENT
userInfo:@{ @"message" : @"the temporary file has expired and is no longer available to move.", @"path" : _path }];
} else {
theError = [NSError errorWithDomain:NSPOSIXErrorDomain
code:EINVAL
userInfo:@{ @"message" : @"there is no temporary file to move." }];
}
if (error) {
*error = theError;
}
return !theError;
}
@end
id<TNLTemporaryFile> __nullable TNLTemporaryFileConstructedFromExistingFile(NSString *filePath,
NSError * __nullable * __nullable errorOut)
{
return [TNLTemporaryFile temporaryFileWithExistingFilePath:filePath
error:errorOut];
}
NS_ASSUME_NONNULL_END