5 @implementation Storage
7 @synthesize currentDirectory;
8 @synthesize documentRoot;
9 @synthesize fileManager;
12 Storage *storage = [Storage alloc];
13 storage.currentDirectory = @"/";
15 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
16 storage.documentRoot = [paths objectAtIndex:0];
18 storage.fileManager = [NSFileManager defaultManager];
23 -(NSArray*)listItems {
25 NSString *currentPath = [documentRoot stringByAppendingString: currentDirectory];
26 NSArray *files = [fileManager contentsOfDirectoryAtPath: currentPath
29 return [NSArray alloc];
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];
39 [fileManager fileExistsAtPath:item.path isDirectory:&bDir];
40 item.isDirectory = bDir;
43 if ([@"chi" isEqualToString:[f pathExtension]]) {
48 [result addObject:item];
53 -(void)chdir:(NSString *)subdir {
54 NSString *newCurrent = [currentDirectory stringByAppendingPathComponent:subdir];
55 self.currentDirectory = [newCurrent stringByAppendingString:@"/"];
59 NSString *parent = [currentDirectory stringByDeletingLastPathComponent];
60 if ([parent isEqualToString:@"/"]) {
61 self.currentDirectory = parent;
63 self.currentDirectory = [parent stringByAppendingString:@"/"];
68 return [currentDirectory isEqualToString:@"/"];
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;
81 [data writeToFile:path atomically:YES];
84 - (NSString *)pickupTitle:(NSString *)note {
88 NSRange titleRange = [note lineRangeForRange:r];
89 NSString *title = [note substringWithRange:titleRange];
91 if ([title characterAtIndex:(title.length - 1)] == '\n') {
92 title = [title substringToIndex:(title.length - 1)];
94 if (title.length == 0) {
95 title = @"New document";
100 - (FileItem *)savePlain:(NSString *)note item:(FileItem *)item {
101 if (!item) return nil;
103 NSString *title = [self pickupTitle:note];
105 // If title is changed, rename one.
107 if (!item.name || ![title isEqualToString: item.name]) {
108 result = [self decideFileName:title path:item.path];
111 // If it is not new item, needs rename.
112 NSError *error = nil;
113 [fileManager moveItemAtPath:item.path toPath:result.path error:&error];
120 [self saveDataWithBOM:note file:result.path];
125 - (FileItem *)saveCrypt:(NSString *)note item:(FileItem *)item password:(NSString *)password {
126 NSString *title = [self pickupTitle:note];
129 if (!item.name || ![title isEqualToString: item.name]) {
130 result = [self decideFileName:title path:item.path];
133 NSError *error = nil;
134 [fileManager moveItemAtPath:item.path toPath:result.path error:&error];
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;
150 NSError *error = nil;
151 NSData *encData = [CryptCore encrypt:password data:data error:&error];
152 if (error) return nil;
154 if (![encData writeToFile:result.path atomically:YES]) return nil;
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];
164 for (NSString *t in chars) {
165 result = [result stringByReplacingOccurrencesOfString:t withString:@""];
170 - (FileItem *)decideFileName:(NSString *)titleCand path:(NSString *)origPath {
171 FileItem *result = [FileItem alloc];
173 NSString *ext = [origPath pathExtension];
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];
184 if ([fileManager fileExistsAtPath:path]) {
190 [path deleteCharactersInRange:r];
191 [path appendFormat:@"(%d)", cnt];
192 [path appendString:@"."];
193 [path appendString:ext];
196 if (![fileManager fileExistsAtPath:path]) break;
200 result.name = [[path lastPathComponent] stringByDeletingPathExtension];
202 result.name = titleCand;
209 - (FileItem *)newItem {
210 FileItem *p = [FileItem allocWithName: nil];
211 p.path = [documentRoot stringByAppendingString:@"/_dummy.txt"];
215 - (void)deleteItem:(FileItem*)item {
216 [fileManager removeItemAtPath:item.path error:nil];
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];
227 FileItem *item = [FileItem allocWithName:folder];
229 item.isDirectory = YES;
233 +(NSString *)load:(NSString *)path {
234 NSData *data = [NSData dataWithContentsOfFile:path];
235 return [Storage trimBOM:data];
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];
251 if ([[[NSLocale currentLocale] localeIdentifier] isEqualToString:@"ja_JP"]) {
252 note = [[NSString alloc] initWithBytes:[data bytes]
254 encoding:NSShiftJISStringEncoding];
255 if (note) return note;
259 note = [[NSString alloc] initWithBytes:[data bytes]
261 encoding:NSUTF8StringEncoding];
262 if (note) return note;
264 // encode UTF-8 fail.
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;
274 return [Storage trimBOM:plainData];
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;
283 NSMutableString *newPath = [[NSMutableString alloc] initWithCapacity:256];
284 [newPath appendString:[item.path stringByDeletingPathExtension]];
285 [newPath appendString:@".chi"];
287 FileItem *newItem = [self decideFileName:item.name path:newPath];
288 newItem.isCrypt = YES;
290 if (![encData writeToFile:newItem.path atomically:YES]) {
293 [fileManager removeItemAtPath:item.path error:&error];
297 - (FileItem *)decrypt:(NSString *)key item:(FileItem*)item {
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;
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];
309 if (![plainData writeToFile:newItem.path atomically:YES]) {
312 [fileManager removeItemAtPath:item.path error:&error];