1/* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "WebDatabaseManagerPrivate.h" 30 31#if ENABLE(SQL_DATABASE) 32 33#import "WebDatabaseManagerClient.h" 34#import "WebPlatformStrategies.h" 35#import "WebSecurityOriginInternal.h" 36 37#import <WebCore/DatabaseManager.h> 38#import <WebCore/SecurityOrigin.h> 39 40#if PLATFORM(IOS) 41#import "WebDatabaseManagerInternal.h" 42#import <WebCore/DatabaseTracker.h> 43#import <WebCore/WebCoreThread.h> 44#endif 45 46using namespace WebCore; 47 48NSString *WebDatabaseDirectoryDefaultsKey = @"WebDatabaseDirectory"; 49 50NSString *WebDatabaseDisplayNameKey = @"WebDatabaseDisplayNameKey"; 51NSString *WebDatabaseExpectedSizeKey = @"WebDatabaseExpectedSizeKey"; 52NSString *WebDatabaseUsageKey = @"WebDatabaseUsageKey"; 53 54NSString *WebDatabaseDidModifyOriginNotification = @"WebDatabaseDidModifyOriginNotification"; 55NSString *WebDatabaseDidModifyDatabaseNotification = @"WebDatabaseDidModifyDatabaseNotification"; 56NSString *WebDatabaseIdentifierKey = @"WebDatabaseIdentifierKey"; 57 58#if PLATFORM(IOS) 59CFStringRef WebDatabaseOriginsDidChangeNotification = CFSTR("WebDatabaseOriginsDidChangeNotification"); 60#endif 61 62static NSString *databasesDirectoryPath(); 63 64@implementation WebDatabaseManager 65 66+ (WebDatabaseManager *) sharedWebDatabaseManager 67{ 68 static WebDatabaseManager *sharedManager = [[WebDatabaseManager alloc] init]; 69 return sharedManager; 70} 71 72- (id)init 73{ 74 if (!(self = [super init])) 75 return nil; 76 77 WebPlatformStrategies::initializeIfNecessary(); 78 79 DatabaseManager& dbManager = DatabaseManager::manager(); 80 81 // Set the database root path in WebCore 82 dbManager.initialize(databasesDirectoryPath()); 83 84 // Set the DatabaseManagerClient 85 dbManager.setClient(WebDatabaseManagerClient::sharedWebDatabaseManagerClient()); 86 87 return self; 88} 89 90- (NSArray *)origins 91{ 92 Vector<RefPtr<SecurityOrigin>> coreOrigins; 93 DatabaseManager::manager().origins(coreOrigins); 94 NSMutableArray *webOrigins = [[NSMutableArray alloc] initWithCapacity:coreOrigins.size()]; 95 96 for (unsigned i = 0; i < coreOrigins.size(); ++i) { 97 WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:coreOrigins[i].get()]; 98 [webOrigins addObject:webOrigin]; 99 [webOrigin release]; 100 } 101 102 return [webOrigins autorelease]; 103} 104 105- (NSArray *)databasesWithOrigin:(WebSecurityOrigin *)origin 106{ 107 Vector<String> nameVector; 108 if (!DatabaseManager::manager().databaseNamesForOrigin([origin _core], nameVector)) 109 return nil; 110 111 NSMutableArray *names = [[NSMutableArray alloc] initWithCapacity:nameVector.size()]; 112 113 for (unsigned i = 0; i < nameVector.size(); ++i) 114 [names addObject:(NSString *)nameVector[i]]; 115 116 return [names autorelease]; 117} 118 119- (NSDictionary *)detailsForDatabase:(NSString *)databaseIdentifier withOrigin:(WebSecurityOrigin *)origin 120{ 121 static id keys[3] = {WebDatabaseDisplayNameKey, WebDatabaseExpectedSizeKey, WebDatabaseUsageKey}; 122 123 DatabaseDetails details = DatabaseManager::manager().detailsForNameAndOrigin(databaseIdentifier, [origin _core]); 124 if (details.name().isNull()) 125 return nil; 126 127 id objects[3]; 128 objects[0] = details.displayName().isEmpty() ? databaseIdentifier : (NSString *)details.displayName(); 129 objects[1] = [NSNumber numberWithUnsignedLongLong:details.expectedUsage()]; 130 objects[2] = [NSNumber numberWithUnsignedLongLong:details.currentUsage()]; 131 132 return [[[NSDictionary alloc] initWithObjects:objects forKeys:keys count:3] autorelease]; 133} 134 135- (void)deleteAllDatabases 136{ 137 DatabaseManager::manager().deleteAllDatabases(); 138#if PLATFORM(IOS) 139 // FIXME: This needs to be removed once DatabaseTrackers in multiple processes 140 // are in sync: <rdar://problem/9567500> Remove Website Data pane is not kept in sync with Safari 141 [[NSFileManager defaultManager] removeItemAtPath:databasesDirectoryPath() error:NULL]; 142#endif 143} 144 145- (BOOL)deleteOrigin:(WebSecurityOrigin *)origin 146{ 147 return DatabaseManager::manager().deleteOrigin([origin _core]); 148} 149 150- (BOOL)deleteDatabase:(NSString *)databaseIdentifier withOrigin:(WebSecurityOrigin *)origin 151{ 152 return DatabaseManager::manager().deleteDatabase([origin _core], databaseIdentifier); 153} 154 155#if PLATFORM(IOS) 156static bool isFileHidden(NSString *file) 157{ 158 ASSERT([file length]); 159 return [file characterAtIndex:0] == '.'; 160} 161 162+ (void)removeEmptyDatabaseFiles 163{ 164 NSString *databasesDirectory = databasesDirectoryPath(); 165 NSFileManager *fileManager = [NSFileManager defaultManager]; 166 NSArray *array = [fileManager contentsOfDirectoryAtPath:databasesDirectory error:0]; 167 if (!array) 168 return; 169 170 NSUInteger count = [array count]; 171 for (NSUInteger i = 0; i < count; ++i) { 172 NSString *fileName = [array objectAtIndex:i]; 173 // Skip hidden files. 174 if (![fileName length] || isFileHidden(fileName)) 175 continue; 176 177 NSString *path = [databasesDirectory stringByAppendingPathComponent:fileName]; 178 // Look for directories that contain database files belonging to the same origins. 179 BOOL isDirectory; 180 if (![fileManager fileExistsAtPath:path isDirectory:&isDirectory] || !isDirectory) 181 continue; 182 183 // Make sure the directory is not a symbolic link that points to something else. 184 NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:0]; 185 if ([attributes fileType] == NSFileTypeSymbolicLink) 186 continue; 187 188 NSArray *databaseFilesInOrigin = [fileManager contentsOfDirectoryAtPath:path error:0]; 189 NSUInteger databaseFileCount = [databaseFilesInOrigin count]; 190 NSUInteger deletedDatabaseFileCount = 0; 191 for (NSUInteger j = 0; j < databaseFileCount; ++j) { 192 NSString *dbFileName = [databaseFilesInOrigin objectAtIndex:j]; 193 // Skip hidden files. 194 if (![dbFileName length] || isFileHidden(dbFileName)) 195 continue; 196 197 NSString *dbFilePath = [path stringByAppendingPathComponent:dbFileName]; 198 199 // There shouldn't be any directories in this folder - but check for it anyway. 200 if (![fileManager fileExistsAtPath:dbFilePath isDirectory:&isDirectory] || isDirectory) 201 continue; 202 203 if (DatabaseTracker::deleteDatabaseFileIfEmpty(dbFilePath)) 204 ++deletedDatabaseFileCount; 205 } 206 207 // If we have removed every database file for this origin, delete the folder for this origin. 208 if (databaseFileCount == deletedDatabaseFileCount) { 209 // Use rmdir - we don't want the deletion to happen if the folder is not empty. 210 rmdir([path fileSystemRepresentation]); 211 } 212 } 213} 214 215+ (void)scheduleEmptyDatabaseRemoval 216{ 217 DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled(); 218 219 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 220 [WebDatabaseManager removeEmptyDatabaseFiles]; 221 DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish(); 222 }); 223} 224#endif // PLATFORM(IOS) 225 226@end 227 228#if PLATFORM(IOS) 229@implementation WebDatabaseManager (WebDatabaseManagerInternal) 230 231static Mutex& transactionBackgroundTaskIdentifierLock() 232{ 233 DEPRECATED_DEFINE_STATIC_LOCAL(Mutex, mutex, ()); 234 return mutex; 235} 236 237static WebBackgroundTaskIdentifier transactionBackgroundTaskIdentifier; 238 239static void setTransactionBackgroundTaskIdentifier(WebBackgroundTaskIdentifier identifier) 240{ 241 transactionBackgroundTaskIdentifier = identifier; 242} 243 244static WebBackgroundTaskIdentifier getTransactionBackgroundTaskIdentifier() 245{ 246 static dispatch_once_t pred; 247 dispatch_once(&pred, ^{ 248 setTransactionBackgroundTaskIdentifier(invalidWebBackgroundTaskIdentifier()); 249 }); 250 251 return transactionBackgroundTaskIdentifier; 252} 253 254+ (void)willBeginFirstTransaction 255{ 256 [self startBackgroundTask]; 257} 258 259+ (void)didFinishLastTransaction 260{ 261 [self endBackgroundTask]; 262} 263 264+ (void)startBackgroundTask 265{ 266 MutexLocker lock(transactionBackgroundTaskIdentifierLock()); 267 268 // If there's already an existing background task going on, there's no need to start a new one. 269 if (getTransactionBackgroundTaskIdentifier() != invalidWebBackgroundTaskIdentifier()) 270 return; 271 272 setTransactionBackgroundTaskIdentifier(startBackgroundTask(^ { [WebDatabaseManager endBackgroundTask]; })); 273} 274 275+ (void)endBackgroundTask 276{ 277 MutexLocker lock(transactionBackgroundTaskIdentifierLock()); 278 279 // It is possible that we were unable to start the background task when the first transaction began. 280 // Don't try to end the task in that case. 281 // It is also possible we finally finish the last transaction right when the background task expires 282 // and this will end up being called twice for the same background task. transactionBackgroundTaskIdentifier 283 // will be invalid for the second caller. 284 if (getTransactionBackgroundTaskIdentifier() == invalidWebBackgroundTaskIdentifier()) 285 return; 286 287 endBackgroundTask(getTransactionBackgroundTaskIdentifier()); 288 setTransactionBackgroundTaskIdentifier(invalidWebBackgroundTaskIdentifier()); 289} 290 291@end 292 293void WebKitSetWebDatabasePaused(bool paused) 294{ 295 DatabaseTracker::tracker().setDatabasesPaused(paused); 296} 297#endif // PLATFORM(IOS) 298 299static NSString *databasesDirectoryPath() 300{ 301 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 302 NSString *databasesDirectory = [defaults objectForKey:WebDatabaseDirectoryDefaultsKey]; 303 if (!databasesDirectory || ![databasesDirectory isKindOfClass:[NSString class]]) 304 databasesDirectory = @"~/Library/WebKit/Databases"; 305 306 return [databasesDirectory stringByStandardizingPath]; 307} 308 309#endif 310