001/*
002 *  Copyright 2020 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.cms.schedule;
017
018import java.io.IOException;
019import java.nio.file.DirectoryStream;
020import java.nio.file.Files;
021import java.nio.file.Path;
022import java.time.Instant;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.quartz.JobExecutionContext;
027
028import org.ametys.core.file.FileHelper;
029import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable;
030
031/**
032 * Abstract schedulable to delete files or folders.
033 */
034public abstract class AbstractDeleteFilesSchedulable extends AbstractStaticSchedulable
035{
036    /** File Helper */
037    protected FileHelper _fileHelper;
038    
039    @Override
040    public void service(ServiceManager manager) throws ServiceException
041    {
042        super.service(manager);
043        _fileHelper = (FileHelper) manager.lookup(FileHelper.ROLE);
044    }
045    
046    /**
047     * Get the needed values so the script can run
048     * @param context the context
049     * @return the configuration with needed values for deletion
050     */
051    protected abstract DeleteFilesConfiguration _getConfiguration(JobExecutionContext context);
052    
053    @Override
054    public void execute(JobExecutionContext context) throws Exception
055    {
056        DeleteFilesConfiguration configuration = _getConfiguration(context);
057        
058        Path root = configuration.getRootPath();
059        if (root == null)
060        {
061            throw new IOException("The path is empty.");
062        }
063        
064        if (Files.exists(root))
065        {
066            DirectoryStream.Filter<Path> fileFilter = _createFileFilter(configuration);
067            
068            if (configuration.deleteRoot() || Files.isRegularFile(root))
069            {
070                _delete(root, configuration, fileFilter);
071            }
072            else
073            {
074                try (DirectoryStream<Path> entries = Files.newDirectoryStream(root))
075                {
076                    for (Path entry : entries)
077                    {
078                        _delete(entry, configuration, fileFilter);
079                    }
080                }
081            }
082        }
083    }
084    
085    /**
086     * Delete all files corresponding to the file filter into the file tree.
087     * @param path the path to delete (can be a file or a directory)
088     * @param configuration the configuration for deletion
089     * @param fileFilter the file filter to apply
090     * @throws IOException if an error occured while exploring or deleting files
091     */
092    protected void _delete(Path path, DeleteFilesConfiguration configuration, DirectoryStream.Filter<Path> fileFilter) throws IOException
093    {
094        _fileHelper.delete(path, fileFilter, configuration.deleteRecursively(), configuration.deleteEmptyFolders());
095    }
096    
097    /**
098     * Create the file filter to delete files.
099     * @param configuration The delete files configuration
100     * @return a file filter
101     */
102    protected DirectoryStream.Filter<Path> _createFileFilter(DeleteFilesConfiguration configuration)
103    {
104        return new DirectoryStream.Filter<>()
105        {
106            @Override
107            public boolean accept(Path entry) throws IOException
108            {
109                return Files.getLastModifiedTime(entry)
110                    .toInstant()
111                    .isBefore(configuration.getAgeLimit());
112            }
113        };
114    }
115    
116    /**
117     * Inner class to store configuration for deletion.
118     */
119    protected static class DeleteFilesConfiguration
120    {
121        private Path _rootPath;
122        private boolean _deleteRoot;
123        private boolean _deleteRecursively;
124        private boolean _deleteEmptyFolders;
125        private Instant _ageLimit;
126        
127        /**
128         * Constructor.
129         * Default values:
130         *  - ageLimit = now
131         *  - deleteRoot = false
132         *  - deleteRecursively = true
133         *  - deleteEmptyFolders = true
134         * @param rootPath The root path for deletion
135         */
136        public DeleteFilesConfiguration(Path rootPath)
137        {
138            _rootPath = rootPath;
139            _ageLimit = Instant.now();
140            _deleteRoot = false;
141            _deleteRecursively = true;
142            _deleteEmptyFolders = true;
143        }
144        
145        /**
146         * Get the root folder or file to delete
147         * @return a Directory or a file, that will be deleted/emptied
148         */
149        public Path getRootPath()
150        {
151            return _rootPath;
152        }
153        
154        /**
155         * Set to <code>true</code> to delete the root folder.
156         * @param deleteRoot <code>true</code> to delete the root folde
157         */
158        public void setDeleteRoot(boolean deleteRoot)
159        {
160            _deleteRoot = deleteRoot;
161        }
162        
163        /**
164         * Do the root needs to be deleted or emptied ?
165         * @return <code>true</code> to delete the root folder
166         */
167        public boolean deleteRoot()
168        {
169            return _deleteRoot;
170        }
171        
172        /**
173         * Set to <code>true</code> to delete files recursively.
174         * @param deleteRecursively <code>true</code> to delete files recursively
175         */
176        public void setDeleteRecursively(boolean deleteRecursively)
177        {
178            _deleteRecursively = deleteRecursively;
179        }
180        
181        /**
182         * Do the folders needs to be deleted recursively ?
183         * @return <code>true</code> to delete files recursively
184         */
185        public boolean deleteRecursively()
186        {
187            return _deleteRecursively;
188        }
189        
190        /**
191         * Set to <code>true</code> to delete empty folders.
192         * @param deleteEmptyFolders <code>true</code> to delete empty folders
193         */
194        public void setDeleteEmptyFolders(boolean deleteEmptyFolders)
195        {
196            _deleteEmptyFolders = deleteEmptyFolders;
197        }
198        
199        /**
200         * When a folder is empty, do it needs to be deleted ?
201         * @return <code>true</code> to delete empty folders
202         */
203        public boolean deleteEmptyFolders()
204        {
205            return _deleteEmptyFolders;
206        }
207        
208        /**
209         * Set the age limit of files to delete.
210         * @param ageLimit The age limit
211         */
212        public void setAgeLimit(Instant ageLimit)
213        {
214            _ageLimit = ageLimit;
215        }
216        
217        /**
218         * Only delete files older than this instant.
219         * @return instant, only older files will be deleted
220         */
221        public Instant getAgeLimit()
222        {
223            return _ageLimit;
224        }
225    }
226}