940f60102d5714d85f54daf1f839e631c6f4813c
[pithos-macos] / pithos-macos / PithosSubdirNode.m
1 //
2 //  PithosSubdirNode.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
15 //   2. Redistributions in binary form must reproduce the above
16 //      copyright notice, this list of conditions and the following
17 //      disclaimer in the documentation and/or other materials
18 //      provided with the distribution.
19 // 
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 // 
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
37
38 #import "PithosSubdirNode.h"
39 #import "ASIPithosRequest.h"
40 #import "ASIPithos.h"
41 #import "ASIPithosObjectRequest.h"
42 #import "ASIPithosContainer.h"
43 #import "ASIPithosObject.h"
44 #import "ASINetworkQueue.h"
45 #import "ASIDownloadCache.h"
46 #import "PithosUtilities.h"
47 #import "PithosObjectNodeInfoController.h"
48
49 static NSImage *sharedIcon = nil;
50
51 @implementation PithosSubdirNode
52 @synthesize pithosObject;
53 @synthesize isPublic;
54
55 + (void)initialize {
56         if (self == [PithosSubdirNode class])
57         sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain];
58 }
59
60 #pragma mark -
61 #pragma mark Object Lifecycle
62
63 - (id)initWithPithos:(ASIPithos *)aPithos 
64      pithosContainer:(ASIPithosContainer *)aPithosContainer 
65         pithosObject:(ASIPithosObject *)aPithosObject {
66     if ((self = [super init])) {
67         self.pithos = aPithos;
68         self.pithosContainer = aPithosContainer;
69         self.pithosObject = aPithosObject;
70         refreshParent = NO;
71     }
72     return self;
73 }
74
75 - (void)dealloc {
76     [refreshMetadataObjectRequest clearDelegatesAndCancel];
77     [refreshMetadataObjectRequest release];
78     [applyMetadataObjectRequest clearDelegatesAndCancel];
79     [applyMetadataObjectRequest release];
80     [pithosObject release];
81     [super dealloc];
82 }
83
84 #pragma mark -
85 #pragma mark Properties
86
87 - (void)setPithos:(ASIPithos *)aPithos {
88     if (aPithos && ![aPithos isEqualTo:pithos]) {
89         [pithos release];
90         pithos = [aPithos retain];
91         [url release];
92         url = nil;
93     }
94 }
95
96 - (NSString *)url {
97     if (url == nil)
98         url = [[NSString alloc] initWithFormat:@"subdir %@/%@/%@%@", 
99                (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
100                pithosContainer.name, 
101                prefix, 
102                (shared ? @"?shared" : @"")];
103     return url;
104 }
105
106 - (NSString *)displayName {
107     if (displayName == nil) {
108         displayName = [pithosObject.name lastPathComponent];
109         if (!pithosObject.subdir && [pithosObject.name hasSuffix:@"/"])
110             displayName = [displayName stringByAppendingString:@"/"];
111         [displayName retain];
112     } 
113     return [[displayName copy] autorelease];
114 }
115
116 - (void)setDisplayName:(NSString *)aDisplayName {    
117 }
118
119 - (NSImage *)icon {
120     if (icon == nil)
121         icon = [sharedIcon retain];
122     return icon;
123 }
124
125 - (void)setPithosObject:(ASIPithosObject *)aPithosObject {
126     if (![pithosObject isEqualTo:aPithosObject]) {
127         [pithosObject release];
128         pithosObject = [aPithosObject retain];
129     }
130     if (pithosObject.subdir) {
131         self.prefix = [pithosObject.name substringToIndex:([pithosObject.name length] - 1)];
132     } else {
133         self.prefix = [NSString stringWithString:pithosObject.name];
134     }
135     self.isPublic = (pithosObject.publicURI != nil);
136     // Refresh browser if the object is in my shared and is no longer shared
137     if (shared && !pithosObject.subdir && !pithosObject.sharing) {
138         [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosBrowserRefreshNeeeded" object:self];
139     }
140 }
141
142 - (void)setLimitedPithosObject:(ASIPithosObject *)aPithosObject {
143     if (![pithosObject isEqualTo:aPithosObject]) {
144         self.pithosObject.subdir = aPithosObject.subdir;
145         self.pithosObject.name = aPithosObject.name;
146         self.pithosObject.hash = aPithosObject.hash;
147         self.pithosObject.objectHash = aPithosObject.objectHash;
148         self.pithosObject.UUID = aPithosObject.UUID;
149         self.pithosObject.bytes = aPithosObject.bytes;
150         self.pithosObject.contentType = aPithosObject.contentType;
151         self.pithosObject.lastModified = aPithosObject.lastModified;
152         self.pithosObject.version = aPithosObject.version;
153         self.pithosObject.versionTimestamp = aPithosObject.versionTimestamp;
154         self.pithosObject.modifiedBy = aPithosObject.modifiedBy;
155         self.pithosObject.sharedBy = aPithosObject.sharedBy;
156         self.pithosObject.allowedTo = aPithosObject.allowedTo;
157         if (!pithosNodeInfoController) {
158             self.pithosObject.sharing = aPithosObject.sharing;
159             self.pithosObject.publicURI = aPithosObject.publicURI;
160             self.pithosObject = pithosObject;
161         }
162     }
163 }
164
165 #pragma mark -
166 #pragma mark ASIHTTPRequestDelegate
167
168 - (void)objectRequestFinished:(ASIPithosObjectRequest *)request {
169     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
170     NSLog(@"URL: %@", [request url]);
171     NSLog(@"cached: %d", [request didUseCachedResponse]);
172     
173     if ([request isEqualTo:applyMetadataObjectRequest]) {
174         int responseStatusCode = applyMetadataObjectRequest.responseStatusCode;
175         if ((responseStatusCode != 201) && (responseStatusCode != 202))
176             [PithosUtilities unexpectedResponseStatusAlertWithRequest:applyMetadataObjectRequest];
177         @synchronized(self) {
178             [applyMetadataObjectRequest release];
179             applyMetadataObjectRequest = nil;
180         }
181         if ((responseStatusCode == 201) || (responseStatusCode == 202))
182             [self refreshInfo];
183     } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
184         [[pithosNodeInfoController window] makeFirstResponder:nil];
185         self.pithosObject = [refreshMetadataObjectRequest object];
186         if (refreshParent) {
187             // Ask the parent for refresh for the case where an object was removed
188             // It is done here so that it doesn't affect the info window refresh
189             [parent refresh];
190             refreshParent = NO;
191         }
192         @synchronized(self) {
193             [refreshMetadataObjectRequest release];
194             refreshMetadataObjectRequest = nil;
195         }
196     }
197     [pool drain];
198 }
199
200 - (void)objectRequestFailed:(ASIPithosObjectRequest *)request {
201     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
202     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
203     if (retries > 0) {
204         ASIPithosObjectRequest *newRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:request] autorelease];
205         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
206         if ([request isEqualTo:applyMetadataObjectRequest]) {
207             @synchronized(self) {
208                 [applyMetadataObjectRequest release];
209                 applyMetadataObjectRequest = newRequest;
210             }
211         } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
212             @synchronized(self) {
213                 [refreshMetadataObjectRequest release];
214                 refreshMetadataObjectRequest = newRequest;
215             }
216         }
217         [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
218     } else {
219         if ([request isEqualTo:applyMetadataObjectRequest]) {
220             dispatch_async(dispatch_get_main_queue(), ^{
221                 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataObjectRequest];
222             });
223             @synchronized(self) {
224                 [applyMetadataObjectRequest release];
225                 applyMetadataObjectRequest = nil;
226             }
227         } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
228             dispatch_async(dispatch_get_main_queue(), ^{
229                 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataObjectRequest];
230             });
231             @synchronized(self) {
232                 [refreshMetadataObjectRequest release];
233                 refreshMetadataObjectRequest = nil;
234             }
235         }
236     }
237     [pool drain];
238 }
239
240 #pragma mark -
241 #pragma mark Info
242
243 - (void)applyInfo {
244     @synchronized(self) {
245         if (applyMetadataObjectRequest == nil) {
246             if (pithosObject.subdir) {
247                 BOOL createObject = NO;
248                 NSAlert *alert;
249                 ASIPithosObjectRequest *request = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
250                                                                                             containerName:pithosContainer.name 
251                                                                                                objectName:prefix];
252                 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
253                 [networkQueue go];
254                 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:request]] waitUntilFinished:YES];
255                 if ([request error]) {
256                     alert = [[[NSAlert alloc] init] autorelease];
257                     [alert setMessageText:@"HTTP Request Error"];
258                     [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [request error]]];
259                     [alert addButtonWithTitle:@"OK"];
260                     [alert runModal];
261                     return;
262                 } else if (request.responseStatusCode == 200) {
263                     alert = [[[NSAlert alloc] init] autorelease];
264                     [alert setMessageText:@"Apply changes"];
265                     [alert setInformativeText:[NSString stringWithFormat:@"In order to apply the changes in '%@', the object at the same path must be replaced. Continue?", self.displayName]];
266                     [alert addButtonWithTitle:@"OK"];
267                     [alert addButtonWithTitle:@"Cancel"];
268                     NSInteger choice = [alert runModal];
269                     if (choice == NSAlertFirstButtonReturn) {
270                         request = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
271                                                                           containerName:pithosContainer.name 
272                                                                              objectName:prefix];
273                         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
274                         [networkQueue go];
275                         [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:request]] waitUntilFinished:YES];
276                         if ([request error]) {
277                             alert = [[[NSAlert alloc] init] autorelease];
278                             [alert setMessageText:@"HTTP Request Error"];
279                             [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [request error]]];
280                             [alert addButtonWithTitle:@"OK"];
281                             [alert runModal];
282                             return;
283                         } else if (request.responseStatusCode != 204) {
284                             [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
285                             return;
286                         }
287                         refreshParent = YES;
288                         createObject = YES;
289                     } else {
290                         return;
291                     }
292                 } else if (request.responseStatusCode == 404) {
293                     createObject = YES;
294                 } else {
295                     [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
296                     return;
297                 }
298                 if (createObject) {
299                     [[pithosNodeInfoController window] makeFirstResponder:nil];
300                     applyMetadataObjectRequest = [[ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
301                                                                                              containerName:pithosContainer.name 
302                                                                                                 objectName:prefix 
303                                                                                                       eTag:nil
304                                                                                                contentType:@"application/directory"
305                                                                                            contentEncoding:pithosObject.contentEncoding 
306                                                                                         contentDisposition:pithosObject.contentDisposition 
307                                                                                                   manifest:pithosObject.manifest 
308                                                                                                    sharing:pithosObject.sharing 
309                                                                                                   isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) 
310                                                                                                   metadata:pithosObject.metadata 
311                                                                                                       data:[NSData data]] retain];
312                     pithosObject.subdir = NO;
313                     applyMetadataObjectRequest.delegate = self;
314                     applyMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
315                     applyMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
316                     applyMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
317                                                            [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
318                                                            [NSNumber numberWithUnsignedInteger:10], @"retries", 
319                                                            NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
320                                                            NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
321                                                            nil];
322                     [[PithosUtilities prepareRequest:applyMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
323                 }
324             } else {
325                 [[pithosNodeInfoController window] makeFirstResponder:nil];
326                 if (sharingAccount) {
327                     applyMetadataObjectRequest = [[ASIPithosObjectRequest updateObjectMetadataRequestWithPithos:pithos 
328                                                                                                   containerName:pithosContainer.name 
329                                                                                                      objectName:pithosObject.name 
330                                                                                                 contentEncoding:nil
331                                                                                              contentDisposition:nil
332                                                                                                        manifest:nil 
333                                                                                                         sharing:nil 
334                                                                                                        isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) 
335                                                                                                        metadata:pithosObject.metadata
336                                                                                                          update:NO] retain];
337                     [applyMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
338                 } else {
339                     applyMetadataObjectRequest = [[ASIPithosObjectRequest updateObjectMetadataRequestWithPithos:pithos 
340                                                                                                   containerName:pithosContainer.name 
341                                                                                                      objectName:pithosObject.name 
342                                                                                                 contentEncoding:pithosObject.contentEncoding
343                                                                                              contentDisposition:pithosObject.contentDisposition 
344                                                                                                        manifest:pithosObject.manifest 
345                                                                                                         sharing:(pithosObject.sharing ? pithosObject.sharing : @"") 
346                                                                                                        isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) 
347                                                                                                        metadata:pithosObject.metadata
348                                                                                                          update:NO] retain];
349                 }
350                 applyMetadataObjectRequest.delegate = self;
351                 applyMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
352                 applyMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
353                 applyMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
354                                                        [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
355                                                        [NSNumber numberWithUnsignedInteger:10], @"retries", 
356                                                        NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
357                                                        NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
358                                                        nil];
359                 [[PithosUtilities prepareRequest:applyMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
360             }
361         }
362     }
363 }
364
365 - (void)refreshInfo {
366     @synchronized(self) {
367         if (pithosObject.subdir) {
368             self.pithosObject = [ASIPithosObject subdirWithName:pithosObject.name];
369         } else if (refreshMetadataObjectRequest == nil) {
370             refreshMetadataObjectRequest = [[ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
371                                                                                       containerName:pithosContainer.name 
372                                                                                          objectName:prefix] retain];
373             if (sharingAccount)
374                 [refreshMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
375             refreshMetadataObjectRequest.delegate = self;
376             refreshMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
377             refreshMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
378             refreshMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
379                                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
380                                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
381                                                      NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
382                                                      NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
383                                                      nil];
384             refreshMetadataObjectRequest.downloadCache = [ASIDownloadCache sharedCache];
385             [[PithosUtilities prepareRequest:refreshMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
386         }
387     }
388 }
389
390 #pragma mark -
391 #pragma mark Actions
392
393 - (void)showPithosNodeInfo:(id)sender {
394     if (!pithosNodeInfoController) {
395         pithosNodeInfoController = [[PithosObjectNodeInfoController alloc] initWithPithosNode:self];
396         [self refreshInfo];
397     }
398     [pithosNodeInfoController showWindow:sender];
399     [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
400     [NSApp activateIgnoringOtherApps:YES];
401 }
402
403 @end