added notification on upload
added case create on history git-svn-id: http://svn.d4science-ii.research-infrastructures.eu/gcube/trunk/portlets/user/workspace-tree-widget@76327 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
parent
05b27f5011
commit
8584bc4647
|
@ -99,6 +99,7 @@ import org.gcube.portlets.user.workspace.client.view.tree.AsyncTreePanel;
|
|||
import org.gcube.portlets.user.workspace.client.view.windows.BulkCreatorWindow;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.DialogAddFolderAndSmart;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.DialogAddFolderAndSmart.AddType;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.accounting.WindowAccountingInfo;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.DialogAddUrl;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.DialogGetInfo;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.DialogText;
|
||||
|
@ -108,7 +109,6 @@ import org.gcube.portlets.user.workspace.client.view.windows.InfoDisplay;
|
|||
import org.gcube.portlets.user.workspace.client.view.windows.MessageBoxAlert;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.MessageBoxConfirm;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.MessageBoxInfo;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.WindowAccountingInfo;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.WindowImagePreview;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.WindowOpenUrl;
|
||||
import org.gcube.portlets.user.workspace.client.workspace.GWTWorkspaceItem;
|
||||
|
|
|
@ -274,5 +274,8 @@ public interface Icons extends ClientBundle {
|
|||
|
||||
@Source("icons/cut.png")
|
||||
ImageResource cut();
|
||||
|
||||
@Source("icons/new_create.png")
|
||||
ImageResource createNew();
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,11 @@ public class Resources {
|
|||
return AbstractImagePrototype.create(ICONS.table());
|
||||
}
|
||||
|
||||
public static AbstractImagePrototype getIconCreateNew(){
|
||||
|
||||
return AbstractImagePrototype.create(ICONS.createNew());
|
||||
}
|
||||
|
||||
public static AbstractImagePrototype getIconPpt(){
|
||||
|
||||
return AbstractImagePrototype.create(ICONS.ppt());
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -1,4 +1,4 @@
|
|||
package org.gcube.portlets.user.workspace.client.view.windows;
|
||||
package org.gcube.portlets.user.workspace.client.view.windows.accounting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -103,16 +103,20 @@ public class AccoutingInfoContainer extends LayoutContainer {
|
|||
public Object render(ModelData model, String property, ColumnData config, int rowIndex, int colIndex,
|
||||
ListStore<ModelData> store, Grid<ModelData> grid) {
|
||||
|
||||
if(model.get(OPERATION).equals(GxtAccountingEntryType.READ))
|
||||
return Resources.getIconRead().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.CUT))
|
||||
return Resources.getIconCut().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.PASTE))
|
||||
return Resources.getIconPaste().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.REMOVE))
|
||||
return Resources.getIconCancel().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.RENAME))
|
||||
return Resources.getIconRenameItem().createImage();
|
||||
if(model.get(OPERATION)!=null){
|
||||
if(model.get(OPERATION).equals(GxtAccountingEntryType.CREATE))
|
||||
return Resources.getIconCreateNew().createImage();
|
||||
if(model.get(OPERATION).equals(GxtAccountingEntryType.READ))
|
||||
return Resources.getIconRead().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.CUT))
|
||||
return Resources.getIconCut().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.PASTE))
|
||||
return Resources.getIconPaste().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.REMOVE))
|
||||
return Resources.getIconCancel().createImage();
|
||||
else if(model.get(OPERATION).equals(GxtAccountingEntryType.RENAME))
|
||||
return Resources.getIconRenameItem().createImage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package org.gcube.portlets.user.workspace.client.view.windows;
|
||||
package org.gcube.portlets.user.workspace.client.view.windows.accounting;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -108,6 +108,12 @@ public class GWTWorkspaceBuilder {
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param logger
|
||||
*/
|
||||
public GWTWorkspaceBuilder() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used in test mode
|
||||
|
@ -872,7 +878,7 @@ public class GWTWorkspaceBuilder {
|
|||
* @param listPortalLogin
|
||||
* @return
|
||||
*/
|
||||
protected List<InfoContactModel> buildGxtInfoContactsFromPortalLogins(List<String> listPortalLogin){
|
||||
public List<InfoContactModel> buildGxtInfoContactsFromPortalLogins(List<String> listPortalLogin){
|
||||
|
||||
List<InfoContactModel> listContact = new ArrayList<InfoContactModel>();
|
||||
for (String portalLogin : listPortalLogin)
|
||||
|
@ -1655,6 +1661,17 @@ public class GWTWorkspaceBuilder {
|
|||
|
||||
switch (accountingEntry.getEntryType()) {
|
||||
|
||||
|
||||
case CREATE:
|
||||
|
||||
if(gxtEntryType==null || gxtEntryType.equals(GxtAccountingEntryType.ALLWITHOUTREAD) || gxtEntryType.equals(GxtAccountingEntryType.CREATE)){
|
||||
|
||||
af.setOperation(GxtAccountingEntryType.CREATE);
|
||||
af.setDescription("create by "+user.getName());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case READ:
|
||||
|
||||
if(gxtEntryType==null || gxtEntryType.equals(GxtAccountingEntryType.READ)){
|
||||
|
|
|
@ -14,6 +14,7 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.apache.commons.fileupload.FileItemFactory;
|
||||
|
@ -40,6 +41,7 @@ import org.gcube.portlets.user.homelibrary.util.MimeTypeUtil;
|
|||
import org.gcube.portlets.user.homelibrary.util.WorkspaceUtil;
|
||||
import org.gcube.portlets.user.homelibrary.util.zip.UnzipUtil;
|
||||
import org.gcube.portlets.user.workspace.client.view.windows.UploadResultMessage;
|
||||
import org.gcube.portlets.user.workspace.server.notifications.NotificationsUtil;
|
||||
import org.gcube.portlets.user.workspace.server.util.Util;
|
||||
|
||||
/**
|
||||
|
@ -201,7 +203,7 @@ public class UploadServlet extends HttpServlet {
|
|||
// System.out.println("itemwithoutext " +itemwithoutext);
|
||||
|
||||
logger.trace("createTemplate: "+itemwithoutext);
|
||||
createTemplate(wa, itemwithoutext, uploadItem.getInputStream(), destinationFolder, response);
|
||||
createTemplate(request.getSession(), wa, itemwithoutext, uploadItem.getInputStream(), destinationFolder, response);
|
||||
|
||||
}else if(isZipFile && (extension.compareToIgnoreCase(D4SR)==0)){ //Create REPORT
|
||||
|
||||
|
@ -210,11 +212,11 @@ public class UploadServlet extends HttpServlet {
|
|||
// System.out.println("itemwithoutext " +itemwithoutext);
|
||||
|
||||
logger.trace("createReport: "+itemwithoutext);
|
||||
createReport(wa, itemwithoutext, uploadItem.getInputStream(), destinationFolder, response);
|
||||
createReport(request.getSession(), wa, itemwithoutext, uploadItem.getInputStream(), destinationFolder, response);
|
||||
|
||||
}else{ //CREATE AN EXTERNAL FILE
|
||||
|
||||
createExternalFile(itemName, uploadItem, destinationFolder, contentType, response);
|
||||
createExternalFile(request.getSession(), wa, itemName, uploadItem, destinationFolder, contentType, response);
|
||||
}
|
||||
|
||||
}else {//IS ARCHIVE UPLOAD
|
||||
|
@ -222,11 +224,13 @@ public class UploadServlet extends HttpServlet {
|
|||
if (MimeTypeUtil.isZipContentType(contentType)){
|
||||
logger.trace("Unziping content");
|
||||
UnzipUtil.unzip(destinationFolder, uploadItem.getInputStream(), itemName);
|
||||
|
||||
//TODO NOTIFY UPLOAD ARCHIVE
|
||||
sendMessage(response, "Archive "+uploadItem.getName()+" imported correctly in "+destinationFolder.getPath());
|
||||
} else
|
||||
createExternalFile(itemName, uploadItem, destinationFolder, contentType, response);
|
||||
createExternalFile(request.getSession(), wa, itemName, uploadItem, destinationFolder, contentType, response);
|
||||
}
|
||||
|
||||
|
||||
uploadItem.delete();
|
||||
} catch (InsufficientPrivilegesException e) {
|
||||
logger.error("Error creating elements", e);
|
||||
|
@ -243,20 +247,55 @@ public class UploadServlet extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
private void createExternalFile(String itemName, FileItem uploadItem, WorkspaceFolder destinationFolder, String contentType, HttpServletResponse response) throws InternalErrorException, InsufficientPrivilegesException, ItemAlreadyExistException, IOException {
|
||||
/**
|
||||
*
|
||||
* @param httpSession
|
||||
* @param workspace
|
||||
* @param itemId
|
||||
* @param destinationFolderId
|
||||
*/
|
||||
private void notifyUploadInSharedFolder(final HttpSession httpSession, final Workspace workspace, final String itemId, final String destinationFolderId){
|
||||
|
||||
new Thread(){
|
||||
|
||||
public void run() {
|
||||
|
||||
WorkspaceItem sourceItem;
|
||||
try {
|
||||
sourceItem = workspace.getItem(itemId);
|
||||
String sourceSharedId = sourceItem.getIdSharedFolder();
|
||||
WorkspaceItem folderDestinationItem = workspace.getItem(destinationFolderId);
|
||||
NotificationsUtil.checkNotifyAddItemToShare(httpSession, sourceItem, sourceSharedId, folderDestinationItem);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in notifyUploadInSharedFolder", e);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void createExternalFile(HttpSession httpSession, Workspace wa, String itemName, FileItem uploadItem, WorkspaceFolder destinationFolder, String contentType, HttpServletResponse response) throws InternalErrorException, InsufficientPrivilegesException, ItemAlreadyExistException, IOException {
|
||||
|
||||
//we need to recalculate the item name
|
||||
itemName = WorkspaceUtil.getUniqueName(uploadItem.getName(), destinationFolder);
|
||||
FolderItem createdItem = WorkspaceUtil.createExternalFile(destinationFolder, itemName, "", contentType, uploadItem.getInputStream());
|
||||
|
||||
notifyUploadInSharedFolder(httpSession,wa,createdItem.getId(),destinationFolder.getId());
|
||||
|
||||
sendMessage(response, "File "+createdItem.getName()+" imported correctly in "+destinationFolder.getPath());
|
||||
}
|
||||
|
||||
|
||||
private void createReport(Workspace wa, String itemName, InputStream stream, WorkspaceFolder destinationFolder, HttpServletResponse response) throws InsufficientPrivilegesException, ItemAlreadyExistException, InternalErrorException, IOException{
|
||||
private void createReport(HttpSession httpSession, Workspace wa, String itemName, InputStream stream, WorkspaceFolder destinationFolder, HttpServletResponse response) throws InsufficientPrivilegesException, ItemAlreadyExistException, InternalErrorException, IOException{
|
||||
|
||||
try {
|
||||
itemName = WorkspaceUtil.getUniqueName(itemName, destinationFolder);
|
||||
Report report = wa.createReport(itemName, "", Calendar.getInstance(), Calendar.getInstance(), "", "", "", 0, "", stream, destinationFolder.getId());
|
||||
|
||||
notifyUploadInSharedFolder(httpSession,wa,report.getId(),destinationFolder.getId());
|
||||
|
||||
sendMessage(response, "File "+report.getName()+" imported correctly in "+destinationFolder.getPath());
|
||||
|
||||
} catch (WrongDestinationException e) {
|
||||
|
@ -269,11 +308,14 @@ public class UploadServlet extends HttpServlet {
|
|||
|
||||
}
|
||||
|
||||
private void createTemplate(Workspace wa, String itemName, InputStream stream, WorkspaceFolder destinationFolder, HttpServletResponse response) throws InsufficientPrivilegesException, ItemAlreadyExistException, InternalErrorException, IOException{
|
||||
private void createTemplate(HttpSession httpSession, Workspace wa, String itemName, InputStream stream, WorkspaceFolder destinationFolder, HttpServletResponse response) throws InsufficientPrivilegesException, ItemAlreadyExistException, InternalErrorException, IOException{
|
||||
|
||||
try {
|
||||
itemName = WorkspaceUtil.getUniqueName(itemName, destinationFolder);
|
||||
ReportTemplate template = wa.createReportTemplate(itemName, "", Calendar.getInstance(), Calendar.getInstance(), "", "", 0, "", stream, destinationFolder.getId());
|
||||
|
||||
notifyUploadInSharedFolder(httpSession,wa,template.getId(),destinationFolder.getId());
|
||||
|
||||
sendMessage(response, "File "+template.getName()+" imported correctly in "+destinationFolder.getPath());
|
||||
|
||||
} catch (WrongDestinationException e) {
|
||||
|
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.gcube.portlets.user.workspace.server.notifications;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.gcube.portlets.user.homelibrary.home.workspace.Workspace;
|
||||
import org.gcube.portlets.user.homelibrary.home.workspace.WorkspaceFolder;
|
||||
import org.gcube.portlets.user.homelibrary.home.workspace.WorkspaceItem;
|
||||
import org.gcube.portlets.user.homelibrary.home.workspace.WorkspaceItemType;
|
||||
import org.gcube.portlets.user.homelibrary.home.workspace.WorkspaceSharedFolder;
|
||||
import org.gcube.portlets.user.workspace.client.model.InfoContactModel;
|
||||
import org.gcube.portlets.user.workspace.server.GWTWorkspaceBuilder;
|
||||
import org.gcube.portlets.user.workspace.server.util.Util;
|
||||
|
||||
/**
|
||||
* @author Francesco Mangiacrapa francesco.mangiacrapa@isti.cnr.it
|
||||
* @May 27, 2013
|
||||
*
|
||||
*/
|
||||
public class NotificationsUtil {
|
||||
|
||||
|
||||
protected static Logger logger = Logger.getLogger(NotificationsUtil.class);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param httpSession
|
||||
* @param sourceItem
|
||||
* @param sourceSharedId
|
||||
* @param folderDestinationItem
|
||||
*/
|
||||
public static void checkNotifyAddItemToShare(HttpSession httpSession, final WorkspaceItem sourceItem, final String sourceSharedId, final WorkspaceItem folderDestinationItem) {
|
||||
|
||||
logger.trace("checkNotifyAddItemToShare");
|
||||
|
||||
if(folderDestinationItem!=null){
|
||||
|
||||
try{
|
||||
//if folder destination is shared folder
|
||||
if(folderDestinationItem.isShared()){ //Notify Added Item To Sharing?
|
||||
|
||||
logger.trace("checkNotifyAddItemToShare source item: "+sourceItem.getName()+" sourceSharedId: "+sourceSharedId + " folder destination: "+folderDestinationItem.getName() + " folder destination shared folder id: "+folderDestinationItem.getIdSharedFolder());
|
||||
|
||||
//share condition is true if source shared folder is not null
|
||||
boolean shareChangeCondition = sourceSharedId==null?false:true;
|
||||
|
||||
//System.out.println("shareChangeCondition add item: "+ shareChangeCondition);
|
||||
|
||||
logger.trace("shareChangeCondition add item: "+shareChangeCondition);
|
||||
|
||||
//if shareChangeCondition is true.. notifies added item to sharing
|
||||
if(shareChangeCondition){
|
||||
|
||||
Workspace workspace = Util.getWorkspace(httpSession);
|
||||
|
||||
List<InfoContactModel> listContacts = getListUserSharedByFolderSharedId(workspace, folderDestinationItem.getIdSharedFolder());
|
||||
|
||||
WorkspaceItem destinationSharedFolder = workspace.getItem(folderDestinationItem.getIdSharedFolder());
|
||||
|
||||
NotificationsProducer np = new NotificationsProducer(Util.getAslSession(httpSession));
|
||||
|
||||
np.notifyAddedItemToSharing(listContacts, sourceItem, (WorkspaceFolder) destinationSharedFolder);
|
||||
|
||||
logger.trace("The notifies was sent correctly");
|
||||
// np.notifyAddedItemToSharing(listContacts, (WorkspaceFolder) folderDestinationItem);
|
||||
}
|
||||
}
|
||||
else
|
||||
logger.trace("folder destination is not shared");
|
||||
|
||||
}catch (Exception e) {
|
||||
logger.error("An error occurred in verifyNotifyAddItemToShare ",e);
|
||||
}
|
||||
}else
|
||||
logger.warn("The notifies is failure in verifyNotifyAddItemToShare because folder destination item is null");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param workspace
|
||||
* @param idSharedFolder
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static List<InfoContactModel> getListUserSharedByFolderSharedId(Workspace workspace, String idSharedFolder) throws Exception {
|
||||
|
||||
logger.trace("getListUserSharedByFolderSharedId "+ idSharedFolder);
|
||||
|
||||
try {
|
||||
|
||||
WorkspaceItem wsItem = workspace.getItem(idSharedFolder);
|
||||
|
||||
if(Util.isASharedFolder(wsItem)){
|
||||
|
||||
WorkspaceSharedFolder wsFolder = (WorkspaceSharedFolder) wsItem;
|
||||
|
||||
GWTWorkspaceBuilder builder = new GWTWorkspaceBuilder();
|
||||
|
||||
List<String> listPortalLogin = wsFolder.getUsers();
|
||||
|
||||
logger.trace("getListUserSharedByFolderSharedId return "+ listPortalLogin.size() + " user");
|
||||
|
||||
return builder.buildGxtInfoContactsFromPortalLogins(listPortalLogin);
|
||||
|
||||
}
|
||||
else{
|
||||
logger.trace("the item with id: "+idSharedFolder+ " is not "+WorkspaceItemType.SHARED_FOLDER);
|
||||
|
||||
//DEBUG
|
||||
//System.out.println("the item with id: "+folderSharedId+ " is not "+WorkspaceItemType.SHARED_FOLDER);
|
||||
}
|
||||
return new ArrayList<InfoContactModel>();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in getListUserSharedByItemId ", e);
|
||||
throw new Exception(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param httpSession
|
||||
* @param sourceItemIsShared
|
||||
* @param sourceItem
|
||||
* @param sourceSharedId
|
||||
* @param folderDestinationItem
|
||||
*/
|
||||
|
||||
public static void checkNotifyRemoveItemToShare(HttpSession httpSession, final boolean sourceItemIsShared, final WorkspaceItem sourceItem, final String sourceSharedId, final WorkspaceItem folderDestinationItem) {
|
||||
|
||||
logger.trace("checkNotifyRemoveItemToShare:");
|
||||
|
||||
try{
|
||||
|
||||
if(folderDestinationItem!=null){
|
||||
|
||||
String idSharedFolder = folderDestinationItem.getIdSharedFolder()!=null?folderDestinationItem.getIdSharedFolder():"";
|
||||
|
||||
//share condition is true if source shared folder is not equal to destination shared folder
|
||||
boolean shareChangeCondition = sourceSharedId==null?false:(sourceSharedId.compareTo(idSharedFolder)!=0);
|
||||
|
||||
logger.trace("checkNotifyRemoveItemToShare source item: "+sourceItem.getName()+" sourceSharedId: "+sourceSharedId + " folder destination: "+folderDestinationItem.getName() +" sourceItemIsShared: "+sourceItemIsShared);
|
||||
|
||||
// System.out.println("shareChangeCondition remove item: "+ shareChangeCondition);
|
||||
|
||||
logger.trace("shareChangeCondition remove item: "+ shareChangeCondition);
|
||||
|
||||
//Notify Removed Item To Sharing?
|
||||
//if source Item is shared and folder destination is not shared or shareChangeCondition is true.. notifies removed item to sharing
|
||||
if(sourceItemIsShared && (!folderDestinationItem.isShared() || shareChangeCondition)){
|
||||
|
||||
Workspace workspace = Util.getWorkspace(httpSession);
|
||||
|
||||
//get contacts
|
||||
List<InfoContactModel> listContacts = getListUserSharedByFolderSharedId(workspace, sourceSharedId);
|
||||
|
||||
|
||||
WorkspaceItem sourceSharedFolder = workspace.getItem(sourceSharedId);
|
||||
|
||||
//System.out.println(" name sourceSharedFolder: "+ sourceSharedFolder.getName());
|
||||
|
||||
NotificationsProducer np = new NotificationsProducer(Util.getAslSession(httpSession));
|
||||
|
||||
np.notifyRemovedItemToSharing(listContacts, sourceItem, (WorkspaceFolder) sourceSharedFolder);
|
||||
|
||||
logger.trace("The notifies was sent correctly");
|
||||
|
||||
}
|
||||
|
||||
}else
|
||||
logger.warn("The notifies is failure in verifyNotifyRemoveItemToShare because folder destination item is null");
|
||||
|
||||
}catch (Exception e) {
|
||||
logger.error("An error occurred in checkNotifyRemoveItemToShare ",e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -45,7 +45,7 @@ public class Util {
|
|||
// public static final String TEST_SCOPE = "/gcube/devsec";
|
||||
// public static final String TEST_USER = "pasquale.pagano";
|
||||
|
||||
public static final String TEST_SCOPE = "/gcube";
|
||||
public static final String TEST_SCOPE = "/gcube/devsec";
|
||||
// public static final String TEST_USER = "federico.defaveri";
|
||||
// public static final String TEST_USER = "massimiliano.assante";
|
||||
// public static final String TEST_USER = "pasquale.pagano";
|
||||
|
|
Loading…
Reference in New Issue