001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
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 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
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 io.jboot.app;
017
018import io.jboot.app.config.JbootConfigManager;
019import io.jboot.utils.FileScanner;
020import io.jboot.utils.StrUtil;
021import org.apache.commons.io.FileUtils;
022
023import java.io.File;
024import java.io.IOException;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.List;
028
029
030public class JbootResourceLoader {
031
032    private String resourcePathName;
033    private List<FileScanner> scanners = new ArrayList<>();
034
035    public JbootResourceLoader() {
036        String pathName = JbootConfigManager.me().getConfigValue("jboot.app.resourcePathName");
037        this.resourcePathName = StrUtil.obtainDefault(pathName, "webapp");
038    }
039
040    public JbootResourceLoader(String resourcePathName) {
041        this.resourcePathName = StrUtil.requireNonBlank(resourcePathName, "Resource path name must not be blank.");
042    }
043
044
045    public void start() {
046        try {
047
048            URL url = JbootResourceLoader.class.getClassLoader().getResource("");
049            if (url == null || url.toString().endsWith(".jar!/")) {
050                return;
051            }
052
053            String classPath = url.toURI().getPath();
054            File srcRootPath = new File(classPath, "../..").getCanonicalFile();
055
056            while (new File(srcRootPath.getParent(), "pom.xml").exists()) {
057                srcRootPath = srcRootPath.getParentFile();
058            }
059
060            List<File> resourcesDirs = new ArrayList<>();
061            findResourcesPath(srcRootPath, resourcesDirs);
062
063            String targetPath = classPath.endsWith("/config")
064                    ? new File(classPath, "..").getCanonicalPath() : classPath;
065            for (File resourcesDir : resourcesDirs) {
066                startNewScanner(resourcesDir.getCanonicalFile(), targetPath);
067            }
068
069            Runtime.getRuntime().addShutdownHook(new Thread(JbootResourceLoader.this::stop));
070            System.err.println("JbootResourceLoader is started, and watching resource dir: " + resourcePathName);
071
072        } catch (Exception e) {
073            e.printStackTrace();
074        }
075    }
076
077    public void stop() {
078        scanners.forEach(FileScanner::stop);
079        System.out.println("JbootResourceLoader has stopped.");
080    }
081
082    private void findResourcesPath(File root, List<File> resourcesDirs) {
083        File[] dirs = root.listFiles(File::isDirectory);
084        if (dirs == null || dirs.length == 0) {
085            return;
086        }
087        for (File dir : dirs) {
088
089            File parentFile = dir.getParentFile();
090            if (parentFile == null) {
091                return;
092            }
093
094            if (dir.getName().equals(resourcePathName)
095                    && "main".equals(parentFile.getName())) {
096                resourcesDirs.add(dir);
097            } else {
098                findResourcesPath(dir, resourcesDirs);
099            }
100        }
101    }
102
103
104    private void startNewScanner(File resourcesDir, String classPath) throws IOException {
105        // main/webapp/
106        String path = "main" + File.separator + resourcePathName + File.separator;
107        FileScanner scanner = new FileScanner(resourcesDir.getCanonicalPath(), 5) {
108            @Override
109            public void onChange(String action, String file) {
110                if (FileScanner.ACTION_INIT.equals(action)) {
111                    return;
112                }
113
114                //忽略掉 windows 和 mac 下的临时文件
115                if (file.endsWith("~") || file.endsWith(".DS_Store")) {
116                    return;
117                }
118
119                int indexOf = file.indexOf(path);
120
121                File target = new File(classPath, resourcePathName + File.separator + file.substring(indexOf + path.length()));
122                System.err.println("JbootResourceLoader " + action + ": " + target);
123
124                //文件删除
125                if (FileScanner.ACTION_DELETE.equals(action)) {
126                    if (!target.delete()) {
127                        System.err.println("JbootResourceLoader can not delete file: " + target);
128                    }
129                }
130                //新增文件 或 修改文件
131                else {
132                    try {
133                        FileUtils.copyFile(new File(file), target);
134                    } catch (Exception e) {
135                        System.err.println("JbootResourceLoader copy file error: " + e.getMessage());
136                    }
137                }
138            }
139        };
140
141        scanner.start();
142        scanners.add(scanner);
143    }
144}