Make info windows modular. Add support for versions pane in info window. Allow downlo...
[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, versions;
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         childrenUpdatedNotificationName = @"PithosSubdirNodeChildrenUpdated";
71         refreshParent = NO;
72     }
73     return self;
74 }
75
76 - (void)dealloc {
77     [refreshVersionsObjectRequest clearDelegatesAndCancel];
78     [refreshVersionsObjectRequest release];
79     [refreshMetadataObjectRequest clearDelegatesAndCancel];
80     [refreshMetadataObjectRequest release];
81     [applyMetadataObjectRequest clearDelegatesAndCancel];
82     [applyMetadataObjectRequest release];
83     [versions release];
84     [pithosObject release];
85     [super dealloc];
86 }
87
88 #pragma mark -
89 #pragma mark Properties
90
91 - (void)setPithos:(ASIPithos *)aPithos {
92     if (aPithos && ![aPithos isEqualTo:pithos]) {
93         [pithos release];
94         pithos = [aPithos retain];
95         [url release];
96         url = nil;
97     }
98 }
99
100 - (NSString *)url {
101     if (url == nil)
102         url = [[NSString alloc] initWithFormat:@"subdir %@/%@/%@%@", 
103                (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
104                pithosContainer.name, 
105                prefix, 
106                (shared ? @"?shared" : @"")];
107     return url;
108 }
109
110 - (NSString *)displayName {
111     if (displayName == nil) {
112         displayName = [pithosObject.name lastPathComponent];
113         if (!pithosObject.subdir && [pithosObject.name hasSuffix:@"/"])
114             displayName = [displayName stringByAppendingString:@"/"];
115         [displayName retain];
116     } 
117     return [[displayName copy] autorelease];
118 }
119
120 - (void)setDisplayName:(NSString *)aDisplayName {    
121 }
122
123 - (NSImage *)icon {
124     if (icon == nil)
125         icon = [sharedIcon retain];
126     return icon;
127 }
128
129 - (void)setPithosObject:(ASIPithosObject *)aPithosObject {
130     if (![pithosObject isEqualTo:aPithosObject]) {
131         [pithosObject release];
132         pithosObject = [aPithosObject retain];
133     }
134     if (pithosObject.subdir) {
135         self.prefix = [pithosObject.name substringToIndex:([pithosObject.name length] - 1)];
136     } else {
137         self.prefix = [NSString stringWithString:pithosObject.name];
138     }
139     self.isPublic = (pithosObject.publicURI != nil);
140     // Refresh browser if the object is in my shared and is no longer shared
141     if (shared && !pithosObject.subdir && !pithosObject.sharing) {
142         [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosBrowserRefreshNeeeded" object:self];
143     }
144 }
145
146 - (void)setLimitedPithosObject:(ASIPithosObject *)aPithosObject {
147     if (![pithosObject isEqualTo:aPithosObject]) {
148         self.pithosObject.subdir = aPithosObject.subdir;
149         self.pithosObject.name = aPithosObject.name;
150         self.pithosObject.hash = aPithosObject.hash;
151         self.pithosObject.objectHash = aPithosObject.objectHash;
152         self.pithosObject.UUID = aPithosObject.UUID;
153         self.pithosObject.bytes = aPithosObject.bytes;
154         self.pithosObject.contentType = aPithosObject.contentType;
155         self.pithosObject.lastModified = aPithosObject.lastModified;
156         self.pithosObject.version = aPithosObject.version;
157         self.pithosObject.versionTimestamp = aPithosObject.versionTimestamp;
158         self.pithosObject.modifiedBy = aPithosObject.modifiedBy;
159         self.pithosObject.sharedBy = aPithosObject.sharedBy;
160         self.pithosObject.allowedTo = aPithosObject.allowedTo;
161         if (!pithosNodeInfoController) {
162             self.pithosObject.sharing = aPithosObject.sharing;
163             self.pithosObject.publicURI = aPithosObject.publicURI;
164             self.pithosObject = pithosObject;
165         }
166     }
167 }
168
169 #pragma mark -
170 #pragma mark ASIHTTPRequestDelegate
171
172 - (void)objectRequestFinished:(ASIPithosObjectRequest *)request {
173     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
174     NSLog(@"URL: %@", [request url]);
175     NSLog(@"cached: %d", [request didUseCachedResponse]);
176     
177     if ([request isEqualTo:applyMetadataObjectRequest]) {
178         int responseStatusCode = applyMetadataObjectRequest.responseStatusCode;
179         if ((responseStatusCode != 201) && (responseStatusCode != 202))
180             [PithosUtilities unexpectedResponseStatusAlertWithRequest:applyMetadataObjectRequest];
181         @synchronized(self) {
182             [applyMetadataObjectRequest release];
183             applyMetadataObjectRequest = nil;
184         }
185         if ((responseStatusCode == 201) || (responseStatusCode == 202))
186             [self refreshInfo];
187     } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
188         [[pithosNodeInfoController window] makeFirstResponder:nil];
189         self.pithosObject = [refreshMetadataObjectRequest object];
190         if (refreshParent) {
191             // Ask the parent for refresh for the case where an object was removed
192             // It is done here so that it doesn't affect the info window refresh
193             [parent refresh];
194             refreshParent = NO;
195         }
196         @synchronized(self) {
197             [refreshMetadataObjectRequest release];
198             refreshMetadataObjectRequest = nil;
199         }
200     } else if ([request isEqualTo:refreshVersionsObjectRequest]) {
201         [[pithosNodeInfoController window] makeFirstResponder:nil];
202         self.versions = [refreshVersionsObjectRequest versions];
203         @synchronized(self) {
204             [refreshVersionsObjectRequest release];
205             refreshVersionsObjectRequest = nil;
206         }
207     }
208     [pool drain];
209 }
210
211 - (void)objectRequestFailed:(ASIPithosObjectRequest *)request {
212     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
213     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
214     if (retries > 0) {
215         ASIPithosObjectRequest *newRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:request] autorelease];
216         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
217         if ([request isEqualTo:applyMetadataObjectRequest]) {
218             @synchronized(self) {
219                 [applyMetadataObjectRequest release];
220                 applyMetadataObjectRequest = newRequest;
221             }
222         } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
223             @synchronized(self) {
224                 [refreshMetadataObjectRequest release];
225                 refreshMetadataObjectRequest = newRequest;
226             }
227         } else if ([request isEqualTo:refreshVersionsObjectRequest]) {
228             @synchronized(self) {
229                 [refreshVersionsObjectRequest release];
230                 refreshVersionsObjectRequest = newRequest;
231             }
232         }
233         [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
234     } else {
235         if ([request isEqualTo:applyMetadataObjectRequest]) {
236             dispatch_async(dispatch_get_main_queue(), ^{
237                 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataObjectRequest];
238             });
239             @synchronized(self) {
240                 [applyMetadataObjectRequest release];
241                 applyMetadataObjectRequest = nil;
242             }
243         } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
244             dispatch_async(dispatch_get_main_queue(), ^{
245                 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataObjectRequest];
246             });
247             @synchronized(self) {
248                 [refreshMetadataObjectRequest release];
249                 refreshMetadataObjectRequest = nil;
250             }
251         } else if ([request isEqualTo:refreshVersionsObjectRequest]) {
252             dispatch_async(dispatch_get_main_queue(), ^{
253                 [PithosUtilities httpRequestErrorAlertWithRequest:refreshVersionsObjectRequest];
254             });
255             @synchronized(self) {
256                 [refreshVersionsObjectRequest release];
257                 refreshVersionsObjectRequest = nil;
258             }
259         }
260     }
261     [pool drain];
262 }
263
264 #pragma mark -
265 #pragma mark Info
266
267 - (void)applyInfo {
268     @synchronized(self) {
269         if (applyMetadataObjectRequest == nil) {
270             if (pithosObject.subdir) {
271                 BOOL createObject = NO;
272                 NSAlert *alert;
273                 ASIPithosObjectRequest *request = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
274                                                                                             containerName:pithosContainer.name 
275                                                                                                objectName:prefix];
276                 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
277                 [networkQueue go];
278                 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:request]] waitUntilFinished:YES];
279                 if ([request error]) {
280                     alert = [[[NSAlert alloc] init] autorelease];
281                     [alert setMessageText:@"HTTP Request Error"];
282                     [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [request error]]];
283                     [alert addButtonWithTitle:@"OK"];
284                     [alert runModal];
285                     return;
286                 } else if (request.responseStatusCode == 200) {
287                     alert = [[[NSAlert alloc] init] autorelease];
288                     [alert setMessageText:@"Apply changes"];
289                     [alert setInformativeText:[NSString stringWithFormat:@"In order to apply the changes in '%@', the object at the same path must be replaced. Continue?", self.displayName]];
290                     [alert addButtonWithTitle:@"OK"];
291                     [alert addButtonWithTitle:@"Cancel"];
292                     NSInteger choice = [alert runModal];
293                     if (choice == NSAlertFirstButtonReturn) {
294                         request = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
295                                                                           containerName:pithosContainer.name 
296                                                                              objectName:prefix];
297                         ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
298                         [networkQueue go];
299                         [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:request]] waitUntilFinished:YES];
300                         if ([request error]) {
301                             alert = [[[NSAlert alloc] init] autorelease];
302                             [alert setMessageText:@"HTTP Request Error"];
303                             [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [request error]]];
304                             [alert addButtonWithTitle:@"OK"];
305                             [alert runModal];
306                             return;
307                         } else if (request.responseStatusCode != 204) {
308                             [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
309                             return;
310                         }
311                         refreshParent = YES;
312                         createObject = YES;
313                     } else {
314                         return;
315                     }
316                 } else if (request.responseStatusCode == 404) {
317                     createObject = YES;
318                 } else {
319                     [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
320                     return;
321                 }
322                 if (createObject) {
323                     [[pithosNodeInfoController window] makeFirstResponder:nil];
324                     applyMetadataObjectRequest = [[ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
325                                                                                              containerName:pithosContainer.name 
326                                                                                                 objectName:prefix 
327                                                                                                       eTag:nil
328                                                                                                contentType:@"application/directory"
329                                                                                            contentEncoding:pithosObject.contentEncoding 
330                                                                                         contentDisposition:pithosObject.contentDisposition 
331                                                                                                   manifest:pithosObject.manifest 
332                                                                                                    sharing:pithosObject.sharing 
333                                                                                                   isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) 
334                                                                                                   metadata:pithosObject.metadata 
335                                                                                                       data:[NSData data]] retain];
336                     pithosObject.subdir = NO;
337                     applyMetadataObjectRequest.delegate = self;
338                     applyMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
339                     applyMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
340                     applyMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
341                                                            [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
342                                                            [NSNumber numberWithUnsignedInteger:10], @"retries", 
343                                                            NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
344                                                            NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
345                                                            nil];
346                     [[PithosUtilities prepareRequest:applyMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
347                 }
348             } else {
349                 [[pithosNodeInfoController window] makeFirstResponder:nil];
350                 if (sharingAccount) {
351                     applyMetadataObjectRequest = [[ASIPithosObjectRequest updateObjectMetadataRequestWithPithos:pithos 
352                                                                                                   containerName:pithosContainer.name 
353                                                                                                      objectName:pithosObject.name 
354                                                                                                 contentEncoding:nil
355                                                                                              contentDisposition:nil
356                                                                                                        manifest:nil 
357                                                                                                         sharing:nil 
358                                                                                                        isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) 
359                                                                                                        metadata:pithosObject.metadata
360                                                                                                          update:NO] retain];
361                     [applyMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
362                 } else {
363                     applyMetadataObjectRequest = [[ASIPithosObjectRequest updateObjectMetadataRequestWithPithos:pithos 
364                                                                                                   containerName:pithosContainer.name 
365                                                                                                      objectName:pithosObject.name 
366                                                                                                 contentEncoding:pithosObject.contentEncoding
367                                                                                              contentDisposition:pithosObject.contentDisposition 
368                                                                                                        manifest:pithosObject.manifest 
369                                                                                                         sharing:(pithosObject.sharing ? pithosObject.sharing : @"") 
370                                                                                                        isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) 
371                                                                                                        metadata:pithosObject.metadata
372                                                                                                          update:NO] retain];
373                 }
374                 applyMetadataObjectRequest.delegate = self;
375                 applyMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
376                 applyMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
377                 applyMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
378                                                        [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
379                                                        [NSNumber numberWithUnsignedInteger:10], @"retries", 
380                                                        NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
381                                                        NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
382                                                        nil];
383                 [[PithosUtilities prepareRequest:applyMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
384             }
385         }
386     }
387 }
388
389 - (void)refreshInfo {
390     @synchronized(self) {
391         if (pithosObject.subdir) {
392             self.pithosObject = [ASIPithosObject subdirWithName:pithosObject.name];
393             return;
394         } else if (refreshMetadataObjectRequest == nil) {
395             refreshMetadataObjectRequest = [[ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
396                                                                                       containerName:pithosContainer.name 
397                                                                                          objectName:prefix] retain];
398             if (sharingAccount)
399                 [refreshMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
400             refreshMetadataObjectRequest.delegate = self;
401             refreshMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
402             refreshMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
403             refreshMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
404                                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
405                                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
406                                                      NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
407                                                      NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
408                                                      nil];
409             refreshMetadataObjectRequest.downloadCache = [ASIDownloadCache sharedCache];
410             [[PithosUtilities prepareRequest:refreshMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
411         }
412     }
413     [self refreshVersions];
414 }
415
416 #pragma mark -
417 #pragma mark Versions
418
419 - (void)refreshVersions {
420     @synchronized(self) {
421         if (pithosObject.subdir) {
422             return;
423         } else if (refreshVersionsObjectRequest == nil) {
424             refreshVersionsObjectRequest = [[ASIPithosObjectRequest objectVersionsRequestWithPithos:pithos 
425                                                                                       containerName:pithosContainer.name 
426                                                                                          objectName:pithosObject.name] retain];
427             if (sharingAccount)
428                 [refreshVersionsObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
429             refreshVersionsObjectRequest.delegate = self;
430             refreshVersionsObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
431             refreshVersionsObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
432             refreshVersionsObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
433                                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
434                                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
435                                                      NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", 
436                                                      NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", 
437                                                      nil];
438             refreshVersionsObjectRequest.downloadCache = [ASIDownloadCache sharedCache];
439             [[PithosUtilities prepareRequest:refreshVersionsObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
440         }
441     }
442 }
443
444 #pragma mark -
445 #pragma mark Actions
446
447 - (void)showPithosNodeInfo:(id)sender {
448     if (!pithosNodeInfoController) {
449         pithosNodeInfoController = [[PithosObjectNodeInfoController alloc] initWithPithosNode:self];
450         [self refreshInfo];
451     }
452     [pithosNodeInfoController showWindow:sender];
453     [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
454     [NSApp activateIgnoringOtherApps:YES];
455 }
456
457 @end