ac12b14998cfbb2d5da784bb26697a3ad6d16d05
[WebKit-https.git] / Source / WebKit / mac / Storage / WebDatabaseManager.mm
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 #import "WebDatabaseManagerClient.h"
32 #import "WebPlatformStrategies.h"
33 #import "WebSecurityOriginInternal.h"
34
35 #import <WebCore/DatabaseManager.h>
36 #import <WebCore/SecurityOrigin.h>
37
38 #if PLATFORM(IOS)
39 #import "WebDatabaseManagerInternal.h"
40 #import <WebCore/DatabaseTracker.h>
41 #import <WebCore/WebCoreThread.h>
42 #endif
43
44 using namespace WebCore;
45
46 NSString *WebDatabaseDirectoryDefaultsKey = @"WebDatabaseDirectory";
47
48 NSString *WebDatabaseDisplayNameKey = @"WebDatabaseDisplayNameKey";
49 NSString *WebDatabaseExpectedSizeKey = @"WebDatabaseExpectedSizeKey";
50 NSString *WebDatabaseUsageKey = @"WebDatabaseUsageKey";
51
52 NSString *WebDatabaseDidModifyOriginNotification = @"WebDatabaseDidModifyOriginNotification";
53 NSString *WebDatabaseDidModifyDatabaseNotification = @"WebDatabaseDidModifyDatabaseNotification";
54 NSString *WebDatabaseIdentifierKey = @"WebDatabaseIdentifierKey";
55
56 #if PLATFORM(IOS)
57 CFStringRef WebDatabaseOriginsDidChangeNotification = CFSTR("WebDatabaseOriginsDidChangeNotification");
58 #endif
59
60 static NSString *databasesDirectoryPath();
61
62 @implementation WebDatabaseManager
63
64 + (WebDatabaseManager *) sharedWebDatabaseManager
65 {
66     static WebDatabaseManager *sharedManager = [[WebDatabaseManager alloc] init];
67     return sharedManager;
68 }
69
70 - (id)init
71 {
72     if (!(self = [super init]))
73         return nil;
74
75     WebPlatformStrategies::initializeIfNecessary();
76
77     DatabaseManager& dbManager = DatabaseManager::singleton();
78
79     // Set the database root path in WebCore
80     dbManager.initialize(databasesDirectoryPath());
81
82     // Set the DatabaseManagerClient
83     dbManager.setClient(WebDatabaseManagerClient::sharedWebDatabaseManagerClient());
84
85     return self;
86 }
87
88 - (NSArray *)origins
89 {
90     Vector<RefPtr<SecurityOrigin>> coreOrigins;
91     DatabaseManager::singleton().origins(coreOrigins);
92     NSMutableArray *webOrigins = [[NSMutableArray alloc] initWithCapacity:coreOrigins.size()];
93
94     for (unsigned i = 0; i < coreOrigins.size(); ++i) {
95         WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:coreOrigins[i].get()];
96         [webOrigins addObject:webOrigin];
97         [webOrigin release];
98     }
99
100     return [webOrigins autorelease];
101 }
102
103 - (NSArray *)databasesWithOrigin:(WebSecurityOrigin *)origin
104 {
105     Vector<String> nameVector;
106     if (!DatabaseManager::singleton().databaseNamesForOrigin([origin _core], nameVector))
107         return nil;
108     
109     NSMutableArray *names = [[NSMutableArray alloc] initWithCapacity:nameVector.size()];
110
111     for (unsigned i = 0; i < nameVector.size(); ++i)
112         [names addObject:(NSString *)nameVector[i]];
113
114     return [names autorelease];
115 }
116
117 - (NSDictionary *)detailsForDatabase:(NSString *)databaseIdentifier withOrigin:(WebSecurityOrigin *)origin
118 {
119     static id keys[3] = {WebDatabaseDisplayNameKey, WebDatabaseExpectedSizeKey, WebDatabaseUsageKey};
120     
121     DatabaseDetails details = DatabaseManager::singleton().detailsForNameAndOrigin(databaseIdentifier, [origin _core]);
122     if (details.name().isNull())
123         return nil;
124         
125     id objects[3];
126     objects[0] = details.displayName().isEmpty() ? databaseIdentifier : (NSString *)details.displayName();
127     objects[1] = [NSNumber numberWithUnsignedLongLong:details.expectedUsage()];
128     objects[2] = [NSNumber numberWithUnsignedLongLong:details.currentUsage()];
129     
130     return [[[NSDictionary alloc] initWithObjects:objects forKeys:keys count:3] autorelease];
131 }
132
133 - (void)deleteAllDatabases
134 {
135     DatabaseManager::singleton().deleteAllDatabases();
136 #if PLATFORM(IOS)
137     // FIXME: This needs to be removed once DatabaseTrackers in multiple processes
138     // are in sync: <rdar://problem/9567500> Remove Website Data pane is not kept in sync with Safari
139     [[NSFileManager defaultManager] removeItemAtPath:databasesDirectoryPath() error:NULL];
140 #endif
141 }
142
143 - (BOOL)deleteOrigin:(WebSecurityOrigin *)origin
144 {
145     return DatabaseManager::singleton().deleteOrigin([origin _core]);
146 }
147
148 - (BOOL)deleteDatabase:(NSString *)databaseIdentifier withOrigin:(WebSecurityOrigin *)origin
149 {
150     return DatabaseManager::singleton().deleteDatabase([origin _core], databaseIdentifier);
151 }
152
153 #if PLATFORM(IOS)
154 static bool isFileHidden(NSString *file)
155 {
156     ASSERT([file length]);
157     return [file characterAtIndex:0] == '.';
158 }
159
160 + (void)removeEmptyDatabaseFiles
161 {
162     NSString *databasesDirectory = databasesDirectoryPath();
163     NSFileManager *fileManager = [NSFileManager defaultManager];
164     NSArray *array = [fileManager contentsOfDirectoryAtPath:databasesDirectory error:0];
165     if (!array)
166         return;
167     
168     NSUInteger count = [array count];
169     for (NSUInteger i = 0; i < count; ++i) {
170         NSString *fileName = [array objectAtIndex:i];
171         // Skip hidden files.
172         if (![fileName length] || isFileHidden(fileName))
173             continue;
174         
175         NSString *path = [databasesDirectory stringByAppendingPathComponent:fileName];
176         // Look for directories that contain database files belonging to the same origins.
177         BOOL isDirectory;
178         if (![fileManager fileExistsAtPath:path isDirectory:&isDirectory] || !isDirectory)
179             continue;
180         
181         // Make sure the directory is not a symbolic link that points to something else.
182         NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:0];
183         if ([attributes fileType] == NSFileTypeSymbolicLink)
184             continue;
185         
186         NSArray *databaseFilesInOrigin = [fileManager contentsOfDirectoryAtPath:path error:0];
187         NSUInteger databaseFileCount = [databaseFilesInOrigin count];
188         NSUInteger deletedDatabaseFileCount = 0;
189         for (NSUInteger j = 0; j < databaseFileCount; ++j) {
190             NSString *dbFileName = [databaseFilesInOrigin objectAtIndex:j];
191             // Skip hidden files.
192             if (![dbFileName length] || isFileHidden(dbFileName))
193                 continue;
194             
195             NSString *dbFilePath = [path stringByAppendingPathComponent:dbFileName];
196             
197             // There shouldn't be any directories in this folder - but check for it anyway.
198             if (![fileManager fileExistsAtPath:dbFilePath isDirectory:&isDirectory] || isDirectory)
199                 continue;
200             
201             if (DatabaseTracker::deleteDatabaseFileIfEmpty(dbFilePath))
202                 ++deletedDatabaseFileCount;
203         }
204         
205         // If we have removed every database file for this origin, delete the folder for this origin.
206         if (databaseFileCount == deletedDatabaseFileCount) {
207             // Use rmdir - we don't want the deletion to happen if the folder is not empty.
208             rmdir([path fileSystemRepresentation]);
209         }
210     }
211 }
212
213 + (void)scheduleEmptyDatabaseRemoval
214 {
215     DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled();
216     
217     dispatch_async(dispatch_get_global_queue(0, 0), ^{
218         [WebDatabaseManager removeEmptyDatabaseFiles];
219         DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish();
220     });
221 }
222 #endif // PLATFORM(IOS)
223
224 @end
225
226 #if PLATFORM(IOS)
227 @implementation WebDatabaseManager (WebDatabaseManagerInternal)
228
229 static DeprecatedMutex& transactionBackgroundTaskIdentifierLock()
230 {
231     DEPRECATED_DEFINE_STATIC_LOCAL(DeprecatedMutex, mutex, ());
232     return mutex;
233 }
234
235 static WebBackgroundTaskIdentifier transactionBackgroundTaskIdentifier;
236
237 static void setTransactionBackgroundTaskIdentifier(WebBackgroundTaskIdentifier identifier)
238 {
239     transactionBackgroundTaskIdentifier = identifier;
240 }
241
242 static WebBackgroundTaskIdentifier getTransactionBackgroundTaskIdentifier()
243 {
244     static dispatch_once_t pred;
245     dispatch_once(&pred, ^{
246         setTransactionBackgroundTaskIdentifier(invalidWebBackgroundTaskIdentifier());
247     });
248     
249     return transactionBackgroundTaskIdentifier;
250 }
251
252 + (void)willBeginFirstTransaction
253 {
254     [self startBackgroundTask];
255 }
256
257 + (void)didFinishLastTransaction
258 {
259     [self endBackgroundTask];
260 }
261
262 + (void)startBackgroundTask
263 {
264     DeprecatedMutexLocker lock(transactionBackgroundTaskIdentifierLock());
265
266     // If there's already an existing background task going on, there's no need to start a new one.
267     if (getTransactionBackgroundTaskIdentifier() != invalidWebBackgroundTaskIdentifier())
268         return;
269     
270     setTransactionBackgroundTaskIdentifier(startBackgroundTask(^ {
271         DatabaseTracker::tracker().closeAllDatabases();
272         [WebDatabaseManager endBackgroundTask];
273     }));
274 }
275
276 + (void)endBackgroundTask
277 {
278     DeprecatedMutexLocker lock(transactionBackgroundTaskIdentifierLock());
279
280     // It is possible that we were unable to start the background task when the first transaction began.
281     // Don't try to end the task in that case.
282     // It is also possible we finally finish the last transaction right when the background task expires
283     // and this will end up being called twice for the same background task.  transactionBackgroundTaskIdentifier
284     // will be invalid for the second caller.
285     if (getTransactionBackgroundTaskIdentifier() == invalidWebBackgroundTaskIdentifier())
286         return;
287         
288     endBackgroundTask(getTransactionBackgroundTaskIdentifier());
289     setTransactionBackgroundTaskIdentifier(invalidWebBackgroundTaskIdentifier());
290 }
291
292 @end
293
294 #endif // PLATFORM(IOS)
295
296 static NSString *databasesDirectoryPath()
297 {
298     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
299     NSString *databasesDirectory = [defaults objectForKey:WebDatabaseDirectoryDefaultsKey];
300     if (!databasesDirectory || ![databasesDirectory isKindOfClass:[NSString class]])
301         databasesDirectory = @"~/Library/WebKit/Databases";
302     
303     return [databasesDirectory stringByStandardizingPath];
304 }