[iOS] Upstream PLATFORM(IOS) changes to Source/WebKit/
[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 Computer, 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
46 using namespace WebCore;
47
48 NSString *WebDatabaseDirectoryDefaultsKey = @"WebDatabaseDirectory";
49
50 NSString *WebDatabaseDisplayNameKey = @"WebDatabaseDisplayNameKey";
51 NSString *WebDatabaseExpectedSizeKey = @"WebDatabaseExpectedSizeKey";
52 NSString *WebDatabaseUsageKey = @"WebDatabaseUsageKey";
53
54 NSString *WebDatabaseDidModifyOriginNotification = @"WebDatabaseDidModifyOriginNotification";
55 NSString *WebDatabaseDidModifyDatabaseNotification = @"WebDatabaseDidModifyDatabaseNotification";
56 NSString *WebDatabaseIdentifierKey = @"WebDatabaseIdentifierKey";
57
58 #if PLATFORM(IOS)
59 CFStringRef WebDatabaseOriginsDidChangeNotification = CFSTR("WebDatabaseOriginsDidChangeNotification");
60 #endif
61
62 static 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)
156 static 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
231 static Mutex& transactionBackgroundTaskIdentifierLock()
232 {
233     DEFINE_STATIC_LOCAL(Mutex, mutex, ());
234     return mutex;
235 }
236
237 static WebBackgroundTaskIdentifier transactionBackgroundTaskIdentifier;
238
239 static void setTransactionBackgroundTaskIdentifier(WebBackgroundTaskIdentifier identifier)
240 {
241     transactionBackgroundTaskIdentifier = identifier;
242 }
243
244 static 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
293 void WebKitSetWebDatabasePaused(bool paused)
294 {
295     DatabaseTracker::tracker().setDatabasesPaused(paused);
296 }
297 #endif // PLATFORM(IOS)
298
299 static 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