Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebKitLegacy / 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/DatabaseTracker.h>
37 #import <WebCore/SecurityOrigin.h>
38
39 #if ENABLE(INDEXED_DATABASE)
40 #import "WebDatabaseProvider.h"
41 #endif
42
43 #if PLATFORM(IOS)
44 #import "WebDatabaseManagerInternal.h"
45 #import <WebCore/DatabaseTracker.h>
46 #import <WebCore/WebCoreThread.h>
47 #endif
48
49 using namespace WebCore;
50
51 NSString *WebDatabaseDirectoryDefaultsKey = @"WebDatabaseDirectory";
52
53 NSString *WebDatabaseDisplayNameKey = @"WebDatabaseDisplayNameKey";
54 NSString *WebDatabaseExpectedSizeKey = @"WebDatabaseExpectedSizeKey";
55 NSString *WebDatabaseUsageKey = @"WebDatabaseUsageKey";
56
57 NSString *WebDatabaseDidModifyOriginNotification = @"WebDatabaseDidModifyOriginNotification";
58 NSString *WebDatabaseDidModifyDatabaseNotification = @"WebDatabaseDidModifyDatabaseNotification";
59 NSString *WebDatabaseIdentifierKey = @"WebDatabaseIdentifierKey";
60
61 #if PLATFORM(IOS)
62 CFStringRef WebDatabaseOriginsDidChangeNotification = CFSTR("WebDatabaseOriginsDidChangeNotification");
63 #endif
64
65 static NSString *databasesDirectoryPath();
66
67 @implementation WebDatabaseManager
68
69 + (WebDatabaseManager *) sharedWebDatabaseManager
70 {
71     static WebDatabaseManager *sharedManager = [[WebDatabaseManager alloc] init];
72     return sharedManager;
73 }
74
75 - (id)init
76 {
77     if (!(self = [super init]))
78         return nil;
79
80     WebPlatformStrategies::initializeIfNecessary();
81
82     DatabaseManager& dbManager = DatabaseManager::singleton();
83
84     // Set the database root path in WebCore
85     dbManager.initialize(databasesDirectoryPath());
86
87     // Set the DatabaseManagerClient
88     dbManager.setClient(WebDatabaseManagerClient::sharedWebDatabaseManagerClient());
89
90     return self;
91 }
92
93 - (NSArray *)origins
94 {
95     auto coreOrigins = DatabaseTracker::singleton().origins();
96     NSMutableArray *webOrigins = [[NSMutableArray alloc] initWithCapacity:coreOrigins.size()];
97     for (auto& coreOrigin : coreOrigins) {
98         WebSecurityOrigin *webOrigin = [[WebSecurityOrigin alloc] _initWithWebCoreSecurityOrigin:coreOrigin.securityOrigin().ptr()];
99         [webOrigins addObject:webOrigin];
100         [webOrigin release];
101     }
102     return [webOrigins autorelease];
103 }
104
105 - (NSArray *)databasesWithOrigin:(WebSecurityOrigin *)origin
106 {
107     if (!origin)
108         return nil;
109     Vector<String> nameVector = DatabaseTracker::singleton().databaseNames(SecurityOriginData::fromSecurityOrigin(*[origin _core]));
110     NSMutableArray *names = [[NSMutableArray alloc] initWithCapacity:nameVector.size()];
111     for (auto& name : nameVector)
112         [names addObject:(NSString *)name];
113     return [names autorelease];
114 }
115
116 - (NSDictionary *)detailsForDatabase:(NSString *)databaseIdentifier withOrigin:(WebSecurityOrigin *)origin
117 {
118     if (!origin)
119         return nil;
120
121     DatabaseDetails details = DatabaseManager::singleton().detailsForNameAndOrigin(databaseIdentifier, *[origin _core]);
122     if (details.name().isNull())
123         return nil;
124         
125     static const id keys[3] = { WebDatabaseDisplayNameKey, WebDatabaseExpectedSizeKey, WebDatabaseUsageKey };
126     id objects[3];
127     objects[0] = details.displayName().isEmpty() ? databaseIdentifier : (NSString *)details.displayName();
128     objects[1] = [NSNumber numberWithUnsignedLongLong:details.expectedUsage()];
129     objects[2] = [NSNumber numberWithUnsignedLongLong:details.currentUsage()];
130     return [[[NSDictionary alloc] initWithObjects:objects forKeys:keys count:3] autorelease];
131 }
132
133 - (void)deleteAllDatabases
134 {
135     DatabaseTracker::singleton().deleteAllDatabasesImmediately();
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 origin && DatabaseTracker::singleton().deleteOrigin(SecurityOriginData::fromSecurityOrigin(*[origin _core]));
146 }
147
148 - (BOOL)deleteDatabase:(NSString *)databaseIdentifier withOrigin:(WebSecurityOrigin *)origin
149 {
150     return origin && DatabaseTracker::singleton().deleteDatabase(SecurityOriginData::fromSecurityOrigin(*[origin _core]), databaseIdentifier);
151 }
152
153 // For DumpRenderTree support only
154 - (void)deleteAllIndexedDatabases
155 {
156 #if ENABLE(INDEXED_DATABASE)
157     WebDatabaseProvider::singleton().deleteAllDatabases();
158 #endif
159 }
160
161 #if PLATFORM(IOS)
162
163 static bool isFileHidden(NSString *file)
164 {
165     ASSERT([file length]);
166     return [file characterAtIndex:0] == '.';
167 }
168
169 + (void)removeEmptyDatabaseFiles
170 {
171     NSString *databasesDirectory = databasesDirectoryPath();
172     NSFileManager *fileManager = [NSFileManager defaultManager];
173     NSArray *array = [fileManager contentsOfDirectoryAtPath:databasesDirectory error:0];
174     if (!array)
175         return;
176     
177     NSUInteger count = [array count];
178     for (NSUInteger i = 0; i < count; ++i) {
179         NSString *fileName = [array objectAtIndex:i];
180         // Skip hidden files.
181         if (![fileName length] || isFileHidden(fileName))
182             continue;
183         
184         NSString *path = [databasesDirectory stringByAppendingPathComponent:fileName];
185         // Look for directories that contain database files belonging to the same origins.
186         BOOL isDirectory;
187         if (![fileManager fileExistsAtPath:path isDirectory:&isDirectory] || !isDirectory)
188             continue;
189         
190         // Make sure the directory is not a symbolic link that points to something else.
191         NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:0];
192         if ([attributes fileType] == NSFileTypeSymbolicLink)
193             continue;
194         
195         NSArray *databaseFilesInOrigin = [fileManager contentsOfDirectoryAtPath:path error:0];
196         NSUInteger databaseFileCount = [databaseFilesInOrigin count];
197         NSUInteger deletedDatabaseFileCount = 0;
198         for (NSUInteger j = 0; j < databaseFileCount; ++j) {
199             NSString *dbFileName = [databaseFilesInOrigin objectAtIndex:j];
200             // Skip hidden files.
201             if (![dbFileName length] || isFileHidden(dbFileName))
202                 continue;
203             
204             NSString *dbFilePath = [path stringByAppendingPathComponent:dbFileName];
205             
206             // There shouldn't be any directories in this folder - but check for it anyway.
207             if (![fileManager fileExistsAtPath:dbFilePath isDirectory:&isDirectory] || isDirectory)
208                 continue;
209             
210             if (DatabaseTracker::deleteDatabaseFileIfEmpty(dbFilePath))
211                 ++deletedDatabaseFileCount;
212         }
213         
214         // If we have removed every database file for this origin, delete the folder for this origin.
215         if (databaseFileCount == deletedDatabaseFileCount || ![fileManager contentsOfDirectoryAtPath:path error:nullptr].count) {
216             // Use rmdir - we don't want the deletion to happen if the folder is not empty.
217             rmdir([path fileSystemRepresentation]);
218         }
219     }
220 }
221
222 + (void)scheduleEmptyDatabaseRemoval
223 {
224     DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled();
225     
226     dispatch_async(dispatch_get_global_queue(0, 0), ^{
227         [WebDatabaseManager removeEmptyDatabaseFiles];
228         DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish();
229     });
230 }
231
232 #endif // PLATFORM(IOS)
233
234 @end
235
236 static NSString *databasesDirectoryPath()
237 {
238     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
239     NSString *databasesDirectory = [defaults objectForKey:WebDatabaseDirectoryDefaultsKey];
240     if (!databasesDirectory || ![databasesDirectory isKindOfClass:[NSString class]])
241         databasesDirectory = @"~/Library/WebKit/Databases";
242     
243     return [databasesDirectory stringByStandardizingPath];
244 }