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