Gridarta Editor
MapsIndexer.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.model.index;
21 
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.ObjectInputStream;
29 import java.io.ObjectOutputStream;
30 import java.io.OutputStream;
31 import java.nio.charset.StandardCharsets;
32 import java.util.Arrays;
33 import java.util.concurrent.Semaphore;
46 import net.sf.gridarta.utils.Xtea;
47 import org.apache.log4j.Category;
48 import org.apache.log4j.Logger;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51 
57 public class MapsIndexer<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> {
58 
62  @NotNull
63  private static final Category LOG = Logger.getLogger(MapsIndexer.class);
64 
68  @NotNull
69  private final Semaphore mapsIndexSemaphore = new Semaphore(1);
70 
74  @NotNull
75  private final MapsIndex mapsIndex;
76 
80  @NotNull
82 
86  @NotNull
88 
93  @NotNull
94  private final Object syncMapsDirectory = new Object();
95 
99  @Nullable
100  private File mapsDirectory;
101 
106  @Nullable
107  private File newMapsDirectory;
108 
112  @NotNull
113  private final Object syncState = new Object();
114 
118  @NotNull
119  private State state = State.INIT;
120 
124  private enum State {
125 
130 
136 
141 
145  IDLE
146 
147  }
148 
153  @NotNull
155 
156  @Override
157  public void currentMapChanged(@Nullable final MapControl<G, A, R> mapControl) {
158  // ignore
159  }
160 
161  @Override
162  public void mapCreated(@NotNull final MapControl<G, A, R> mapControl, final boolean interactive) {
163  if (!mapControl.isPickmap()) {
164  mapControl.addMapControlListener(mapControlListener);
165  }
166  }
167 
168  @Override
169  public void mapClosing(@NotNull final MapControl<G, A, R> mapControl) {
170  // ignore
171  }
172 
173  @Override
174  public void mapClosed(@NotNull final MapControl<G, A, R> mapControl) {
175  if (!mapControl.isPickmap()) {
176  mapControl.removeMapControlListener(mapControlListener);
177  }
178  }
179 
180  };
181 
185  @NotNull
187 
188  @Override
189  public void saved(@NotNull final DefaultMapControl<G, A, R> mapControl) {
190  final MapFile mapFile = mapControl.getMapModel().getMapFile();
191  if (mapFile != null) {
192  mapsIndex.setPending(mapFile);
193  }
194  }
195 
196  };
197 
202  @NotNull
204 
205  @Override
206  public void mapsDirectoryChanged(@NotNull final File mapsDirectory) {
207  synchronized (syncMapsDirectory) {
209  }
210  }
211 
212  };
213 
218  @NotNull
220 
221  @Override
222  public void valueAdded(@NotNull final MapFile value) {
223  // ignore
224  }
225 
226  @Override
227  public void valueRemoved(@NotNull final MapFile value) {
228  // ignore
229  }
230 
231  @Override
232  public void nameChanged() {
233  // ignore
234  }
235 
236  @Override
237  public void pendingChanged() {
238  mapsIndexSemaphore.release();
239  }
240 
241  @Override
242  public void indexingFinished() {
243  // ignore
244  }
245 
246  };
247 
251  @NotNull
252  private final Runnable runnable = new Runnable() {
253 
254  @Override
255  public void run() {
256  while (!Thread.currentThread().isInterrupted()) {
259  if (state == State.IDLE) {
260  try {
261  mapsIndexSemaphore.acquire();
262  } catch (final InterruptedException ignored) {
263  Thread.currentThread().interrupt();
264  break;
265  }
266  }
267  }
268  }
269 
270  };
271 
275  @NotNull
276  private final Thread thread = new Thread(runnable, "indexer");
277 
285  public MapsIndexer(@NotNull final MapsIndex mapsIndex, @NotNull final MapManager<G, A, R> mapManager, @NotNull final ProjectSettings projectSettings) {
286  this.mapsIndex = mapsIndex;
287  this.mapManager = mapManager;
288  this.projectSettings = projectSettings;
290  }
291 
295  public void start() {
298  synchronized (syncMapsDirectory) {
300  }
302  thread.start();
303  }
304 
310  public void stop() throws InterruptedException {
311  try {
312  thread.interrupt();
313  thread.join();
314  } finally {
317  for (final MapControl<G, A, R> mapControl : mapManager.getOpenedMaps()) {
318  if (!mapControl.isPickmap()) {
319  mapControl.removeMapControlListener(mapControlListener);
320  }
321  }
323  }
324 
325  synchronized (syncMapsDirectory) {
326  saveMapsIndex();
327  }
328  }
329 
335  public void waitForIdle() throws InterruptedException {
336  synchronized (syncState) {
337  while (state != State.IDLE || mapsIndex.hasPending()) {
338  syncState.wait(1000L);
339  }
340  }
341  }
342 
347  private void updateMapsDirectory() {
348  final File tmpMapsDirectory;
349  synchronized (syncMapsDirectory) {
350  if (newMapsDirectory == null || (mapsDirectory != null && mapsDirectory.equals(newMapsDirectory))) {
351  return;
352  }
353 
354  setState(State.SCAN);
355 
356  saveMapsIndex();
357 
358  tmpMapsDirectory = newMapsDirectory;
359  assert tmpMapsDirectory != null;
360  mapsDirectory = tmpMapsDirectory;
361  newMapsDirectory = null;
362 
363  loadMapsIndex();
364  }
366  scanMapsDirectoryInt(new MapFile(tmpMapsDirectory), "");
368  }
369 
373  private void saveMapsIndex() {
374  assert Thread.holdsLock(syncMapsDirectory);
375  if (mapsDirectory == null) {
376  return;
377  }
378 
379  if (!projectSettings.saveIndices()) {
380  return;
381  }
382 
383  if (!mapsIndex.isModified()) {
384  return;
385  }
386 
387  assert mapsDirectory != null;
388  final File cacheFile = getCacheFile(mapsDirectory);
389  try {
390  try (OutputStream outputStream = new FileOutputStream(cacheFile)) {
391  try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream)) {
392  mapsIndex.save(objectOutputStream);
393  }
394  }
395  if (LOG.isInfoEnabled()) {
396  LOG.info(cacheFile + ": saved " + mapsIndex.size() + " entries");
397  }
398  } catch (final IOException ex) {
399  LOG.warn(cacheFile + ": cannot save cache file: " + ex.getMessage());
400  }
401  }
402 
406  private void loadMapsIndex() {
407  assert Thread.holdsLock(syncMapsDirectory);
408  assert mapsDirectory != null;
409  final File cacheFile = getCacheFile(mapsDirectory);
410  try {
411  try (InputStream inputStream = new FileInputStream(cacheFile)) {
412  try (ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)) {
413  mapsIndex.load(objectInputStream);
414  }
415  }
416  if (LOG.isInfoEnabled()) {
417  LOG.info(cacheFile + ": loaded " + mapsIndex.size() + " entries");
418  }
419  } catch (final FileNotFoundException ex) {
420  if (LOG.isDebugEnabled()) {
421  LOG.debug(cacheFile + ": cache file does not exist: " + ex.getMessage());
422  }
423  mapsIndex.clear();
424  } catch (final IOException ex) {
425  LOG.warn(cacheFile + ": cannot load cache file: " + ex.getMessage());
426  mapsIndex.clear();
427  }
428  }
429 
435  private void scanMapsDirectoryInt(@NotNull final MapFile dir, @NotNull final String mapPath) {
436  final File[] files = dir.getFile().listFiles();
437  if (files == null) {
438  return;
439  }
440 
441  for (final File file : files) {
442  final MapFile mapFile = new MapFile(dir, file.getName());
443  if (file.isFile() && !file.getName().endsWith("~")) {
444  mapsIndex.add(mapFile, file.lastModified());
445  } else if (file.isDirectory() && !file.getName().equalsIgnoreCase(".svn") && !file.getName().equalsIgnoreCase(".dedit")) {
446  scanMapsDirectoryInt(mapFile, mapPath + "/" + file.getName());
447  }
448  }
449  }
450 
455  private void indexPendingMaps() {
456  final MapFile mapFile = mapsIndex.removePending();
457  if (mapFile == null) {
458  setState(State.IDLE);
459  return;
460  }
461 
462  setState(State.INDEX);
463  final long timestamp = mapFile.getFile().lastModified();
464  final MapControl<G, A, R> mapControl;
465  try {
466  mapControl = mapManager.openMapFile(mapFile, false);
467  } catch (final IOException ex) {
468  if (LOG.isInfoEnabled()) {
469  LOG.info(mapFile + ": load failed:" + ex.getMessage());
470  }
471  return;
472  }
473  try {
474  final String mapName = mapControl.getMapModel().getMapArchObject().getMapName();
475  if (LOG.isDebugEnabled()) {
476  LOG.debug(mapFile + ": indexing as '" + mapName + "'");
477  }
478  mapsIndex.setName(mapFile, timestamp, mapName);
479  } finally {
480  mapManager.release(mapControl);
481  }
482  }
483 
489  @NotNull
490  private static File getCacheFile(@NotNull final File mapsDirectory) {
491  final byte[] key = new byte[16];
492  final Xtea xtea = new Xtea(key);
493  final byte[] data = mapsDirectory.getAbsoluteFile().toString().getBytes(StandardCharsets.UTF_8);
494  final byte[] hash = new byte[8];
495  final byte[] in = new byte[8];
496  final byte[] out = new byte[8];
497  int i;
498  for (i = 0; i + 8 < data.length; i++) {
499  System.arraycopy(data, i, in, 0, 8);
500  xtea.encrypt(in, out);
501  for (int j = 0; j < 8; j++) {
502  hash[j] ^= out[j];
503  }
504  }
505  final int len = data.length % 8;
506  System.arraycopy(data, i, in, 0, len);
507  in[len] = 1;
508  Arrays.fill(in, len + 1, 8, (byte) 0);
509  xtea.encrypt(in, out);
510  for (int j = 0; j < 8; j++) {
511  hash[j] ^= out[j];
512  }
513 
514  final StringBuilder sb = new StringBuilder("index/maps/");
515  for (int j = 0; j < 8; j++) {
516  sb.append(String.format("%02x", hash[j] & 0xff));
517  }
518  sb.append(mapsDirectory.getName());
519  final File file = ConfigFileUtils.getHomeFile(sb.toString());
520  final File dir = file.getParentFile();
521  if (dir != null && !dir.exists() && !dir.mkdirs()) {
522  LOG.warn("cannot create directory: " + dir);
523  }
524  return file;
525  }
526 
531  private void setState(@NotNull final State state) {
532  synchronized (syncState) {
533  if (this.state == state) {
534  return;
535  }
536 
537  this.state = state;
539  syncState.notifyAll();
540  }
541  }
542 
546  private void reportStateChange() {
547  if (LOG.isDebugEnabled()) {
548  LOG.debug("state=" + state);
549  }
550 
551  if (state == State.IDLE) {
553  }
554  }
555 
556 }
net.sf.gridarta.model.index.MapsIndexer.LOG
static final Category LOG
Definition: MapsIndexer.java:63
net.sf.gridarta.model.mapmanager
Definition: AbstractMapManager.java:20
net.sf.gridarta.model.index.MapsIndexer.State.INDEX
INDEX
Definition: MapsIndexer.java:140
net.sf.gridarta.model.index.MapsIndexer.MapsIndexer
MapsIndexer(@NotNull final MapsIndex mapsIndex, @NotNull final MapManager< G, A, R > mapManager, @NotNull final ProjectSettings projectSettings)
Definition: MapsIndexer.java:285
net.sf.gridarta.model.mapmodel.MapModel.getMapArchObject
A getMapArchObject()
net.sf.gridarta.model.mapmanager.MapManager
Definition: MapManager.java:37
net.sf.gridarta.utils.ConfigFileUtils.getHomeFile
static File getHomeFile(@NotNull final String filename)
Definition: ConfigFileUtils.java:53
net.sf.gridarta.utils.Xtea.encrypt
void encrypt(final byte @NotNull[] plaintext, final byte @NotNull[] ciphertext)
Definition: Xtea.java:65
net.sf.gridarta.model.index.MapsIndexer.syncState
final Object syncState
Definition: MapsIndexer.java:113
net.sf.gridarta.model.mapmanager.MapManager.removeMapManagerListener
void removeMapManagerListener(@NotNull MapManagerListener< G, A, R > listener)
net.sf.gridarta
net.sf.gridarta.model.index.AbstractIndex.removeIndexListener
void removeIndexListener(@NotNull final IndexListener< V > listener)
Definition: AbstractIndex.java:274
net.sf.gridarta.model.index.MapsIndexer.mapsIndex
final MapsIndex mapsIndex
Definition: MapsIndexer.java:75
net.sf
net.sf.gridarta.model.index.AbstractIndex.add
void add(@NotNull final V value, final long timestamp)
Definition: AbstractIndex.java:154
net.sf.gridarta.model.mapmanager.MapManagerListener
Definition: MapManagerListener.java:42
net.sf.gridarta.model.index.MapsIndexer.saveMapsIndex
void saveMapsIndex()
Definition: MapsIndexer.java:373
net.sf.gridarta.model.index.AbstractIndex.addIndexListener
void addIndexListener(@NotNull final IndexListener< V > listener)
Definition: AbstractIndex.java:269
net.sf.gridarta.model.mapmodel
Definition: AboveFloorInsertionMode.java:20
net.sf.gridarta.model.index.MapsIndexer.newMapsDirectory
File newMapsDirectory
Definition: MapsIndexer.java:107
net.sf.gridarta.model.mapmanager.MapManager.addMapManagerListener
void addMapManagerListener(@NotNull MapManagerListener< G, A, R > listener)
net.sf.gridarta.model.archetype
Definition: AbstractArchetype.java:20
net.sf.gridarta.model.gameobject.GameObject
Definition: GameObject.java:36
net.sf.gridarta.model.mapcontrol
Definition: DefaultMapControl.java:20
net.sf.gridarta.model.index.AbstractIndex.setName
void setName(@NotNull final V value, final long timestamp, @NotNull final String name)
Definition: AbstractIndex.java:217
net.sf.gridarta.model.index.AbstractIndex.setPending
void setPending(@NotNull final V value)
Definition: AbstractIndex.java:198
net.sf.gridarta.model.index.AbstractIndex.beginUpdate
void beginUpdate()
Definition: AbstractIndex.java:118
net.sf.gridarta.model.index.MapsIndexer.start
void start()
Definition: MapsIndexer.java:295
net.sf.gridarta.model.mapmanager.MapManager.getOpenedMaps
List< MapControl< G, A, R > > getOpenedMaps()
net.sf.gridarta.model.index.AbstractIndex.removePending
V removePending()
Definition: AbstractIndex.java:241
net.sf.gridarta.model.index.MapsIndexer.State
Definition: MapsIndexer.java:124
net.sf.gridarta.model.index.MapsIndexer.syncMapsDirectory
final Object syncMapsDirectory
Definition: MapsIndexer.java:94
net.sf.gridarta.model.gameobject
Definition: AbstractGameObject.java:20
net
net.sf.gridarta.model.index.MapsIndexer.stop
void stop()
Definition: MapsIndexer.java:310
net.sf.gridarta.model.index.MapsIndexer.mapManagerListener
final MapManagerListener< G, A, R > mapManagerListener
Definition: MapsIndexer.java:154
net.sf.gridarta.utils.Xtea
Definition: Xtea.java:28
net.sf.gridarta.model.index.MapsIndexer.reportStateChange
void reportStateChange()
Definition: MapsIndexer.java:546
net.sf.gridarta.model.index.MapsIndexer.projectSettings
final ProjectSettings projectSettings
Definition: MapsIndexer.java:87
net.sf.gridarta.model.index.AbstractIndex.save
void save(@NotNull final ObjectOutputStream objectOutputStream)
Definition: AbstractIndex.java:279
net.sf.gridarta.model.index.MapsIndexer.State.SCAN
SCAN
Definition: MapsIndexer.java:135
net.sf.gridarta.model.index.MapsIndexer.thread
final Thread thread
Definition: MapsIndexer.java:276
net.sf.gridarta.model.maparchobject.MapArchObject
Definition: MapArchObject.java:40
net.sf.gridarta.model.settings.ProjectSettingsListener
Definition: ProjectSettingsListener.java:30
net.sf.gridarta.model.index.MapsIndexer.runnable
final Runnable runnable
Definition: MapsIndexer.java:252
net.sf.gridarta.model.mapmodel.MapFile.getFile
File getFile()
Definition: MapFile.java:102
net.sf.gridarta.model.index.MapsIndexer.mapsDirectory
File mapsDirectory
Definition: MapsIndexer.java:100
net.sf.gridarta.model.index.MapsIndexer.indexListener
final IndexListener< MapFile > indexListener
Definition: MapsIndexer.java:219
net.sf.gridarta.model.mapmanager.MapManager.openMapFile
MapControl< G, A, R > openMapFile(@NotNull MapFile mapFile, boolean interactive)
net.sf.gridarta.model.settings.ProjectSettings.addProjectSettingsListener
void addProjectSettingsListener(@NotNull ProjectSettingsListener listener)
net.sf.gridarta.model.index.MapsIndexer.setState
void setState(@NotNull final State state)
Definition: MapsIndexer.java:531
net.sf.gridarta.model.index.MapsIndexer
Definition: MapsIndexer.java:57
net.sf.gridarta.model.index.AbstractIndex.isModified
boolean isModified()
Definition: AbstractIndex.java:262
net.sf.gridarta.model.mapmodel.MapFile
Definition: MapFile.java:31
net.sf.gridarta.model.settings.ProjectSettings
Definition: ProjectSettings.java:29
net.sf.gridarta.model.mapmanager.MapManager.release
void release(@NotNull MapControl< G, A, R > mapControl)
net.sf.gridarta.model.index.MapsIndexer.updateMapsDirectory
void updateMapsDirectory()
Definition: MapsIndexer.java:347
net.sf.gridarta.model.index.MapsIndexer.waitForIdle
void waitForIdle()
Definition: MapsIndexer.java:335
net.sf.gridarta.model.index.MapsIndexer.loadMapsIndex
void loadMapsIndex()
Definition: MapsIndexer.java:406
net.sf.gridarta.model.index.MapsIndexer.projectSettingsListener
final ProjectSettingsListener projectSettingsListener
Definition: MapsIndexer.java:203
net.sf.gridarta.model.settings.ProjectSettings.removeProjectSettingsListener
void removeProjectSettingsListener(@NotNull ProjectSettingsListener listener)
net.sf.gridarta.model
net.sf.gridarta.model.archetype.Archetype
Definition: Archetype.java:41
net.sf.gridarta.model.index.MapsIndexer.indexPendingMaps
void indexPendingMaps()
Definition: MapsIndexer.java:455
net.sf.gridarta.model.settings.ProjectSettings.saveIndices
boolean saveIndices()
net.sf.gridarta.model.index.IndexListener
Definition: IndexListener.java:30
net.sf.gridarta.model.index.MapsIndexer.mapsIndexSemaphore
final Semaphore mapsIndexSemaphore
Definition: MapsIndexer.java:69
net.sf.gridarta.model.index.AbstractIndex.clear
void clear()
Definition: AbstractIndex.java:316
net.sf.gridarta.model.mapcontrol.MapControl
Definition: MapControl.java:35
net.sf.gridarta.model.mapcontrol.MapControlListener
Definition: MapControlListener.java:33
net.sf.gridarta.model.index.MapsIndexer.getCacheFile
static File getCacheFile(@NotNull final File mapsDirectory)
Definition: MapsIndexer.java:490
net.sf.gridarta.model.index.MapsIndexer.State.IDLE
IDLE
Definition: MapsIndexer.java:145
net.sf.gridarta.model.index.MapsIndex
Definition: MapsIndex.java:28
net.sf.gridarta.model.mapcontrol.MapControl.getMapModel
MapModel< G, A, R > getMapModel()
net.sf.gridarta.model.index.MapsIndexer.state
State state
Definition: MapsIndexer.java:119
net.sf.gridarta.model.maparchobject
Definition: AbstractMapArchObject.java:20
net.sf.gridarta.model.index.MapsIndexer.scanMapsDirectoryInt
void scanMapsDirectoryInt(@NotNull final MapFile dir, @NotNull final String mapPath)
Definition: MapsIndexer.java:435
net.sf.gridarta.model.index.MapsIndexer.mapManager
final MapManager< G, A, R > mapManager
Definition: MapsIndexer.java:81
net.sf.gridarta.model.index.AbstractIndex.indexingFinished
void indexingFinished()
Definition: AbstractIndex.java:327
net.sf.gridarta.model.settings.ProjectSettings.getMapsDirectory
File getMapsDirectory()
net.sf.gridarta.model.mapcontrol.DefaultMapControl
Definition: DefaultMapControl.java:44
net.sf.gridarta.model.index.AbstractIndex.size
int size()
Definition: AbstractIndex.java:98
net.sf.gridarta.model.index.AbstractIndex.hasPending
boolean hasPending()
Definition: AbstractIndex.java:255
net.sf.gridarta.utils
Definition: ActionBuilderUtils.java:20
net.sf.gridarta.model.index.AbstractIndex.load
void load(@NotNull final ObjectInputStream objectInputStream)
Definition: AbstractIndex.java:292
net.sf.gridarta.model.settings
Definition: AbstractDefaultProjectSettings.java:20
net.sf.gridarta.model.index.MapsIndexer.mapControlListener
final MapControlListener< G, A, R > mapControlListener
Definition: MapsIndexer.java:186
net.sf.gridarta.model.index.AbstractIndex.endUpdate
void endUpdate()
Definition: AbstractIndex.java:127
net.sf.gridarta.model.index.MapsIndexer.State.INIT
INIT
Definition: MapsIndexer.java:129
net.sf.gridarta.utils.ConfigFileUtils
Definition: ConfigFileUtils.java:29