blob: 8853dbcd3f41c8ebe467c944fae35fed239e6c97 [file] [log] [blame]
/*
* 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 "WXStorageModule.h"
#import "WXSDKManager.h"
#import "WXThreadSafeMutableDictionary.h"
#import "WXThreadSafeMutableArray.h"
#import <CommonCrypto/CommonCrypto.h>
#import "WXUtility.h"
static NSString * const WXStorageDirectory = @"wxstorage";
static NSString * const WXStorageFileName = @"wxstorage.plist";
static NSString * const WXStorageInfoFileName = @"wxstorage.info.plist";
static NSString * const WXStorageIndexFileName = @"wxstorage.index.plist";
static NSUInteger const WXStorageLineLimit = 1024;
static NSUInteger const WXStorageTotalLimit = 5 * 1024 * 1024;
static NSString * const WXStorageThreadName = @"com.taobao.weex.storage";
static NSString * const WXStorageNullValue = @"#{eulaVlluNegarotSXW}";
@implementation WXStorageModule
@synthesize weexInstance;
WX_EXPORT_METHOD(@selector(length:))
WX_EXPORT_METHOD(@selector(getItem:callback:))
WX_EXPORT_METHOD(@selector(setItem:value:callback:))
WX_EXPORT_METHOD(@selector(setItemPersistent:value:callback:))
WX_EXPORT_METHOD(@selector(getAllKeys:))
WX_EXPORT_METHOD(@selector(removeItem:callback:))
#pragma mark - Export
- (dispatch_queue_t)targetExecuteQueue {
return [WXStorageModule storageQueue];
}
- (void)length:(WXModuleCallback)callback
{
if (callback) {
callback(@{@"result":@"success",@"data":@([[WXStorageModule memory] count])});
}
}
- (void)getAllKeys:(WXModuleCallback)callback
{
if (callback) {
callback(@{@"result":@"success",@"data":[WXStorageModule memory].allKeys});
}
}
- (void)getItem:(NSString *)key callback:(WXModuleCallback)callback
{
if ([self checkInput:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"key must a string or number!"}); // forgive my english
}
return;
}
if ([key isKindOfClass:[NSNumber class]]) {
key = [((NSNumber *)key) stringValue]; // oh no!
}
if ([WXUtility isBlankString:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"invalid_param"});
}
return ;
}
NSString *value = [self.memory objectForKey:key];
if ([WXStorageNullValue isEqualToString:value]) {
value = [[WXUtility globalCache] objectForKey:key];
if (!value) {
NSString *filePath = [WXStorageModule filePathForKey:key];
NSString *contents = [WXUtility stringWithContentsOfFile:filePath];
if (contents) {
[[WXUtility globalCache] setObject:contents forKey:key cost:contents.length];
value = contents;
}
}
}
if (!value) {
[self executeRemoveItem:key];
if (callback) {
callback(@{@"result":@"failed",@"data":@"undefined"});
}
return;
}
[self updateTimestampForKey:key];
[self updateIndexForKey:key];
if (callback) {
callback(@{@"result":@"success",@"data":value});
}
}
- (void)setItem:(NSString *)key value:(NSString *)value callback:(WXModuleCallback)callback
{
if ([self checkInput:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"key must a string or number!"});
}
return;
}
if ([self checkInput:value]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"value must a string or number!"});
}
return;
}
if ([key isKindOfClass:[NSNumber class]]) {
key = [((NSNumber *)key) stringValue];
}
if ([value isKindOfClass:[NSNumber class]]) {
value = [((NSNumber *)value) stringValue];
}
if ([WXUtility isBlankString:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"invalid_param"});
}
return ;
}
[self setObject:value forKey:key persistent:NO callback:callback];
}
- (void)setItemPersistent:(NSString *)key value:(NSString *)value callback:(WXModuleCallback)callback
{
if ([self checkInput:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"key must a string or number!"});
}
return;
}
if ([self checkInput:value]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"value must a string or number!"});
}
return;
}
if ([key isKindOfClass:[NSNumber class]]) {
key = [((NSNumber *)key) stringValue];
}
if ([value isKindOfClass:[NSNumber class]]) {
value = [((NSNumber *)value) stringValue];
}
if ([WXUtility isBlankString:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"invalid_param"});
}
return ;
}
[self setObject:value forKey:key persistent:YES callback:callback];
}
- (void)removeItem:(NSString *)key callback:(WXModuleCallback)callback
{
if ([self checkInput:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"key must a string or number!"});
}
return;
}
if ([key isKindOfClass:[NSNumber class]]) {
key = [((NSNumber *)key) stringValue];
}
if ([WXUtility isBlankString:key]) {
if (callback) {
callback(@{@"result":@"failed",@"data":@"invalid_param"});
}
return ;
}
BOOL removed = [self executeRemoveItem:key];
if (removed) {
if (callback) {
callback(@{@"result":@"success"});
}
} else {
if (callback) {
callback(@{@"result":@"failed"});
}
}
}
- (BOOL)executeRemoveItem:(NSString *)key {
if ([WXStorageNullValue isEqualToString:self.memory[key]]) {
[self.memory removeObjectForKey:key];
NSDictionary *dict = [self.memory copy];
[self write:dict toFilePath:[WXStorageModule filePath]];
dispatch_async([WXStorageModule storageQueue], ^{
NSString *filePath = [WXStorageModule filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[[WXUtility globalCache] removeObjectForKey:key];
});
} else if (self.memory[key]) {
[self.memory removeObjectForKey:key];
NSDictionary *dict = [self.memory copy];
[self write:dict toFilePath:[WXStorageModule filePath]];
} else {
return NO;
}
[self removeInfoForKey:key];
[self removeIndexForKey:key];
return YES;
}
#pragma mark - Utils
- (void)setObject:(NSString *)obj forKey:(NSString *)key persistent:(BOOL)persistent callback:(WXModuleCallback)callback {
NSString *filePath = [WXStorageModule filePathForKey:key];
if (obj.length <= WXStorageLineLimit) {
if ([WXStorageNullValue isEqualToString:self.memory[key]]) {
[[WXUtility globalCache] removeObjectForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
self.memory[key] = obj;
NSDictionary *dict = [self.memory copy];
[self write:dict toFilePath:[WXStorageModule filePath]];
[self setInfo:@{@"persistent":@(persistent),@"size":@(obj.length)} ForKey:key];
[self updateIndexForKey:key];
[self checkStorageLimit];
if (callback) {
callback(@{@"result":@"success"});
}
return;
}
[[WXUtility globalCache] setObject:obj forKey:key cost:obj.length];
if (![WXStorageNullValue isEqualToString:self.memory[key]]) {
self.memory[key] = WXStorageNullValue;
NSDictionary *dict = [self.memory copy];
[self write:dict toFilePath:[WXStorageModule filePath]];
}
dispatch_async([WXStorageModule storageQueue], ^{
[obj writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:NULL];
});
[self setInfo:@{@"persistent":@(persistent),@"size":@(obj.length)} ForKey:key];
[self updateIndexForKey:key];
[self checkStorageLimit];
if (callback) {
callback(@{@"result":@"success"});
}
}
- (void)checkStorageLimit {
NSInteger size = [self totalSize] - WXStorageTotalLimit;
if (size > 0) {
[self removeItemsBySize:size];
}
}
- (void)removeItemsBySize:(NSInteger)size {
NSArray *indexs = [[self indexs] copy];
if (size < 0 || indexs.count == 0) {
return;
}
NSMutableArray *removedKeys = [NSMutableArray array];
for (NSInteger i = 0; i < indexs.count; i++) {
NSString *key = indexs[i];
NSDictionary *info = [self getInfoForKey:key];
// persistent data, can't be removed
if ([info[@"persistent"] boolValue]) {
continue;
}
[removedKeys addObject:key];
size -= [info[@"size"] integerValue];
if (size < 0) {
break;
}
}
// actually remove data
for (NSString *key in removedKeys) {
[self executeRemoveItem:key];
}
}
- (void)write:(NSDictionary *)dict toFilePath:(NSString *)filePath{
[dict writeToFile:filePath atomically:YES];
}
+ (NSString *)filePathForKey:(NSString *)key
{
NSString *safeFileName = [WXUtility md5:key];
return [[WXStorageModule directory] stringByAppendingPathComponent:safeFileName];
}
+ (void)setupDirectory{
BOOL isDirectory = NO;
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[WXStorageModule directory] isDirectory:&isDirectory];
if (!isDirectory && !fileExists) {
[[NSFileManager defaultManager] createDirectoryAtPath:[WXStorageModule directory]
withIntermediateDirectories:YES
attributes:nil
error:NULL];
}
}
+ (NSString *)directory {
static NSString *storageDirectory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
storageDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
storageDirectory = [storageDirectory stringByAppendingPathComponent:WXStorageDirectory];
});
return storageDirectory;
}
+ (NSString *)filePath {
static NSString *storageFilePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
storageFilePath = [[WXStorageModule directory] stringByAppendingPathComponent:WXStorageFileName];
});
return storageFilePath;
}
+ (NSString *)infoFilePath {
static NSString *infoFilePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
infoFilePath = [[WXStorageModule directory] stringByAppendingPathComponent:WXStorageInfoFileName];
});
return infoFilePath;
}
+ (NSString *)indexFilePath {
static NSString *indexFilePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
indexFilePath = [[WXStorageModule directory] stringByAppendingPathComponent:WXStorageIndexFileName];
});
return indexFilePath;
}
+ (dispatch_queue_t)storageQueue {
static dispatch_queue_t storageQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
storageQueue = dispatch_queue_create("com.taobao.weex.storage", DISPATCH_QUEUE_SERIAL);
});
return storageQueue;
}
+ (WXThreadSafeMutableDictionary<NSString *, NSString *> *)memory {
static WXThreadSafeMutableDictionary<NSString *,NSString *> *memory;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WXStorageModule setupDirectory];
if ([[NSFileManager defaultManager] fileExistsAtPath:[WXStorageModule filePath]]) {
NSDictionary *contents = [NSDictionary dictionaryWithContentsOfFile:[WXStorageModule filePath]];
if (contents) {
memory = [[WXThreadSafeMutableDictionary alloc] initWithDictionary:contents];
}
}
if (!memory) {
memory = [WXThreadSafeMutableDictionary new];
}
// [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) {
// [memory removeAllObjects];
// }];
});
return memory;
}
+ (WXThreadSafeMutableDictionary<NSString *, NSDictionary *> *)info {
static WXThreadSafeMutableDictionary<NSString *,NSDictionary *> *info;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WXStorageModule setupDirectory];
if ([[NSFileManager defaultManager] fileExistsAtPath:[WXStorageModule infoFilePath]]) {
NSDictionary *contents = [NSDictionary dictionaryWithContentsOfFile:[WXStorageModule infoFilePath]];
if (contents) {
info = [[WXThreadSafeMutableDictionary alloc] initWithDictionary:contents];
}
}
if (!info) {
info = [WXThreadSafeMutableDictionary new];
}
});
return info;
}
+ (WXThreadSafeMutableArray *)indexs {
static WXThreadSafeMutableArray *indexs;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WXStorageModule setupDirectory];
if ([[NSFileManager defaultManager] fileExistsAtPath:[WXStorageModule indexFilePath]]) {
NSArray *contents = [NSArray arrayWithContentsOfFile:[WXStorageModule indexFilePath]];
if (contents) {
indexs = [[WXThreadSafeMutableArray alloc] initWithArray:contents];
}
}
if (!indexs) {
indexs = [WXThreadSafeMutableArray new];
}
});
return indexs;
}
- (WXThreadSafeMutableDictionary<NSString *, NSString *> *)memory {
return [WXStorageModule memory];
}
- (WXThreadSafeMutableDictionary<NSString *, NSDictionary *> *)info {
return [WXStorageModule info];
}
- (WXThreadSafeMutableArray *)indexs {
return [WXStorageModule indexs];
}
- (BOOL)checkInput:(id)input{
return !([input isKindOfClass:[NSString class]] || [input isKindOfClass:[NSNumber class]]);
}
#pragma mark
#pragma mark - Storage Info method
- (NSDictionary *)getInfoForKey:(NSString *)key {
NSDictionary *info = [[self info] objectForKey:key];
if (!info) {
return nil;
}
return info;
}
- (void)setInfo:(NSDictionary *)info ForKey:(NSString *)key {
NSAssert(info, @"info must not be nil");
// save info for key
NSMutableDictionary *newInfo = [NSMutableDictionary dictionaryWithDictionary:info];
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
[newInfo setObject:@(interval) forKey:@"ts"];
[[self info] setObject:[newInfo copy] forKey:key];
NSDictionary *dict = [[self info] copy];
[self write:dict toFilePath:[WXStorageModule infoFilePath]];
}
- (void)removeInfoForKey:(NSString *)key {
[[self info] removeObjectForKey:key];
NSDictionary *dict = [[self info] copy];
[self write:dict toFilePath:[WXStorageModule infoFilePath]];
}
- (void)updateTimestampForKey:(NSString *)key {
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
NSDictionary *info = [[self info] objectForKey:key];
if (!info) {
info = @{@"persistent":@(NO),@"size":@(0),@"ts":@(interval)};
} else {
NSMutableDictionary *newInfo = [NSMutableDictionary dictionaryWithDictionary:info];
[newInfo setObject:@(interval) forKey:@"ts"];
info = [newInfo copy];
}
[[self info] setObject:info forKey:key];
NSDictionary *dict = [[self info] copy];
[self write:dict toFilePath:[WXStorageModule infoFilePath]];
}
- (NSInteger)totalSize {
NSInteger totalSize = 0;
for (NSDictionary *info in [self info].allValues) {
totalSize += (info[@"size"] ? [info[@"size"] integerValue] : 0);
}
return totalSize;
}
#pragma mark
#pragma mark - Storage Index method
- (void)updateIndexForKey:(NSString *)key {
[[self indexs] removeObject:key];
[[self indexs] addObject:key];
[self write:[[self indexs] copy] toFilePath:[WXStorageModule indexFilePath]];
}
- (void)removeIndexForKey:(NSString *)key {
[[self indexs] removeObject:key];
[self write:[[self indexs] copy] toFilePath:[WXStorageModule indexFilePath]];
}
@end