Fix view of versioned file
[pithos-web-client] / src / gr / grnet / pithos / web / client / FileShareDialog.java
1 /*
2  * Copyright 2011-2013 GRNET S.A. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or
5  * without modification, are permitted provided that the following
6  * conditions are met:
7  *
8  *   1. Redistributions of source code must retain the above
9  *      copyright notice, this list of conditions and the following
10  *      disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials
15  *      provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * The views and conclusions contained in the software and
31  * documentation are those of the authors and should not be
32  * interpreted as representing official policies, either expressed
33  * or implied, of GRNET S.A.
34  */
35 package gr.grnet.pithos.web.client;
36
37 import com.google.gwt.http.client.URL;
38 import com.google.gwt.user.client.ui.*;
39 import gr.grnet.pithos.web.client.foldertree.File;
40 import gr.grnet.pithos.web.client.rest.HeadRequest;
41 import gr.grnet.pithos.web.client.rest.PostRequest;
42
43 import com.google.gwt.core.client.GWT;
44 import com.google.gwt.core.client.Scheduler;
45 import com.google.gwt.dom.client.NativeEvent;
46 import com.google.gwt.event.dom.client.ClickEvent;
47 import com.google.gwt.event.dom.client.ClickHandler;
48 import com.google.gwt.event.dom.client.KeyCodes;
49 import com.google.gwt.http.client.Response;
50 import com.google.gwt.http.client.UrlBuilder;
51 import com.google.gwt.resources.client.ImageResource;
52 import com.google.gwt.user.client.Command;
53 import com.google.gwt.user.client.Window;
54 import com.google.gwt.user.client.Event.NativePreviewEvent;
55
56 import java.util.Map;
57
58 /**
59  * UI for the "Share" command.
60  */
61 public class FileShareDialog extends AbstractPropertiesDialog {
62     private static final class PermissionsUncheckWarning extends AbstractPropertiesDialog {
63         private PermissionsUncheckWarning(Pithos app) {
64             super(app);
65             final Anchor close = new Anchor("close");
66             close.addStyleName("close");
67             close.addClickHandler(new ClickHandler() {
68                 @Override
69                 public void onClick(ClickEvent event) {
70                     hide();
71                 }
72             });
73             final String dialogText = "Info";
74             setText(dialogText);
75             setStyleName("pithos-DialogBox");
76
77             final VerticalPanel panel = new VerticalPanel();
78             panel.add(close);
79
80             VerticalPanel inner = new VerticalPanel();
81             inner.addStyleName("inner");
82
83             inner.add(
84                 new InlineHTML(
85                     "It seems you are already sharing this file with some users." +
86                     "<br>" +
87                     "Please remove all users from the sharing list, to be able to uncheck this option."
88                 )
89             );
90
91             final Button ok = new Button("OK", new ClickHandler() {
92                 @Override
93                 public void onClick(ClickEvent event) {
94                     hide();
95                 }
96             });
97
98             ok.addStyleName("button");
99             inner.add(ok);
100
101             panel.add(inner);
102             panel.setCellHorizontalAlignment(inner, HasHorizontalAlignment.ALIGN_CENTER);
103
104             setWidget(panel);
105         }
106
107         @Override
108         protected boolean accept() {
109             return true;
110         }
111     }
112
113     // For public sharing
114         private final HorizontalPanel publicPathPanel = new HorizontalPanel();
115         private final TextBox publicPathText = new TextBox();
116
117     // For private sharing
118     private final HorizontalPanel privatePathPanel = new HorizontalPanel();
119     private final TextBox privatePathText = new TextBox();
120     private PermissionsList permList;
121
122     public interface PrivateSharingImages extends MessagePanel.Images {
123
124         @Source("gr/grnet/pithos/resources/edit_user.png")
125         ImageResource permUser();
126
127         @Source("gr/grnet/pithos/resources/groups22.png")
128         ImageResource permGroup();
129
130         @Source("gr/grnet/pithos/resources/delete.gif")
131         ImageResource delete();
132     }
133
134     private final File file;
135
136     private final PrivateSharingImages privateSharingImages = GWT.create(PrivateSharingImages.class);
137
138         /**
139          * The widget's constructor.
140          */
141         public FileShareDialog(Pithos app, File file) {
142         super(app);
143         this.file = file;
144
145                 Anchor close = new Anchor("close");
146                 close.addStyleName("close");
147                 close.addClickHandler(new ClickHandler() {
148                         
149                         @Override
150                         public void onClick(ClickEvent event) {
151                                 hide();
152                         }
153                 });
154                 // Set the dialog's caption.
155                 setText(Const.TXT_SHARE_FILE);
156                 setGlassEnabled(true);
157                 setStyleName("pithos-DialogBox");
158
159                 // Outer contains inner and buttons.
160                 final VerticalPanel outer = new VerticalPanel();
161                 outer.add(close);
162                 final FocusPanel focusPanel = new FocusPanel(outer);
163                 // Inner contains generalPanel and permPanel.
164                 inner = new VerticalPanel();
165                 inner.addStyleName("inner");
166
167         inner.add(createMainPanel());
168
169         outer.add(inner);
170
171                 final Button ok = new Button("OK", new ClickHandler() {
172                         @Override
173                         public void onClick(ClickEvent event) {
174                                 closeDialog();
175                         }
176                 });
177                 ok.addStyleName("button");
178
179         outer.add(ok);
180         outer.setCellHorizontalAlignment(inner, HasHorizontalAlignment.ALIGN_CENTER);
181
182         focusPanel.setFocus(true);
183         setWidget(outer);
184         }
185
186     private boolean IamOwner() {
187         return file.getOwnerID().equals(app.getUserID());
188     }
189
190     private void populatePublicSharingPanel(VerticalPanel publicSharingPanel) {
191         if(IamOwner()) {
192             final HorizontalPanel publicCheckPanel = new HorizontalPanel();
193             publicCheckPanel.setSpacing(8);
194
195             // Check box header
196             final CheckBox publicCheckBox = new CheckBox();
197             Label publicCheckTitle = new InlineHTML("<b>Public on the Internet</b>");
198             Label publicCheckInfo = new Label("Anyone who has the public link can access. No sign-in required.", true);
199
200             publicCheckBox.setValue(isFilePubliclyShared());
201             publicCheckBox.addClickHandler(new ClickHandler() {
202                 @Override
203                 public void onClick(ClickEvent event) {
204                     final Boolean published;
205                     if(publicCheckBox.getValue() != file.isPublished() && IamOwner()) {
206                         published = publicCheckBox.getValue();
207                     }
208                     else {
209                         published = Boolean.FALSE;
210                     }
211
212                     updateMetaDataForPublicSharing(published);
213                 }
214             });
215
216             publicCheckPanel.add(publicCheckBox);
217             publicCheckPanel.add(publicCheckTitle);
218             publicCheckPanel.add(publicCheckInfo);
219
220             // Public Link
221             publicPathPanel.setVisible(false);
222             publicPathPanel.setWidth(Const.PERCENT_100);
223             publicPathPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
224             publicPathPanel.add(new Label(Const.TXT_PUBLIC_LINK));
225             publicPathPanel.setSpacing(8);
226             publicPathPanel.addStyleName("pithos-TabPanelBottom");
227
228             publicPathText.setWidth(Const.PERCENT_100);
229             publicPathText.addClickHandler(new ClickHandler() {
230                 @Override
231                 public void onClick(ClickEvent event) {
232                     Pithos.enableIESelection();
233                     ((TextBox) event.getSource()).selectAll();
234                     Pithos.preventIESelection();
235                 }
236             });
237
238             publicPathText.setText(""); // check: showLinkForPublicSharing();
239             publicPathText.setTitle("Use this link for sharing the file via e-mail, IM, etc. (crtl-C/cmd-C to copy to system clipboard)");
240             publicPathText.setReadOnly(true);
241             publicPathPanel.add(publicPathText);
242
243             publicSharingPanel.add(publicCheckPanel);
244             publicSharingPanel.add(publicPathPanel);
245
246             Scheduler.get().scheduleDeferred(new Command() {
247                 @Override
248                 public void execute() {
249                     showLinkForPublicSharing();
250                 }
251             });
252         }
253     }
254
255     private void populatePrivateSharingPanel(VerticalPanel privateSharingPanel) {
256         final HorizontalPanel privateCheckPanel = new HorizontalPanel();
257         final VerticalPanel privatePermPanel = new VerticalPanel();
258         final HorizontalPanel permButtons = new HorizontalPanel();
259
260         privateCheckPanel.setSpacing(8);
261         privatePermPanel.setVisible(isFilePrivatelyShared());
262         permButtons.setSpacing(8);
263
264         // Check box header
265         final CheckBox privateCheckBox = new CheckBox();
266         final Label privateCheckTitle = new  InlineHTML("<b>Private sharing</b>");
267         final Label privateCheckInfo = new Label("Only people explicitly granted permission can access. Sign-in required.", true);
268
269         privateCheckBox.setValue(isFilePrivatelyShared());
270         privateCheckBox.addClickHandler(new ClickHandler() {
271             @Override
272             public void onClick(ClickEvent event) {
273                 // This is the value *after* the click is applied :)
274                 boolean userCheckedIt = privateCheckBox.getValue();
275                 boolean userUncheckedIt = !userCheckedIt;
276
277                 if(isFilePrivatelyShared()) {
278                     // ignore the click, set it always to "checked"
279                     privateCheckBox.setValue(true);
280                     // show permissions
281                     privatePermPanel.setVisible(true);
282
283                     // Refs #3593
284                     if(userUncheckedIt) {
285                         new PermissionsUncheckWarning(app).center();
286                     }
287                 }
288                 else {
289                     privatePermPanel.setVisible(userCheckedIt);
290                 }
291             }
292         });
293
294         privateCheckPanel.add(privateCheckBox);
295         privateCheckPanel.add(privateCheckTitle);
296         privateCheckPanel.add(privateCheckInfo);
297
298         // Permission list
299         permList = new PermissionsList(app, privateSharingImages, file.getPermissions(), file.getOwnerID(), false, new Command() {
300             @Override
301             public void execute() {
302                 updateMetaDataForPrivateSharing();
303             }
304         });
305
306         // Permission Add buttons
307         Button addUser = new Button("Add User", new ClickHandler() {
308             @Override
309             public void onClick(ClickEvent event) {
310                 PermissionsAddDialog dlg = new PermissionsAddDialog(app, app.getAccount().getGroups(), permList, true);
311                 dlg.center();
312                 permList.updatePermissionTable();
313             }
314         });
315         addUser.addStyleName("button");
316         permButtons.add(addUser);
317
318         final boolean haveGroups = app.getAccount().getGroups().size() > 0;
319
320         if(haveGroups) {
321             Button addGroup = new Button("Add Group", new ClickHandler() {
322                 @Override
323                 public void onClick(ClickEvent event) {
324                     PermissionsAddDialog dlg = new PermissionsAddDialog(app, app.getAccount().getGroups(), permList, false);
325                     dlg.center();
326                     permList.updatePermissionTable();
327                 }
328             });
329             addGroup.addStyleName("button");
330
331             permButtons.add(addGroup);
332             permButtons.setCellHorizontalAlignment(addGroup, HasHorizontalAlignment.ALIGN_CENTER);
333         }
334
335         final Button addEverybody = new Button("Add everybody", new ClickHandler() {
336             @Override
337             public void onClick(ClickEvent event) {
338                 Pithos.LOG("Adding to Everybody");
339                 Pithos.LOG("");
340                 permList.addPermission("*", true, false);
341                 permList.updatePermissionTable();
342             }
343         });
344         addEverybody.addStyleName("button");
345         permButtons.add(addEverybody);
346
347         privatePermPanel.add(permList);
348         privatePermPanel.add(permButtons);
349
350         // Private Link
351         privatePathPanel.setVisible(false);
352         privatePathPanel.setWidth(Const.PERCENT_100);
353         privatePathPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
354         privatePathPanel.add(new Label(Const.TXT_PRIVATE_LINK));
355         privatePathPanel.setSpacing(8);
356         privatePathPanel.addStyleName("pithos-TabPanelBottom");
357
358         privatePathText.setWidth(Const.PERCENT_100);
359         privatePathText.addClickHandler(new ClickHandler() {
360             @Override
361             public void onClick(ClickEvent event) {
362                 Pithos.enableIESelection();
363                 ((TextBox) event.getSource()).selectAll();
364                 Pithos.preventIESelection();
365             }
366         });
367
368         privatePathText.setText(""); // check: showLinkForPrivateSharing();
369         privatePathText.setTitle("Use this link for sharing the file via e-mail, IM, etc. (crtl-C/cmd-C to copy to system clipboard)");
370         privatePathText.setWidth(Const.PERCENT_100);
371         privatePathText.setReadOnly(true);
372         privatePathPanel.add(privatePathText);
373
374         Scheduler.get().scheduleDeferred(new Command() {
375             @Override
376             public void execute() {
377                 showLinkForPrivateSharing();
378             }
379         });
380
381         privateSharingPanel.add(privateCheckPanel);
382         privateSharingPanel.add(privatePermPanel);
383         privateSharingPanel.add(privatePathPanel);
384     }
385
386     private Panel createMainPanel() {
387         VerticalPanel panelAll = new VerticalPanel();
388         VerticalPanel panelPublic = new VerticalPanel();
389         VerticalPanel panelPrivate = new VerticalPanel();
390
391         populatePrivateSharingPanel(panelPrivate);
392         populatePublicSharingPanel(panelPublic);
393
394         panelAll.add(panelPrivate);
395         panelAll.add(new InlineHTML("<hr/>"));
396         panelAll.add(panelPublic);
397
398         return panelAll;
399     }
400
401     private boolean isFilePubliclyShared() {
402         return file.isPublished();
403     }
404
405     private boolean isFilePrivatelyShared() {
406         return file.isShared();
407     }
408
409     private void showLinkForPublicSharing() {
410                 if (isFilePubliclyShared()) {
411             // Transitional: When the server returns the full URL in X-Object-Public,
412             //               just use it.
413             final String filePublicURI = file.getPublicUri();
414             final String shownPublicURI;
415             if(filePublicURI.toLowerCase().startsWith("http")) {
416                 shownPublicURI = filePublicURI;
417             }
418             else {
419                 UrlBuilder b = Window.Location.createUrlBuilder();
420                 b.setPath(filePublicURI);
421                 shownPublicURI = b.buildString();
422             }
423
424                         publicPathText.setText(shownPublicURI);
425                 publicPathPanel.setVisible(true);
426                 }
427                 else {
428                         publicPathPanel.setVisible(false);
429                 }
430     }
431
432     private void showLinkForPrivateSharing() {
433         if (isFilePrivatelyShared()) {
434             final String fileViewURL = Pithos.getFileViewURL(file);
435             final String shownViewURL;
436             if(fileViewURL.toLowerCase().startsWith("http")) {
437                 shownViewURL = fileViewURL;
438             }
439             else {
440                 UrlBuilder b = Window.Location.createUrlBuilder();
441                 b.setPath(fileViewURL);
442                 shownViewURL = b.buildString();
443             }
444
445             privatePathText.setText(shownViewURL);
446             privatePathPanel.setVisible(true);
447         }
448         else {
449             privatePathPanel.setVisible(false);
450         }
451     }
452
453         protected void updateMetaDataForPublicSharing(String api, String owner, final String path, final Boolean published) {
454         if (published != null) {
455             PostRequest updateFile = new PostRequest(api, owner, path) {
456                 @Override
457                 public void onSuccess(Resource result) {
458                         HeadRequest<File> headFile = new HeadRequest<File>(File.class, Pithos.getStorageAPIURL(), file.getOwnerID(), path, file) {
459
460                                                 @Override
461                                                 public void onSuccess(File _result) {
462                                                         showLinkForPublicSharing();
463                                                         if (!app.isMySharedSelected()) {
464                                             app.updateFolder(file.getParent(), true, new Command() {
465                                                                         
466                                                                         @Override
467                                                                         public void execute() {
468                                                                                 app.updateMySharedRoot();
469                                                                         }
470                                                                 }, true);
471                             }
472                                                         else {
473                                                                 app.updateSharedFolder(file.getParent(), true);
474                             }
475                                                 }
476
477                                                 @Override
478                                                 public void onError(Throwable t) {
479                                                         app.setError(t);
480                                     app.displayError("System error modifying file:" + t.getMessage());
481                                                 }
482
483                                                 @Override
484                                                 protected void onUnauthorized(Response response) {
485                                                         app.sessionExpired();
486                                                 }
487                                         };
488                                         headFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
489                                         Scheduler.get().scheduleDeferred(headFile);
490                 }
491
492                 @Override
493                 public void onError(Throwable t) {
494                                         app.setError(t);
495                     app.displayError("System error modifying file:" + t.getMessage());
496                 }
497
498                                 @Override
499                                 protected void onUnauthorized(Response response) {
500                                         app.sessionExpired();
501                                 }
502             };
503             updateFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
504             updateFile.setHeader(Const.X_OBJECT_PUBLIC, published.toString());
505             Scheduler.get().scheduleDeferred(updateFile);
506         }
507         else if (!app.isMySharedSelected())
508             app.updateFolder(file.getParent(), true, new Command() {
509                                 @Override
510                                 public void execute() {
511                                         if (file.isSharedOrPublished()) {
512                         app.updateMySharedRoot();
513                     }
514                                 }
515                         }, true);
516         else
517                 app.updateSharedFolder(file.getParent(), true);
518     }
519     protected void updateMetaDataForPublicSharing(Boolean published) {
520         updateMetaDataForPublicSharing(
521             Pithos.getStorageAPIURL(),
522             app.getUserID(),
523             file.getUri() + Const.QUESTION_MARK_UPDATE_EQ,
524             published
525         );
526     }
527
528     protected void updateMetaDataForPrivateSharing(String api, String owner, final String path, final Map<String, Boolean[]> newPermissions) {
529         if (newPermissions != null) {
530             PostRequest updateFile = new PostRequest(api, owner, path) {
531                 @Override
532                 public void onSuccess(Resource result) {
533                     HeadRequest<File> headFile = new HeadRequest<File>(File.class, Pithos.getStorageAPIURL(), file.getOwnerID(), path, file) {
534
535                         @Override
536                         public void onSuccess(File _result) {
537                             showLinkForPrivateSharing();
538                             if (!app.isMySharedSelected())
539                                 app.updateFolder(file.getParent(), true, new Command() {
540
541                                     @Override
542                                     public void execute() {
543                                         app.updateMySharedRoot();
544                                     }
545                                 }, true);
546                             else
547                                 app.updateSharedFolder(file.getParent(), true);
548                         }
549
550                         @Override
551                         public void onError(Throwable t) {
552                             app.setError(t);
553                             app.displayError("System error modifying file:" + t.getMessage());
554                         }
555
556                         @Override
557                         protected void onUnauthorized(Response response) {
558                             app.sessionExpired();
559                         }
560                     };
561                     headFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
562                     Scheduler.get().scheduleDeferred(headFile);
563                 }
564
565                 @Override
566                 public void onError(Throwable t) {
567                     app.setError(t);
568                     app.displayError("System error modifying file:" + t.getMessage());
569                 }
570
571                 @Override
572                 protected void onUnauthorized(Response response) {
573                     app.sessionExpired();
574                 }
575             };
576             updateFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
577
578             String readPermHeader = Const.READ_EQ;
579             String writePermHeader = Const.WRITE_EQ;
580             for (String u : newPermissions.keySet()) {
581                 Boolean[] p = newPermissions.get(u);
582                 if (p[0] != null && p[0]) {
583                     readPermHeader += u + Const.COMMA;
584                 }
585                 if (p[1] != null && p[1]) {
586                     writePermHeader += u + Const.COMMA;
587                 }
588             }
589             if (readPermHeader.endsWith(Const.EQ)) {
590                 readPermHeader = "";
591             }
592             else if (readPermHeader.endsWith(Const.COMMA)) {
593                 readPermHeader = readPermHeader.substring(0, readPermHeader.length() - 1);
594             }
595             if (writePermHeader.endsWith(Const.EQ)) {
596                 writePermHeader = "";
597             }
598             else if (writePermHeader.endsWith(Const.COMMA)) {
599                 writePermHeader = writePermHeader.substring(0, writePermHeader.length() - 1);
600             }
601             String permHeader = readPermHeader +
602                 ((readPermHeader.length()  > 0 && writePermHeader.length() > 0) ?  Const.SEMI : "") + writePermHeader;
603             if (permHeader.length() == 0) {
604                 permHeader=Const.TILDE;
605             }
606             else {
607                 permHeader = URL.encodePathSegment(permHeader);
608             }
609             updateFile.setHeader(Const.X_OBJECT_SHARING, permHeader);
610             Scheduler.get().scheduleDeferred(updateFile);
611         }
612         else if (!app.isMySharedSelected()) {
613             app.updateFolder(file.getParent(), true, new Command() {
614                 @Override
615                 public void execute() {
616                     if (file.isSharedOrPublished())
617                         app.updateMySharedRoot();
618                 }
619             }, true);
620         }
621         else {
622             app.updateSharedFolder(file.getParent(), true);
623         }
624     }
625     protected void updateMetaDataForPrivateSharing() {
626         updateMetaDataForPrivateSharing(
627             Pithos.getStorageAPIURL(),
628             app.getUserID(),
629             file.getUri() + Const.QUESTION_MARK_UPDATE_EQ,
630             permList.getPermissions()
631         );
632     }
633
634     @Override
635         protected void onPreviewNativeEvent(NativePreviewEvent preview) {
636             super.onPreviewNativeEvent(preview);
637
638             NativeEvent evt = preview.getNativeEvent();
639             if (evt.getType().equals(Const.EVENT_TYPE_KEYDOWN) &&
640             evt.getKeyCode() == KeyCodes.KEY_ENTER) {
641
642             closeDialog();
643         }
644         }
645
646     @Override
647     protected boolean accept() {
648         return true;
649     }
650 }