001/*
002 *  Copyright 2023 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.nextcloud;
017
018import java.io.IOException;
019import java.util.Iterator;
020import java.util.List;
021import java.util.Optional;
022
023import org.apache.avalon.framework.activity.Initializable;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.cocoon.ProcessingException;
028import org.apache.cocoon.generation.AbstractGenerator;
029import org.apache.cocoon.xml.AttributesImpl;
030import org.apache.cocoon.xml.XMLUtils;
031import org.apache.commons.lang3.StringUtils;
032import org.xml.sax.SAXException;
033
034import org.ametys.core.util.DateUtils;
035import org.ametys.plugins.explorer.resources.generators.ResourcesExplorerGenerator;
036import org.ametys.plugins.extrausermgt.oauth.OAuthProvider;
037import org.ametys.plugins.extrausermgt.oauth.OauthProviderExtensionPoint;
038import org.ametys.runtime.config.Config;
039
040import com.github.sardine.DavResource;
041import com.github.sardine.Sardine;
042import com.github.sardine.impl.SardineImpl;
043import com.nimbusds.oauth2.sdk.token.AccessToken;
044
045/**
046 * Generate SAX event to represent the content of a remote Nextcloud repository.
047 * 
048 * This generator is intended to produced an XML equivalent to one produced by {@link ResourcesExplorerGenerator}.
049 * The main difference will be in the attribute available. No id or icon cls can be computed.
050 */
051public class NextcloudExplorerGenerator extends AbstractGenerator implements Serviceable, Initializable
052{
053    private String _remoteUrl;
054    private OauthProviderExtensionPoint _oauthEP;
055
056    public void service(ServiceManager manager) throws ServiceException
057    {
058        _oauthEP = (OauthProviderExtensionPoint) manager.lookup(OauthProviderExtensionPoint.ROLE);
059    }
060
061    public void initialize() throws Exception
062    {
063        _remoteUrl = Config.getInstance().getValue("nextcloud.remote.url");
064    }
065    
066    public void generate() throws IOException, SAXException, ProcessingException
067    {
068        OAuthProvider nextcloudProvider = _oauthEP.getExtension("nextcloud");
069        Optional<AccessToken> accessToken = nextcloudProvider.getStoredAccessToken();
070        Optional<String> userId = nextcloudProvider.getStoredCustomParameter("user_id");
071        if (accessToken.isEmpty() || userId.isEmpty())
072        {
073            contentHandler.startDocument();
074            XMLUtils.createElement(contentHandler, "not-connected");
075            contentHandler.endDocument();
076            return;
077        }
078        
079        Sardine webDavClient = new SardineImpl(accessToken.get().getValue());
080        
081        contentHandler.startDocument();
082        
083        saxRemoteFolder("/remote.php/dav/files/" + userId.get() + "/", webDavClient);
084        
085        contentHandler.endDocument();
086    }
087
088    private void saxRemoteFolder(String path, Sardine davClient) throws ProcessingException, SAXException
089    {
090        try
091        {
092            // Retrieve all resources
093            List<DavResource> resources = davClient.list(_remoteUrl + path);
094            DavResource target = null;
095            for (Iterator iterator = resources.iterator(); iterator.hasNext() && target == null;)
096            {
097                DavResource resource = (DavResource) iterator.next();
098                if (StringUtils.equals(resource.getPath(), path))
099                {
100                    resources.remove(resource);
101                    target = resource;
102                }
103            }
104            if (target != null)
105            {
106                saxDirectory(target, resources, davClient);
107            }
108            else
109            {
110                throw new ProcessingException("Failed to retrieve information for remote folder at path " + path);
111            }
112        }
113        catch (IOException e)
114        {
115            throw new ProcessingException("Failed to list content of remote folder '" + path + "'", e);
116        }
117        
118    }
119
120    private void saxDirectory(DavResource directory, List<DavResource> children, Sardine davClient) throws SAXException, ProcessingException
121    {
122        AttributesImpl childAtts = new AttributesImpl();
123        
124        childAtts.addCDATAAttribute("name", directory.getName());
125        
126        childAtts.addCDATAAttribute("path", directory.getPath());
127        childAtts.addCDATAAttribute("type", ResourcesExplorerGenerator.RESOURCE_COLLECTION);
128        
129        boolean hasResources = false;
130        boolean hasChildNodes = false;
131        for (Iterator iterator = children.iterator(); iterator.hasNext() && !(hasChildNodes && hasResources);)
132        {
133            DavResource davResource = (DavResource) iterator.next();
134            if (davResource.isDirectory())
135            {
136                hasChildNodes = true;
137            }
138            else
139            {
140                hasResources = true;
141            }
142        }
143        if (hasChildNodes)
144        {
145            childAtts.addCDATAAttribute("hasChildNodes", "true");
146        }
147        
148        if (hasResources)
149        {
150            childAtts.addCDATAAttribute("hasResources", "true");
151        }
152        XMLUtils.startElement(contentHandler, "Node", childAtts);
153        
154        for (DavResource child : children)
155        {
156            if (child.isDirectory())
157            {
158                saxRemoteFolder(child.getPath(), davClient);
159            }
160            else
161            {
162                saxResource(child);
163            }
164        }
165        
166        XMLUtils.endElement(contentHandler, "Node");
167        
168    }
169
170    private void saxResource(DavResource resource) throws SAXException
171    {
172        AttributesImpl childAtts = new AttributesImpl();
173        childAtts.addCDATAAttribute("href", _remoteUrl + resource.getHref().toString());
174        childAtts.addCDATAAttribute("name", resource.getName());
175        childAtts.addCDATAAttribute("mimetype", resource.getContentType());
176        childAtts.addCDATAAttribute("lastModified", DateUtils.dateToString(resource.getModified()));
177        childAtts.addCDATAAttribute("size", String.valueOf(resource.getContentLength()));
178        childAtts.addCDATAAttribute("type", ResourcesExplorerGenerator.RESOURCE);
179        childAtts.addCDATAAttribute("path", resource.getPath());
180        
181        XMLUtils.createElement(contentHandler, "Node", childAtts);
182    }
183}