it.anyplace.sync.bep.IndexBrowser.java Source code

Java tutorial

Introduction

Here is the source code for it.anyplace.sync.bep.IndexBrowser.java

Source

/* 
 * Copyright (C) 2016 Davide Imbriaco
 *
 * This Java file is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package it.anyplace.sync.bep;

import com.google.common.base.Function;
import static com.google.common.base.Objects.equal;
import com.google.common.collect.Lists;
import it.anyplace.sync.core.beans.FileInfo;
import java.util.Collections;
import java.util.List;
import it.anyplace.sync.core.utils.PathUtils;
import static it.anyplace.sync.core.utils.PathUtils.*;
import java.util.Comparator;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.eventbus.Subscribe;
import it.anyplace.sync.core.utils.ExecutorUtils;
import java.io.Closeable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Strings.emptyToNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import it.anyplace.sync.core.interfaces.IndexRepository;
import static it.anyplace.sync.core.utils.FileInfoOrdering.ALPHA_ASC_DIR_FIRST;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author aleph
 */
public final class IndexBrowser implements Closeable {

    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final LoadingCache<String, List<FileInfo>> listFolderCache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            //        .weigher(new Weigher<String, List<FileInfo>>() {
            //            @Override
            //            public int weigh(String key, List<FileInfo> list) {
            //                return list.size();
            //            }
            //        })
            //        .maximumSize(1000)
            .build(new CacheLoader<String, List<FileInfo>>() {
                @Override
                public List<FileInfo> load(String path) throws Exception {
                    return doListFiles(path);
                }

            });
    private final LoadingCache<String, FileInfo> fileInfoCache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            //        .maximumSize(1000)
            //        .weigher(new Weigher<String, FileInfo>() {
            //            @Override
            //            public int weigh(String key, FileInfo fileInfo) {
            //                return fileInfo.getBlocks().size();
            //            }
            //        })
            .build(new CacheLoader<String, FileInfo>() {
                @Override
                public FileInfo load(String path) throws Exception {
                    return doGetFileInfoByAbsolutePath(path);
                }
            });

    private final String folder;
    private final IndexRepository indexRepository;
    private final IndexHandler indexHandler;
    private String currentPath;
    private final boolean includeParentInList, allowParentInRoot;
    private final FileInfo PARENT_FILE_INFO, ROOT_FILE_INFO;
    private Comparator<FileInfo> ordering;
    private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    private final Object indexHandlerEventListener = new Object() {
        @Subscribe
        public void handleIndexChangedEvent(IndexHandler.IndexChangedEvent event) {
            if (equal(event.getFolder(), folder)) {
                invalidateCache();
            }
        }

    };
    private final Set<String> preloadJobs = Sets.newLinkedHashSet();
    private final Object preloadJobsLock = new Object();

    private IndexBrowser(IndexRepository indexRepository, IndexHandler indexHandler, String folder,
            boolean includeParentInList, boolean allowParentInRoot, Comparator<FileInfo> ordering) {
        checkNotNull(indexRepository);
        checkNotNull(indexHandler);
        checkNotNull(emptyToNull(folder));
        this.indexRepository = indexRepository;
        this.indexHandler = indexHandler;
        this.indexHandler.getEventBus().register(indexHandlerEventListener);
        this.folder = folder;
        this.includeParentInList = includeParentInList;
        this.allowParentInRoot = allowParentInRoot;
        this.ordering = ordering;
        PARENT_FILE_INFO = FileInfo.newBuilder().setFolder(folder).setTypeDir().setPath(PARENT_PATH).build();
        ROOT_FILE_INFO = FileInfo.newBuilder().setFolder(folder).setTypeDir().setPath(ROOT_PATH).build();
        this.currentPath = ROOT_PATH;
        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                logger.debug("folder cache cleanup");
                listFolderCache.cleanUp();
                fileInfoCache.cleanUp();
            }

        }, 1, 1, TimeUnit.MINUTES);
    }

    private void invalidateCache() {
        listFolderCache.invalidateAll();
        fileInfoCache.invalidateAll();
        preloadFileInfoForCurrentPath();
    }

    private void preloadFileInfoForCurrentPath() {
        logger.debug("trigger preload for folder = '{}'", folder);
        synchronized (preloadJobsLock) {
            if (preloadJobs.contains(currentPath)) {
                preloadJobs.remove(currentPath);
                preloadJobs.add(currentPath); ///add last
                return; // no need to trigger job
            } else {
                preloadJobs.add(currentPath);
                executorService.submit(new Runnable() {

                    @Override
                    public void run() {

                        String preloadPath;
                        synchronized (preloadJobsLock) {
                            checkArgument(!preloadJobs.isEmpty());
                            preloadPath = Iterables.getLast(preloadJobs); //pop last job
                        }

                        logger.info("folder preload BEGIN for folder = '{}' path = '{}'", folder, preloadPath);
                        getFileInfoByAbsolutePath(preloadPath);
                        if (!PathUtils.isRoot(preloadPath)) {
                            String parent = getParentPath(preloadPath);
                            getFileInfoByAbsolutePath(parent);
                            listFiles(parent);
                        }
                        for (FileInfo record : listFiles(preloadPath)) {
                            if (!equal(record.getPath(), PARENT_FILE_INFO.getPath()) && record.isDirectory()) {
                                listFiles(record.getPath());
                            }
                        }
                        logger.info("folder preload END for folder = '{}' path = '{}'", folder, preloadPath);
                        synchronized (preloadJobsLock) {
                            preloadJobs.remove(preloadPath);
                            if (isCacheReady()) {
                                logger.info("cache ready, notify listeners");
                                preloadJobsLock.notifyAll();
                            } else {
                                logger.info("still {} job[s] left in cache loader", preloadJobs.size());
                                executorService.submit(this);
                            }
                        }
                    }

                });
            }

        }
    }

    public boolean isCacheReady() {
        synchronized (preloadJobsLock) {
            return preloadJobs.isEmpty();
        }
    }

    public boolean isCacheReadyAfterALittleWait() {
        synchronized (preloadJobsLock) {
            if (!isCacheReady()) {
                try {
                    preloadJobsLock.wait(100);
                } catch (InterruptedException ex) {
                }
            }
            return isCacheReady();
        }
    }

    public IndexBrowser waitForCacheReady() {
        logger.debug("waiting for cache to be ready");
        synchronized (preloadJobsLock) {
            while (!isCacheReady()) {
                try {
                    preloadJobsLock.wait();
                } catch (InterruptedException ex) {
                }
            }
        }
        return this;
    }

    public String getFolder() {
        return folder;
    }

    public String getCurrentPath() {
        return currentPath;
    }

    public FileInfo getCurrentPathInfo() {
        return getFileInfoByAbsolutePath(getCurrentPath());
    }

    public String getCurrentPathFileName() {
        return PathUtils.getFileName(getCurrentPath());
    }

    public IndexBrowser setOrdering(Comparator<FileInfo> ordering) {
        checkNotNull(ordering);
        this.ordering = ordering;
        //re-sort all data in cache
        for (Map.Entry<String, List<FileInfo>> entry : Lists.newArrayList(listFolderCache.asMap().entrySet())) {
            List<FileInfo> res = Lists.newArrayList(entry.getValue());
            Collections.sort(res, IndexBrowser.this.ordering);
            listFolderCache.put(entry.getKey(), res);
        }
        return this;
    }

    public List<FileInfo> listFiles() {
        return listFiles(currentPath);
    }

    public List<FileInfo> listFiles(String absoluteDirPath) {
        logger.debug("listFiles for path = '{}'", absoluteDirPath);
        return listFolderCache.getUnchecked(absoluteDirPath);
    }

    private List<FileInfo> doListFiles(String path) {
        logger.debug("doListFiles for path = '{}' BEGIN", path);
        List<FileInfo> list = indexRepository.findNotDeletedFilesByFolderAndParent(folder, path);
        logger.debug("doListFiles for path = '{}' : {} records loaded)", path, list.size());
        for (FileInfo fileInfo : list) {
            fileInfoCache.put(fileInfo.getPath(), fileInfo);
        }
        Collections.sort(list, ordering);
        if (includeParentInList && (!PathUtils.isRoot(path) || allowParentInRoot)) {
            list.add(0, PARENT_FILE_INFO);
        }
        logger.debug("doListFiles for path = '{}' : loaded list = {}", list);
        logger.debug("doListFiles for path = '{}' END", path);
        return Collections.unmodifiableList(list);
    }

    public boolean isRoot() {
        return PathUtils.isRoot(currentPath);
    }

    public List<String> listNames() {
        return Collections.unmodifiableList(Lists.transform(listFiles(), new Function<FileInfo, String>() {
            @Override
            public String apply(FileInfo input) {
                return input.getFileName();
            }
        }));
    }

    public FileInfo getFileInfoByRelativePath(String relativePath) {
        return getFileInfoByAbsolutePath(getAbsolutePath(relativePath));
    }

    public FileInfo getFileInfoByAbsolutePath(String path) {
        return PathUtils.isRoot(path) ? ROOT_FILE_INFO : fileInfoCache.getUnchecked(path);
    }

    private FileInfo doGetFileInfoByAbsolutePath(String path) {
        logger.debug("doGetFileInfoByAbsolutePath for path = '{}' BEGIN", path);
        FileInfo fileInfo = indexRepository.findNotDeletedFileInfo(folder, path);
        checkNotNull(fileInfo, "file not found for path = %s", path);
        logger.debug("doGetFileInfoByAbsolutePath for path = '{}' END", path);
        return fileInfo;
    }

    private String getAbsolutePath(String relativePath) {
        if (equal(PARENT_PATH, relativePath)) {
            return getParentPath(currentPath);
        } else {
            return normalizePath(currentPath + PATH_SEPARATOR + relativePath);
        }
    }

    public IndexBrowser navigateToRelativePath(String newPath) {
        return navigateToAbsolutePath(getAbsolutePath(newPath));
    }

    public IndexBrowser navigateTo(FileInfo fileInfo) {
        checkArgument(fileInfo.isDirectory());
        checkArgument(equal(fileInfo.getFolder(), folder));
        return equal(fileInfo.getPath(), PARENT_FILE_INFO.getPath()) ? navigateToParentPath()
                : navigateToAbsolutePath(fileInfo.getPath());
    }

    public IndexBrowser navigateToNearestPath(@Nullable String oldPath) {
        while (!StringUtils.isBlank(oldPath)) {
            try {
                return navigateToAbsolutePath(oldPath);
            } catch (Exception ex) {
                return navigateToNearestPath(PathUtils.getParentPath(oldPath));
            }
        }
        return this;
    }

    public IndexBrowser navigateToAbsolutePath(String newPath) {
        if (PathUtils.isRoot(newPath)) {
            currentPath = ROOT_PATH;
        } else {
            FileInfo fileInfo = getFileInfoByAbsolutePath(newPath);
            checkNotNull(fileInfo, "path %s does not exist", getAbsolutePath(newPath));
            checkArgument(fileInfo.isDirectory(), "cannot navigate to path %s: not a directory",
                    fileInfo.getPath());
            currentPath = fileInfo.getPath();
        }
        logger.info("navigate to path = '{}'", currentPath);
        preloadFileInfoForCurrentPath();
        return this;
    }

    public IndexBrowser navigateToParentPath() {
        return navigateToAbsolutePath(getParentPath(currentPath));
    }

    @Override
    public void close() {
        logger.info("closing");
        this.indexHandler.getEventBus().unregister(indexHandlerEventListener);
        executorService.shutdown();
        ExecutorUtils.awaitTerminationSafe(executorService);
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public static class Builder {

        private String folder;
        private IndexRepository indexRepository;
        private IndexHandler indexHandler;
        private boolean includeParentInList, allowParentInRoot;
        private Comparator<FileInfo> ordering = ALPHA_ASC_DIR_FIRST;

        private Builder() {

        }

        public String getFolder() {
            return folder;
        }

        public Builder setFolder(String folder) {
            this.folder = folder;
            return this;
        }

        public IndexRepository getIndexRepository() {
            return indexRepository;
        }

        public Builder setIndexRepository(IndexRepository indexRepository) {
            this.indexRepository = indexRepository;
            return this;
        }

        public IndexHandler getIndexHandler() {
            return indexHandler;
        }

        public Builder setIndexHandler(IndexHandler indexHandler) {
            this.indexHandler = indexHandler;
            return this;
        }

        public boolean doIncludeParentInList() {
            return includeParentInList;
        }

        public Builder includeParentInList(boolean includeParentInList) {
            this.includeParentInList = includeParentInList;
            return this;
        }

        public boolean doAllowParentInRoot() {
            return allowParentInRoot;
        }

        public Builder allowParentInRoot(boolean allowParentInRoot) {
            this.allowParentInRoot = allowParentInRoot;
            return this;
        }

        public Comparator<FileInfo> getOrdering() {
            return ordering;
        }

        public Builder setOrdering(Comparator<FileInfo> ordering) {
            checkNotNull(ordering);
            this.ordering = ordering;
            return this;
        }

        public IndexBrowser build() {
            return buildToAbsolutePath(ROOT_PATH);
        }

        public IndexBrowser buildToNearestPath(@Nullable String oldPath) {
            return new IndexBrowser(indexRepository, indexHandler, folder, includeParentInList, allowParentInRoot,
                    ordering).navigateToNearestPath(oldPath);
        }

        public IndexBrowser buildToAbsolutePath(String absolutePath) {
            return new IndexBrowser(indexRepository, indexHandler, folder, includeParentInList, allowParentInRoot,
                    ordering).navigateToAbsolutePath(absolutePath);
        }
    }
}