Sources/SPTPersistentCacheFileManager.m (133 lines of code) (raw):
/*
Copyright (c) 2015-2021 Spotify AB.
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
#import "SPTPersistentCacheFileManager+Private.h"
#import "SPTPersistentCacheDebugUtilities.h"
#import <SPTPersistentCache/SPTPersistentCacheOptions.h>
static const double SPTPersistentCacheFileManagerMinFreeDiskSpace = 0.1;
const NSUInteger SPTPersistentCacheFileManagerSubDirNameLength = 2;
@implementation SPTPersistentCacheFileManager
#pragma mark - Initializer
- (instancetype)initWithOptions:(SPTPersistentCacheOptions *)options
{
self = [super init];
if (self) {
_options = [options copy];
_fileManager = [NSFileManager defaultManager];
_debugOutput = options.debugOutput;
}
return self;
}
#pragma mark -
- (BOOL)createCacheDirectory
{
BOOL isDirectory = NO;
BOOL exists = [self.fileManager fileExistsAtPath:self.options.cachePath isDirectory:&isDirectory];
if (exists && !isDirectory) {
SPTPersistentCacheSafeDebugCallback(
[NSString stringWithFormat:@"PersistentDataCache: Unable to create dir: %@ - file exists at path", self.options.cachePath],
self.debugOutput);
return NO;
}
if (exists == NO) {
NSError *error = nil;
BOOL didCreateDirectory = [self.fileManager createDirectoryAtPath:self.options.cachePath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (didCreateDirectory == NO) {
SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Unable to create dir: %@ with error:%@", self.options.cachePath, error], self.debugOutput);
return NO;
}
}
return YES;
}
/**
2 letter separation is handled only by this method. All other code is agnostic to this fact.
*/
- (NSString *)subDirectoryPathForKey:(NSString *)key
{
// make folder tree: xx/ zx/ xy/ yz/ etc.
NSString *subDir = self.options.cachePath;
if (self.options.useDirectorySeparation && key.length >= SPTPersistentCacheFileManagerSubDirNameLength) {
NSString *subDirectoryName = [key substringToIndex:SPTPersistentCacheFileManagerSubDirNameLength];
subDir = [self.options.cachePath stringByAppendingPathComponent:subDirectoryName];
}
return subDir;
}
- (NSString *)pathForKey:(NSString *)key
{
NSString *subDirectoryPathForKey = [self subDirectoryPathForKey:key];
return [subDirectoryPathForKey stringByAppendingPathComponent:key];
}
- (void)removeAllData
{
NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath];
NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
// Enumerate the dirEnumerator results, each value is stored in allURLs
NSURL *theURL = nil;
while ((theURL = [dirEnumerator nextObject])) {
// Retrieve the file name. From cached during the enumeration.
NSNumber *isDirectory;
if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) {
if ([isDirectory boolValue] == NO) {
NSString *key = theURL.lastPathComponent;
// That satisfies Req.#1.3
[self removeDataForKey:key];
}
}
}
}
- (void)removeDataForKey:(NSString *)key
{
NSError *error = nil;
NSString *filePath = [self pathForKey:key];
if (![self.fileManager removeItemAtPath:filePath error:&error]) {
SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Error removing data for Key:%@ , error:%@", key, error], self.debugOutput);
}
}
- (NSUInteger)getFileSizeAtPath:(NSString *)filePath
{
NSError *error = nil;
NSDictionary *attrs = [self.fileManager attributesOfItemAtPath:filePath error:&error];
if (attrs == nil) {
SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: Error getting attributes for file: %@, error: %@", filePath, error], self.debugOutput);
}
return (NSUInteger)[attrs fileSize];
}
- (NSUInteger)totalUsedSizeInBytes
{
NSUInteger size = 0;
NSURL *urlPath = [NSURL fileURLWithPath:self.options.cachePath];
NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtURL:urlPath
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
// Enumerate the dirEnumerator results, each value is stored in allURLs
NSURL *theURL = nil;
while ((theURL = [dirEnumerator nextObject])) {
// Retrieve the file name. From cached during the enumeration.
NSNumber *isDirectory;
if ([theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]) {
if ([isDirectory boolValue] == NO) {
NSString *key = theURL.lastPathComponent;
NSString *filePath = [self pathForKey:key];
size += [self getFileSizeAtPath:filePath];
}
} else {
SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"Unable to fetch isDir#2 attribute:%@", theURL], self.debugOutput);
}
}
return size;
}
- (SPTPersistentCacheDiskSize)optimizedDiskSizeForCacheSize:(SPTPersistentCacheDiskSize)currentCacheSize
{
SPTPersistentCacheDiskSize tempCacheSize = (SPTPersistentCacheDiskSize)self.options.sizeConstraintBytes;
NSError *error = nil;
NSDictionary *fileSystemAttributes = [self.fileManager attributesOfFileSystemForPath:self.options.cachePath
error:&error];
if (fileSystemAttributes) {
// Never use the last SPTImageLoaderMinimumFreeDiskSpace of the disk for caching
NSNumber *fileSystemSize = fileSystemAttributes[NSFileSystemSize];
NSNumber *fileSystemFreeSpace = fileSystemAttributes[NSFileSystemFreeSize];
SPTPersistentCacheDiskSize totalSpace = fileSystemSize.longLongValue;
SPTPersistentCacheDiskSize freeSpace = fileSystemFreeSpace.longLongValue + currentCacheSize;
SPTPersistentCacheDiskSize proposedCacheSize = freeSpace - llrint(totalSpace *
SPTPersistentCacheFileManagerMinFreeDiskSpace);
tempCacheSize = MAX(0, proposedCacheSize);
} else {
SPTPersistentCacheSafeDebugCallback([NSString stringWithFormat:@"PersistentDataCache: %@ ERROR %@", @(__PRETTY_FUNCTION__), [error localizedDescription]], self.debugOutput);
}
return MIN(tempCacheSize, (SPTPersistentCacheDiskSize)self.options.sizeConstraintBytes);
}
@end