source: pypartiesserver/src/main/java/geniusweb/partiesserver/AvailablePartiesUpdater.java@ 11

Last change on this file since 11 was 11, checked in by bart, 3 years ago

refactor to help reusing partiesserver

File size: 5.8 KB
RevLine 
[11]1package geniusweb.partiesserver;
2
3import java.io.File;
4import java.io.IOException;
5import java.net.URISyntaxException;
6import java.nio.file.Path;
7import java.nio.file.Paths;
8import java.util.Collection;
9import java.util.LinkedList;
10import java.util.logging.Level;
11
12import geniusweb.javavenv.PythonPartyFactory;
13import geniusweb.partiesserver.repository.AvailablePartiesRepo;
14import geniusweb.partiesserver.repository.AvailableParty;
15import geniusweb.partiesserver.repository.AvailablePythonParty;
16import tudelft.utilities.files.FileInfo;
17import tudelft.utilities.files.FileWatcher;
18import tudelft.utilities.immutablelist.Tuple;
19import tudelft.utilities.listener.Listenable;
20import tudelft.utilities.logging.ReportToLogger;
21import tudelft.utilities.logging.Reporter;
22import tudelft.utilities.repository.Repository;
23
24/**
25 *
26 * This object keeps the {@link AvailablePartiesRepo} in sync with the file
27 * system. To work, this must be started in a global background thread,
28 * independent of current sessions etc. This thread is created when the
29 * constructor is called.
30 * <p>
31 * The {@link #PARTIESREPO} points to a directory that must exist. All the jar
32 * files in there are collected and tested if they can be loaded and are then
33 * added to the list of available parties.
34 * <p>
35 * Please make only 1 instance of this class. Double updates on the
36 * {@link AvailablePartiesRepo} is not useful
37 */
38public class AvailablePartiesUpdater {
39 private static final String PARTIESREPO = "partiesrepo";
40 private final Path rootdir;
41 private final Reporter log = new ReportToLogger("pypartiesserver");
42 private final Listenable<Tuple<FileInfo, FileInfo>> watcher;
43 private final Repository<String, AvailableParty> repository;
44
45 /**
46 *
47 * @param repo the repo to be automatically updated.
48 */
49 public AvailablePartiesUpdater(Repository<String, AvailableParty> repo) {
50 log.log(Level.INFO, "START Python parties updater");
51 this.repository = repo;
52 this.rootdir = getRootDir();
53 log.log(Level.INFO, "Repository located at " + this.rootdir);
54 watcher = getFileWatcher(rootdir.toFile());
55 watcher.addListener(info -> update(info));
56 // manually trigger the first event.
57 update(new Tuple<FileInfo, FileInfo>(null,
58 new FileInfo(rootdir.toFile(), 2)));
59 }
60
61 /**
62 * Called when some file that may be relevant has changed. Synchronized to
63 * avoid weird states if filesystem changes rapidly. We ignore everything
64 * but jar files.
65 */
66 private synchronized void update(Tuple<FileInfo, FileInfo> info) {
67 log.log(Level.INFO, "change: " + info);
68 File oldfile = info.get1() == null ? null : info.get1().getFile();
69 File newfile = info.get2() == null ? null : info.get2().getFile();
70
71 File file = oldfile != null ? oldfile : newfile;
72
73 Path relpath = file.toPath().relativize(rootdir);
74 // workaround: root gives "" with depth 1 instead of 0.
75 if (relpath.toString().isEmpty()) {
76 reset();
77 return;
78 }
79
80 // it's file or dir inside the root directory
81 if (newfile == null) {
82 // file removed
83 repository.remove(getShortName(file));
84 } else {
85 tryLoad(file);
86 }
87
88 }
89
90 /**
91 * @param jarfile a jarfile
92 * @return the filename only, without .tar.gz extension
93 *
94 */
95 private String getShortName(File jarfile) {
96 String partyname = jarfile.getName();
97 return partyname.substring(0, partyname.length() - 7);
98
99 }
100
101 /**
102 * Try to load a file. Report to logger if it fails to load.
103 *
104 * @param file the file to load. Supposedly a tar.gz file but we will report
105 * if not.
106 */
107 private void tryLoad(File file) {
108 try {
109 if (!file.getName().endsWith(("tar.gz"))) {
110 throw new IOException(
111 "Expected python package.tar.gz but found " + file);
112 }
113 repository.put(new AvailablePythonParty(getShortName(file),
114 new PythonPartyFactory(file)));
115 } catch (Throwable e) {
116 log.log(Level.WARNING, "Can not load class from file " + file, e);
117 }
118 }
119
120 /**
121 * Remove all parties and reload them all from scratch
122 */
123 private void reset() {
124 Collection<AvailableParty> toremove = new LinkedList<>(
125 repository.list());
126 for (AvailableParty party : toremove) {
127 repository.remove(party.getID());
128 }
129 File rootfile = rootdir.toFile();
130 if (rootfile.exists() && rootfile.canRead() && rootfile.isDirectory()) {
131 for (File file : rootfile.listFiles()) {
132 tryLoad(file);
133 }
134 } else {
135 log.log(Level.SEVERE, "Can not read repository root directory "
136 + rootdir
137 + ": it does not exist, can not be read or is not a directory.");
138 }
139 }
140
141 /**
142 * Search from location of this class upwards till we find a directory
143 * called "domainsrepo".
144 *
145 * @return the root dir of the repo. This is the 'database' where all known
146 * profiles are stored.
147 */
148 protected Path getRootDir() {
149 // If you run this normal from tomcat we are somewhere inside
150 // projectroot/WEB_INF/classes/....
151 // Search back upwards to projectroot.
152 Path dir;
153 try {
154 dir = Paths.get(getClass().getProtectionDomain().getCodeSource()
155 .getLocation().toURI()).getParent();
156 } catch (URISyntaxException e) {
157 throw new RuntimeException("problem with path uri", e);
158 }
159 log.log(Level.INFO,
160 "searching for " + PARTIESREPO + " upwards from " + dir);
161 while (dir.getNameCount() > 1) {
162 Path repo = dir.resolve(PARTIESREPO);
163 if (repo.toFile().exists())
164 return repo;
165 log.log(Level.INFO, "Directory did not contain repo:" + dir);
166 dir = dir.getParent();
167 }
168 return Paths.get(PARTIESREPO); // bit silly, it's not there, but we need
169 // to do something.
170 }
171
172 /**
173 * Factory method that gives a watcher for file changes. See
174 * {@link FileWatcher}.
175 *
176 * @param directory the directory to watch
177 * @return a class that watches for file changes in the given directory.
178 * Should ignore changes in sub-directories
179 */
180 protected Listenable<Tuple<FileInfo, FileInfo>> getFileWatcher(
181 File directory) {
182 return new FileWatcher(directory, 3000, 1);
183 }
184}
Note: See TracBrowser for help on using the repository browser.