Configurable service URLs via the otherProperties JS mechanism
[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 PremissionsUncheckWarning extends AbstractPropertiesDialog {
63         private PremissionsUncheckWarning(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 final VerticalPanel privatePermSuperPanel = new VerticalPanel();
121     private PermissionsList permList;
122         
123         /**
124          * An image bundle for this widgets images.
125          */
126         public interface PublicSharingImages extends MessagePanel.Images {
127
128                 @Source("gr/grnet/pithos/resources/edit_user.png")
129                 ImageResource permUser();
130
131                 @Source("gr/grnet/pithos/resources/groups22.png")
132                 ImageResource permGroup();
133
134                 @Source("gr/grnet/pithos/resources/editdelete.png")
135                 ImageResource delete();
136     }
137
138     public interface PrivateSharingImages extends MessagePanel.Images {
139
140         @Source("gr/grnet/pithos/resources/edit_user.png")
141         ImageResource permUser();
142
143         @Source("gr/grnet/pithos/resources/groups22.png")
144         ImageResource permGroup();
145
146         @Source("gr/grnet/pithos/resources/delete.gif")
147         ImageResource delete();
148     }
149
150     private final File file;
151
152     private final PublicSharingImages publicSharingImages = GWT.create(PublicSharingImages.class);
153     private final PrivateSharingImages privateSharingImages = GWT.create(PrivateSharingImages.class);
154
155         /**
156          * The widget's constructor.
157          */
158         public FileShareDialog(Pithos app, File file) {
159         super(app);
160         this.file = file;
161
162                 Anchor close = new Anchor("close");
163                 close.addStyleName("close");
164                 close.addClickHandler(new ClickHandler() {
165                         
166                         @Override
167                         public void onClick(ClickEvent event) {
168                                 hide();
169                         }
170                 });
171                 // Set the dialog's caption.
172                 setText(Const.TXT_SHARE_FILE);
173                 setGlassEnabled(true);
174                 setStyleName("pithos-DialogBox");
175
176                 // Outer contains inner and buttons.
177                 final VerticalPanel outer = new VerticalPanel();
178                 outer.add(close);
179                 final FocusPanel focusPanel = new FocusPanel(outer);
180                 // Inner contains generalPanel and permPanel.
181                 inner = new VerticalPanel();
182                 inner.addStyleName("inner");
183
184         inner.add(createMainPanel());
185
186         outer.add(inner);
187
188                 final Button ok = new Button("OK", new ClickHandler() {
189                         @Override
190                         public void onClick(ClickEvent event) {
191                                 closeDialog();
192                         }
193                 });
194                 ok.addStyleName("button");
195
196         outer.add(ok);
197         outer.setCellHorizontalAlignment(inner, HasHorizontalAlignment.ALIGN_CENTER);
198
199         focusPanel.setFocus(true);
200         setWidget(outer);
201         }
202
203     private boolean IamOwner() {
204         return file.getOwnerID().equals(app.getUserID());
205     }
206
207     private void populatePublicSharingPanel(VerticalPanel publicSharingPanel) {
208         if(IamOwner()) {
209             final HorizontalPanel publicCheckPanel = new HorizontalPanel();
210             publicCheckPanel.setSpacing(8);
211
212             // Check box header
213             final CheckBox publicCheckBox = new CheckBox();
214             Label publicCheckTitle = new InlineHTML("<b>Public on the Internet</b>");
215             Label publicCheckInfo = new Label("Anyone who has the public link can access. No sign-in required.", true);
216
217             publicCheckBox.setValue(isFilePubliclyShared());
218             publicCheckBox.addClickHandler(new ClickHandler() {
219                 @Override
220                 public void onClick(ClickEvent event) {
221                     final Boolean published;
222                     if(publicCheckBox.getValue() != file.isPublished() && IamOwner()) {
223                         published = publicCheckBox.getValue();
224                     }
225                     else {
226                         published = Boolean.FALSE;
227                     }
228
229                     updateMetaDataForPublicSharing(published);
230                 }
231             });
232
233             publicCheckPanel.add(publicCheckBox);
234             publicCheckPanel.add(publicCheckTitle);
235             publicCheckPanel.add(publicCheckInfo);
236
237             // Public Link
238             publicPathPanel.setVisible(false);
239             publicPathPanel.setWidth(Const.PERCENT_100);
240             publicPathPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
241             publicPathPanel.add(new Label(Const.TXT_PUBLIC_LINK));
242             publicPathPanel.setSpacing(8);
243             publicPathPanel.addStyleName("pithos-TabPanelBottom");
244
245             publicPathText.setWidth(Const.PERCENT_100);
246             publicPathText.addClickHandler(new ClickHandler() {
247                 @Override
248                 public void onClick(ClickEvent event) {
249                     Pithos.enableIESelection();
250                     ((TextBox) event.getSource()).selectAll();
251                     Pithos.preventIESelection();
252                 }
253             });
254             publicPathText.setText(Window.Location.getHost() + file.getPublicUri());
255             publicPathText.setTitle("Use this link for sharing the file via e-mail, IM, etc. (crtl-C/cmd-C to copy to system clipboard)");
256             publicPathText.setReadOnly(true);
257             publicPathPanel.add(publicPathText);
258
259             publicSharingPanel.add(publicCheckPanel);
260             publicSharingPanel.add(publicPathPanel);
261
262             Scheduler.get().scheduleDeferred(new Command() {
263                 @Override
264                 public void execute() {
265                     showLinkForPublicSharing();
266                 }
267             });
268         }
269     }
270
271     private void populatePrivateSharingPanel(VerticalPanel privateSharingPanel) {
272         final HorizontalPanel privateCheckPanel = new HorizontalPanel();
273         final VerticalPanel privatePermPanel = new VerticalPanel();
274         final HorizontalPanel permButtons = new HorizontalPanel();
275
276         privateCheckPanel.setSpacing(8);
277         privatePermPanel.setVisible(isFilePrivatelyShared());
278         permButtons.setSpacing(8);
279
280         // Check box header
281         final CheckBox privateCheckBox = new CheckBox();
282         final Label privateCheckTitle = new  InlineHTML("<b>Private sharing</b>");
283         final Label privateCheckInfo = new Label("Only people explicitly granted permission can access. Sign-in required.", true);
284
285         privateCheckBox.setValue(isFilePrivatelyShared());
286         privateCheckBox.addClickHandler(new ClickHandler() {
287             @Override
288             public void onClick(ClickEvent event) {
289                 // This is the value *after* the click is applied :)
290                 boolean userCheckedIt = privateCheckBox.getValue();
291                 boolean userUncheckedIt = !userCheckedIt;
292
293                 if(isFilePrivatelyShared()) {
294                     // ignore the click, set it always to "checked"
295                     privateCheckBox.setValue(true);
296                     // show permissions
297                     privatePermPanel.setVisible(true);
298
299                     // Refs #3593
300                     if(userUncheckedIt) {
301                         new PremissionsUncheckWarning(app).center();
302                     }
303                 }
304                 else {
305                     privatePermPanel.setVisible(userCheckedIt);
306                 }
307             }
308         });
309
310         privateCheckPanel.add(privateCheckBox);
311         privateCheckPanel.add(privateCheckTitle);
312         privateCheckPanel.add(privateCheckInfo);
313
314         // Permission list
315         permList = new PermissionsList(app, privateSharingImages, file.getPermissions(), file.getOwnerID(), false, new Command() {
316             @Override
317             public void execute() {
318                 updateMetaDataForPrivateSharing();
319             }
320         });
321
322         // Permission Add buttons
323         Button addUser = new Button("Add User", new ClickHandler() {
324             @Override
325             public void onClick(ClickEvent event) {
326                 PermissionsAddDialog dlg = new PermissionsAddDialog(app, app.getAccount().getGroups(), permList, true);
327                 dlg.center();
328                 permList.updatePermissionTable();
329             }
330         });
331         addUser.addStyleName("button");
332         permButtons.add(addUser);
333
334         final boolean haveGroups = app.getAccount().getGroups().size() > 0;
335
336         if(haveGroups) {
337             Button addGroup = new Button("Add Group", new ClickHandler() {
338                 @Override
339                 public void onClick(ClickEvent event) {
340                     PermissionsAddDialog dlg = new PermissionsAddDialog(app, app.getAccount().getGroups(), permList, false);
341                     dlg.center();
342                     permList.updatePermissionTable();
343                 }
344             });
345             addGroup.addStyleName("button");
346
347             permButtons.add(addGroup);
348             permButtons.setCellHorizontalAlignment(addGroup, HasHorizontalAlignment.ALIGN_CENTER);
349         }
350
351         final Button addEverybody = new Button("Add everybody", new ClickHandler() {
352             @Override
353             public void onClick(ClickEvent event) {
354                 Pithos.LOG("Adding to Everybody");
355                 Pithos.LOG("");
356                 permList.addPermission("*", true, false);
357                 permList.updatePermissionTable();
358             }
359         });
360         addEverybody.addStyleName("button");
361         permButtons.add(addEverybody);
362
363         privatePermPanel.add(permList);
364         privatePermPanel.add(permButtons);
365
366         // Private Link
367         privatePathPanel.setVisible(false);
368         privatePathPanel.setWidth(Const.PERCENT_100);
369         privatePathPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LEFT);
370         privatePathPanel.add(new Label(Const.TXT_PRIVATE_LINK));
371         privatePathPanel.setSpacing(8);
372         privatePathPanel.addStyleName("pithos-TabPanelBottom");
373
374         privatePathText.setWidth(Const.PERCENT_100);
375         privatePathText.addClickHandler(new ClickHandler() {
376             @Override
377             public void onClick(ClickEvent event) {
378                 Pithos.enableIESelection();
379                 ((TextBox) event.getSource()).selectAll();
380                 Pithos.preventIESelection();
381             }
382         });
383         privatePathText.setText(Window.Location.getHost() + file.getPublicUri());
384         privatePathText.setTitle("Use this link for sharing the file via e-mail, IM, etc. (crtl-C/cmd-C to copy to system clipboard)");
385         privatePathText.setWidth(Const.PERCENT_100);
386         privatePathText.setReadOnly(true);
387         privatePathPanel.add(privatePathText);
388
389         Scheduler.get().scheduleDeferred(new Command() {
390             @Override
391             public void execute() {
392                 showLinkForPrivateSharing();
393             }
394         });
395
396         privateSharingPanel.add(privateCheckPanel);
397         privateSharingPanel.add(privatePermPanel);
398         privateSharingPanel.add(privatePathPanel);
399     }
400
401     private Panel createMainPanel() {
402         VerticalPanel panelAll = new VerticalPanel();
403         VerticalPanel panelPublic = new VerticalPanel();
404         VerticalPanel panelPrivate = new VerticalPanel();
405
406         populatePrivateSharingPanel(panelPrivate);
407         populatePublicSharingPanel(panelPublic);
408
409         panelAll.add(panelPrivate);
410         panelAll.add(new InlineHTML("<hr/>"));
411         panelAll.add(panelPublic);
412
413         return panelAll;
414     }
415
416     private boolean isFilePubliclyShared() {
417         return file.isPublished();
418     }
419
420     private boolean isFilePrivatelyShared() {
421         return file.isShared();
422     }
423
424     private void showLinkForPublicSharing() {
425                 if (isFilePubliclyShared()) {
426                         UrlBuilder b = Window.Location.createUrlBuilder();
427                         b.setPath(file.getPublicUri());
428                         publicPathText.setText(b.buildString());
429                 publicPathPanel.setVisible(true);
430                 }
431                 else {
432                         publicPathPanel.setVisible(false);
433                 }
434     }
435
436     private void showLinkForPrivateSharing() {
437         if (isFilePrivatelyShared()) {
438             UrlBuilder b = Window.Location.createUrlBuilder();
439             b.setPath(Pithos.getStorageAPIURL() + file.getOwnerID() + file.getUri());
440             String href = Window.Location.getHref();
441             boolean hasParameters = href.contains(Const.QUESTION_MARK);
442             privatePathText.setText(href + (hasParameters ? Const.AMPERSAND : Const.QUESTION_MARK) + Const.GOTO_EQ + b.buildString());
443             privatePathPanel.setVisible(true);
444         }
445         else {
446             privatePathPanel.setVisible(false);
447         }
448     }
449
450         protected void updateMetaDataForPublicSharing(String api, String owner, final String path, final Boolean published) {
451         if (published != null) {
452             PostRequest updateFile = new PostRequest(api, owner, path) {
453                 @Override
454                 public void onSuccess(Resource result) {
455                         HeadRequest<File> headFile = new HeadRequest<File>(File.class, Pithos.getStorageAPIURL(), file.getOwnerID(), path, file) {
456
457                                                 @Override
458                                                 public void onSuccess(File _result) {
459                                                         showLinkForPublicSharing();
460                                                         if (!app.isMySharedSelected()) {
461                                             app.updateFolder(file.getParent(), true, new Command() {
462                                                                         
463                                                                         @Override
464                                                                         public void execute() {
465                                                                                 app.updateMySharedRoot();
466                                                                         }
467                                                                 }, true);
468                             }
469                                                         else {
470                                                                 app.updateSharedFolder(file.getParent(), true);
471                             }
472                                                 }
473
474                                                 @Override
475                                                 public void onError(Throwable t) {
476                                                         app.setError(t);
477                                     app.displayError("System error modifying file:" + t.getMessage());
478                                                 }
479
480                                                 @Override
481                                                 protected void onUnauthorized(Response response) {
482                                                         app.sessionExpired();
483                                                 }
484                                         };
485                                         headFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
486                                         Scheduler.get().scheduleDeferred(headFile);
487                 }
488
489                 @Override
490                 public void onError(Throwable t) {
491                                         app.setError(t);
492                     app.displayError("System error modifying file:" + t.getMessage());
493                 }
494
495                                 @Override
496                                 protected void onUnauthorized(Response response) {
497                                         app.sessionExpired();
498                                 }
499             };
500             updateFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
501             updateFile.setHeader(Const.X_OBJECT_PUBLIC, published.toString());
502             Scheduler.get().scheduleDeferred(updateFile);
503         }
504         else if (!app.isMySharedSelected())
505             app.updateFolder(file.getParent(), true, new Command() {
506                                 @Override
507                                 public void execute() {
508                                         if (file.isSharedOrPublished()) {
509                         app.updateMySharedRoot();
510                     }
511                                 }
512                         }, true);
513         else
514                 app.updateSharedFolder(file.getParent(), true);
515     }
516     protected void updateMetaDataForPublicSharing(Boolean published) {
517         updateMetaDataForPublicSharing(
518             Pithos.getStorageAPIURL(),
519             app.getUserID(),
520             file.getUri() + Const.QUESTION_MARK_UPDATE_EQ,
521             published
522         );
523     }
524
525     protected void updateMetaDataForPrivateSharing(String api, String owner, final String path, final Map<String, Boolean[]> newPermissions) {
526         if (newPermissions != null) {
527             PostRequest updateFile = new PostRequest(api, owner, path) {
528                 @Override
529                 public void onSuccess(Resource result) {
530                     HeadRequest<File> headFile = new HeadRequest<File>(File.class, Pithos.getStorageAPIURL(), file.getOwnerID(), path, file) {
531
532                         @Override
533                         public void onSuccess(File _result) {
534                             showLinkForPrivateSharing();
535                             if (!app.isMySharedSelected())
536                                 app.updateFolder(file.getParent(), true, new Command() {
537
538                                     @Override
539                                     public void execute() {
540                                         app.updateMySharedRoot();
541                                     }
542                                 }, true);
543                             else
544                                 app.updateSharedFolder(file.getParent(), true);
545                         }
546
547                         @Override
548                         public void onError(Throwable t) {
549                             app.setError(t);
550                             app.displayError("System error modifying file:" + t.getMessage());
551                         }
552
553                         @Override
554                         protected void onUnauthorized(Response response) {
555                             app.sessionExpired();
556                         }
557                     };
558                     headFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
559                     Scheduler.get().scheduleDeferred(headFile);
560                 }
561
562                 @Override
563                 public void onError(Throwable t) {
564                     app.setError(t);
565                     app.displayError("System error modifying file:" + t.getMessage());
566                 }
567
568                 @Override
569                 protected void onUnauthorized(Response response) {
570                     app.sessionExpired();
571                 }
572             };
573             updateFile.setHeader(Const.X_AUTH_TOKEN, app.getUserToken());
574
575             String readPermHeader = Const.READ_EQ;
576             String writePermHeader = Const.WRITE_EQ;
577             for (String u : newPermissions.keySet()) {
578                 Boolean[] p = newPermissions.get(u);
579                 if (p[0] != null && p[0]) {
580                     readPermHeader += u + Const.COMMA;
581                 }
582                 if (p[1] != null && p[1]) {
583                     writePermHeader += u + Const.COMMA;
584                 }
585             }
586             if (readPermHeader.endsWith(Const.EQ)) {
587                 readPermHeader = "";
588             }
589             else if (readPermHeader.endsWith(Const.COMMA)) {
590                 readPermHeader = readPermHeader.substring(0, readPermHeader.length() - 1);
591             }
592             if (writePermHeader.endsWith(Const.EQ)) {
593                 writePermHeader = "";
594             }
595             else if (writePermHeader.endsWith(Const.COMMA)) {
596                 writePermHeader = writePermHeader.substring(0, writePermHeader.length() - 1);
597             }
598             String permHeader = readPermHeader +
599                 ((readPermHeader.length()  > 0 && writePermHeader.length() > 0) ?  Const.SEMI : "") + writePermHeader;
600             if (permHeader.length() == 0) {
601                 permHeader=Const.TILDE;
602             }
603             else {
604                 permHeader = URL.encodePathSegment(permHeader);
605             }
606             updateFile.setHeader(Const.X_OBJECT_SHARING, permHeader);
607             Scheduler.get().scheduleDeferred(updateFile);
608         }
609         else if (!app.isMySharedSelected()) {
610             app.updateFolder(file.getParent(), true, new Command() {
611                 @Override
612                 public void execute() {
613                     if (file.isSharedOrPublished())
614                         app.updateMySharedRoot();
615                 }
616             }, true);
617         }
618         else {
619             app.updateSharedFolder(file.getParent(), true);
620         }
621     }
622     protected void updateMetaDataForPrivateSharing() {
623         updateMetaDataForPrivateSharing(
624             Pithos.getStorageAPIURL(),
625             app.getUserID(),
626             file.getUri() + Const.QUESTION_MARK_UPDATE_EQ,
627             permList.getPermissions()
628         );
629     }
630
631     @Override
632         protected void onPreviewNativeEvent(NativePreviewEvent preview) {
633             super.onPreviewNativeEvent(preview);
634
635             NativeEvent evt = preview.getNativeEvent();
636             if (evt.getType().equals(Const.EVENT_TYPE_KEYDOWN) &&
637             evt.getKeyCode() == KeyCodes.KEY_ENTER) {
638
639             closeDialog();
640         }
641         }
642
643     @Override
644     protected boolean accept() {
645         return true;
646     }
647 }