OSDN Git Service

23acb2a740f6dc14109b34b758bf00888971530f
[tombo/Tombo.git] / iOS / Tombo / Tombo / Storage.m
1 #import "Storage.h"
2 #import "FileItem.h"
3 #import "CryptCore.h"
4
5 @implementation Storage
6
7 @synthesize currentDirectory;
8 @synthesize documentRoot;
9 @synthesize fileManager;
10
11 +(id)init {
12     Storage *storage = [Storage alloc];
13     storage.currentDirectory = @"/";
14
15     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
16     storage.documentRoot = [paths objectAtIndex:0];
17
18     storage.fileManager = [NSFileManager defaultManager];
19
20     return storage;
21 }
22
23 -(NSArray*)listItems {
24     NSError *error = nil;
25     NSString *currentPath = [documentRoot stringByAppendingString: currentDirectory];
26     NSArray *files = [fileManager contentsOfDirectoryAtPath: currentPath
27                                                       error: &error];
28     if (error) {
29         return [NSArray alloc];
30     }
31     
32     NSMutableArray *result = [NSMutableArray arrayWithCapacity: files.count];
33     for (NSString *f in files) {
34         FileItem *item = [FileItem alloc];
35         item.path = [currentPath stringByAppendingString: f];
36         item.name = [f stringByDeletingPathExtension];
37                 
38         BOOL bDir = NO;
39         [fileManager fileExistsAtPath:item.path isDirectory:&bDir];
40         item.isDirectory = bDir;
41         
42         if (!bDir) {
43             if ([@"chi" isEqualToString:[f pathExtension]]) {
44                 item.isCrypt = YES;
45             }
46         }
47         
48         [result addObject:item];
49     }
50     return result;
51 }
52
53 -(void)chdir:(NSString *)subdir {
54     NSString *newCurrent = [currentDirectory stringByAppendingPathComponent:subdir];
55     self.currentDirectory = [newCurrent stringByAppendingString:@"/"];
56 }
57
58 -(void)updir {
59     NSString *parent = [currentDirectory stringByDeletingLastPathComponent];
60     if ([parent isEqualToString:@"/"]) {
61         self.currentDirectory = parent;
62     } else {
63         self.currentDirectory = [parent stringByAppendingString:@"/"];        
64     }
65 }
66
67 -(BOOL)isTopDir {
68     return [currentDirectory isEqualToString:@"/"];
69 }
70
71 - (void)saveDataWithBOM:(NSString *)note file:(NSString *)path {
72     const char *noteBytes = [note cStringUsingEncoding:NSUTF8StringEncoding];
73     int n = strlen(noteBytes);
74     NSMutableData *data = [[NSMutableData alloc] initWithLength:n + 3];
75     char *buf = (char *)[data mutableBytes];
76     memcpy(buf + 3, noteBytes, n);
77     *(char*)(buf + 0) = 0xEF;
78     *(char*)(buf + 1) = 0xBB;
79     *(char*)(buf + 2) = 0xBF;
80     
81     [data writeToFile:path atomically:YES];
82 }
83
84 - (NSString *)pickupTitle:(NSString *)note {
85     NSRange r;
86     r.location = 0;
87     r.length = 0;
88     NSRange titleRange = [note lineRangeForRange:r];
89     NSString *title = [note substringWithRange:titleRange];
90     
91     if ([title characterAtIndex:(title.length - 1)] == '\n') {
92         title = [title substringToIndex:(title.length - 1)];
93     }
94     if (title.length == 0) {
95         title = @"New document";
96     }
97     return title;
98 }
99
100 - (FileItem *)savePlain:(NSString *)note item:(FileItem *)item {
101     if (!item) return nil;
102
103     NSString *title = [self pickupTitle:note];
104
105     // If title is changed, rename one.
106     FileItem *result;    
107     if (!item.name || ![title isEqualToString: item.name]) {        
108         result = [self decideFileName:title path:item.path];
109         
110         if (item.name) {
111             // If it is not new item, needs rename.
112             NSError *error = nil;
113             [fileManager moveItemAtPath:item.path toPath:result.path error:&error];
114         }
115     } else {
116         result = item;
117     }
118
119     // Save note.
120     [self saveDataWithBOM:note file:result.path];
121     
122     return result;
123 }
124
125 - (FileItem *)saveCrypt:(NSString *)note item:(FileItem *)item password:(NSString *)password {
126     NSString *title = [self pickupTitle:note];
127     
128     FileItem *result;
129     if (!item.name || ![title isEqualToString: item.name]) {
130         result = [self decideFileName:title path:item.path];
131         
132         if (item.name) {
133             NSError *error = nil;
134             [fileManager moveItemAtPath:item.path toPath:result.path error:&error];            
135         }
136     } else {
137         result = item;
138     }
139     
140     // Save note.
141     const char *noteBytes = [note cStringUsingEncoding:NSUTF8StringEncoding];
142     int n = strlen(noteBytes);
143     NSMutableData *data = [[NSMutableData alloc] initWithLength:n + 3];
144     char *buf = (char *)[data mutableBytes];
145     memcpy(buf + 3, noteBytes, n);
146     *(char*)(buf + 0) = 0xEF;
147     *(char*)(buf + 1) = 0xBB;
148     *(char*)(buf + 2) = 0xBF;
149
150     NSError *error = nil;
151     NSData *encData = [CryptCore encrypt:password data:data error:&error];
152     if (error) return nil;
153     
154     if (![encData writeToFile:result.path atomically:YES]) return nil;
155     return result;
156 }
157
158 // Remove characters which can't use file name from given string.
159 - (NSString *)removeInvalidFilenameChars:(NSString *)src {
160     NSString *result = src;
161     // chars are same as Tombo for Windows.
162     NSArray *chars = [[NSArray alloc] initWithObjects:@"\\", @"/", @":", @",", @";", @"*", @"?", @"<", @">", @"\"", @"\t", nil];
163     
164     for (NSString *t in chars) {
165         result = [result stringByReplacingOccurrencesOfString:t withString:@""];
166     }
167     return result;
168 }
169
170 - (FileItem *)decideFileName:(NSString *)titleCand path:(NSString *)origPath {
171     FileItem *result = [FileItem alloc];
172
173     NSString *ext = [origPath pathExtension];
174     
175     NSMutableString *path = [NSMutableString stringWithCapacity:256];
176     [path appendString:[origPath stringByDeletingLastPathComponent]];
177     [path appendString:@"/"];
178     [path appendString:[self removeInvalidFilenameChars:titleCand]];
179     NSUInteger n = [path length];
180     [path appendString:@"."];
181     [path appendString:ext];
182     NSUInteger u = [path length];
183     
184     if ([fileManager fileExistsAtPath:path]) {
185         NSUInteger cnt = 1;
186         while(YES) {
187             NSRange r;
188             r.location = n;
189             r.length = u - n;
190             [path deleteCharactersInRange:r];
191             [path appendFormat:@"(%d)", cnt];
192             [path appendString:@"."];
193             [path appendString:ext];
194             u = [path length];
195             
196             if (![fileManager fileExistsAtPath:path]) break;
197             cnt++;
198         }
199         
200         result.name = [[path lastPathComponent] stringByDeletingPathExtension];
201     } else {
202         result.name = titleCand;
203     }
204     result.path = path;            
205
206     return result;
207 }
208
209 - (FileItem *)newItem {
210     FileItem *p = [FileItem allocWithName: nil];
211     p.path = [documentRoot stringByAppendingString:@"/_dummy.txt"];
212     return p;
213 }
214
215 - (void)deleteItem:(FileItem*)item {
216     [fileManager removeItemAtPath:item.path error:nil];
217 }
218
219 - (FileItem *)newFolder:(NSString *)folder {
220     NSMutableString *path = [[NSMutableString alloc]initWithCapacity:256];
221     [path appendString:documentRoot];
222     [path appendString:currentDirectory];
223     [path appendString:folder];
224     NSError *error = nil;
225     [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
226     
227     FileItem *item = [FileItem allocWithName:folder];
228     item.path = path;
229     item.isDirectory = YES;
230     return item;
231 }
232
233 +(NSString *)load:(NSString *)path {
234     NSData *data = [NSData dataWithContentsOfFile:path];
235     return [Storage trimBOM:data];
236 }
237
238 +(NSString *)trimBOM:(NSData *)data {
239     const char *header = [data bytes];
240     if ([data length] > 3 && 
241         *header == 0xEF && *(header + 1) == 0xBB && *(header + 2) == 0xBF) {
242         // BOM exists. UTF-8.
243         NSString *note = [[NSString alloc] initWithBytes:[data bytes] + 3
244                                                   length:[data length] - 3
245                                                 encoding:NSUTF8StringEncoding];
246         return note;
247     }
248
249     NSString *note;
250     
251     if ([[[NSLocale currentLocale] localeIdentifier] isEqualToString:@"ja_JP"]) {
252         note = [[NSString alloc] initWithBytes:[data bytes]
253                                         length:[data length]
254                                       encoding:NSShiftJISStringEncoding];
255         if (note) return note;
256     }
257
258     // UTF-8
259     note = [[NSString alloc] initWithBytes:[data bytes] 
260                                     length:[data length] 
261                                   encoding:NSUTF8StringEncoding];
262     if (note) return note;
263
264     // encode UTF-8 fail.
265     return @"";
266 }
267
268 + (NSString *)loadCryptFile:(NSString *)path password:(NSString *)password {
269     NSData *encData = [NSData dataWithContentsOfFile:path];
270     NSError *error = nil;
271     NSData *plainData = [CryptCore decrypt:password data:encData error:&error];
272     if (error) return nil;
273     
274     return [Storage trimBOM:plainData];
275 }
276
277 - (FileItem *)encrypt:(NSString *)key item:(FileItem*)item {
278     NSData *plainData = [NSData dataWithContentsOfFile:item.path];
279     NSError *error = nil;
280     NSData *encData = [CryptCore encrypt:key data:plainData error:&error];
281     if (error) return nil;
282
283     NSMutableString *newPath = [[NSMutableString alloc] initWithCapacity:256];
284     [newPath appendString:[item.path stringByDeletingPathExtension]];
285     [newPath appendString:@".chi"];
286     
287     FileItem *newItem = [self decideFileName:item.name path:newPath];
288     newItem.isCrypt = YES;
289     
290     if (![encData writeToFile:newItem.path atomically:YES]) {
291         return nil;
292     }
293     [fileManager removeItemAtPath:item.path error:&error];
294     return newItem;
295 }
296
297 - (FileItem *)decrypt:(NSString *)key item:(FileItem*)item {
298     // TODO: implement
299     NSData *encData = [NSData dataWithContentsOfFile:item.path];
300     NSError *error = nil;
301     NSData *plainData = [CryptCore decrypt:key data:encData error:&error];
302     if (error) return nil;
303     
304     NSMutableString *newPath = [[NSMutableString alloc] initWithCapacity:256];
305     [newPath appendString:[item.path stringByDeletingPathExtension]];
306     [newPath appendString:@".txt"];
307     FileItem *newItem = [self decideFileName:item.name path:newPath];
308
309     if (![plainData writeToFile:newItem.path atomically:YES]) {
310         return nil;
311     }
312     [fileManager removeItemAtPath:item.path error:&error];
313     return newItem;    
314 }
315
316 @end