com.bdaum.zoom.db.internal.DbManager.java Source code

Java tutorial

Introduction

Here is the source code for com.bdaum.zoom.db.internal.DbManager.java

Source

/*
 * This file is part of the ZoRa project: http://www.photozora.org.
 *
 * ZoRa is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * ZoRa is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ZoRa; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * (c) 2009-2018 Berthold Daum  
 */
package com.bdaum.zoom.db.internal;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

import com.bdaum.aoModeling.runtime.AomList;
import com.bdaum.aoModeling.runtime.AomObject;
import com.bdaum.aoModeling.runtime.IIdentifiableObject;
import com.bdaum.aoModeling.runtime.IdentifiableObject;
import com.bdaum.zoom.cat.model.ArtworkOrObject_typeImpl;
import com.bdaum.zoom.cat.model.Asset_typeImpl;
import com.bdaum.zoom.cat.model.Category_typeImpl;
import com.bdaum.zoom.cat.model.ComposedTo_typeImpl;
import com.bdaum.zoom.cat.model.CreatorsContact_typeImpl;
import com.bdaum.zoom.cat.model.Criterion_typeImpl;
import com.bdaum.zoom.cat.model.DerivedBy_typeImpl;
import com.bdaum.zoom.cat.model.Exhibit_typeImpl;
import com.bdaum.zoom.cat.model.Exhibition_typeImpl;
import com.bdaum.zoom.cat.model.Ghost_typeImpl;
import com.bdaum.zoom.cat.model.Group_typeImpl;
import com.bdaum.zoom.cat.model.LocationCreated_typeImpl;
import com.bdaum.zoom.cat.model.LocationShown_typeImpl;
import com.bdaum.zoom.cat.model.Meta_type;
import com.bdaum.zoom.cat.model.Region_typeImpl;
import com.bdaum.zoom.cat.model.SlideShow_typeImpl;
import com.bdaum.zoom.cat.model.Slide_typeImpl;
import com.bdaum.zoom.cat.model.SmartCollection_typeImpl;
import com.bdaum.zoom.cat.model.SortCriterion_typeImpl;
import com.bdaum.zoom.cat.model.TrackRecord_typeImpl;
import com.bdaum.zoom.cat.model.WebExhibit_typeImpl;
import com.bdaum.zoom.cat.model.WebGallery_typeImpl;
import com.bdaum.zoom.cat.model.artworkOrObjectShown.ArtworkOrObjectShownImpl;
import com.bdaum.zoom.cat.model.asset.Asset;
import com.bdaum.zoom.cat.model.asset.AssetImpl;
import com.bdaum.zoom.cat.model.asset.RegionImpl;
import com.bdaum.zoom.cat.model.asset.TrackRecordImpl;
import com.bdaum.zoom.cat.model.composedTo.ComposedToImpl;
import com.bdaum.zoom.cat.model.creatorsContact.CreatorsContactImpl;
import com.bdaum.zoom.cat.model.derivedBy.DerivedByImpl;
import com.bdaum.zoom.cat.model.group.Criterion;
import com.bdaum.zoom.cat.model.group.CriterionImpl;
import com.bdaum.zoom.cat.model.group.Group;
import com.bdaum.zoom.cat.model.group.GroupImpl;
import com.bdaum.zoom.cat.model.group.PostProcessor;
import com.bdaum.zoom.cat.model.group.SmartCollection;
import com.bdaum.zoom.cat.model.group.SmartCollectionImpl;
import com.bdaum.zoom.cat.model.group.SortCriterion;
import com.bdaum.zoom.cat.model.group.SortCriterionImpl;
import com.bdaum.zoom.cat.model.group.exhibition.ExhibitImpl;
import com.bdaum.zoom.cat.model.group.exhibition.ExhibitionImpl;
import com.bdaum.zoom.cat.model.group.exhibition.Wall;
import com.bdaum.zoom.cat.model.group.slideShow.SlideImpl;
import com.bdaum.zoom.cat.model.group.slideShow.SlideShow;
import com.bdaum.zoom.cat.model.group.slideShow.SlideShowImpl;
import com.bdaum.zoom.cat.model.group.webGallery.Storyboard;
import com.bdaum.zoom.cat.model.group.webGallery.WebExhibitImpl;
import com.bdaum.zoom.cat.model.group.webGallery.WebGalleryImpl;
import com.bdaum.zoom.cat.model.location.Location;
import com.bdaum.zoom.cat.model.location.LocationImpl;
import com.bdaum.zoom.cat.model.locationCreated.LocationCreatedImpl;
import com.bdaum.zoom.cat.model.locationShown.LocationShownImpl;
import com.bdaum.zoom.cat.model.meta.Meta;
import com.bdaum.zoom.cat.model.meta.MetaImpl;
import com.bdaum.zoom.common.GeoMessages;
import com.bdaum.zoom.core.CatalogListener;
import com.bdaum.zoom.core.Constants;
import com.bdaum.zoom.core.Core;
import com.bdaum.zoom.core.IAssetFilter;
import com.bdaum.zoom.core.IVolumeManager;
import com.bdaum.zoom.core.QueryField;
import com.bdaum.zoom.core.Range;
import com.bdaum.zoom.core.TetheredRange;
import com.bdaum.zoom.core.db.ICollectionProcessor;
import com.bdaum.zoom.core.db.IDbListener;
import com.bdaum.zoom.core.db.IDbManager;
import com.bdaum.zoom.core.internal.CoreActivator;
import com.bdaum.zoom.core.internal.Utilities;
import com.bdaum.zoom.core.internal.peer.AssetOrigin;
import com.bdaum.zoom.core.internal.peer.ConnectionLostException;
import com.bdaum.zoom.core.internal.peer.IPeerService;
import com.bdaum.zoom.core.trash.FileLocationBackup;
import com.bdaum.zoom.core.trash.HistoryItem;
import com.bdaum.zoom.core.trash.StructBackup;
import com.bdaum.zoom.core.trash.Trash;
import com.bdaum.zoom.db.internal.job.BackupJob;
import com.bdaum.zoom.db.internal.job.CleanupJob;
import com.bdaum.zoom.image.ImageConstants;
import com.bdaum.zoom.program.BatchConstants;
import com.db4o.Db4o;
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.CommonConfiguration;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.config.ObjectClass;
import com.db4o.config.QueryEvaluationMode;
import com.db4o.config.encoding.StringEncodings;
import com.db4o.defragment.DefragmentConfig;
import com.db4o.diagnostic.Diagnostic;
import com.db4o.diagnostic.DiagnosticConfiguration;
import com.db4o.diagnostic.DiagnosticListener;
import com.db4o.ext.DatabaseClosedException;
import com.db4o.ext.DatabaseFileLockedException;
import com.db4o.ext.Db4oException;
import com.db4o.ext.Db4oIOException;
import com.db4o.ext.ExtObjectContainer;
import com.db4o.ext.StoredClass;
import com.db4o.ext.SystemInfo;
import com.db4o.internal.caching.Cache4;
import com.db4o.internal.caching.CacheFactory;
import com.db4o.io.CachingStorage;
import com.db4o.io.FileStorage;
import com.db4o.query.Constraint;
import com.db4o.query.Query;

@SuppressWarnings("restriction")
public class DbManager implements IDbManager, IAdaptable {

    private static final String IMPL = "Impl"; //$NON-NLS-1$

    private static final String TYPEIMPL = "_typeImpl"; //$NON-NLS-1$

    private static final ArrayList<AssetImpl> EMPTYASSETS = new ArrayList<AssetImpl>(0);

    private static final ArrayList<Ghost_typeImpl> EMPTYGHOSTS = new ArrayList<Ghost_typeImpl>(0);

    private static final int MAXIDS = 256;

    private static final int VERSION = 15;

    private static final boolean DIAGNOSE = false;

    private static final int TIMEOUT = 30000;

    private static final String SAFE_TRANSACTION = "transaction"; //$NON-NLS-1$

    private static final String LUCENE_WRITE_LOCK = "write.lock"; //$NON-NLS-1$

    private static final List<LocationImpl> EMPTYLOCATIONS = new ArrayList<LocationImpl>(0);

    private static final List<Trash> EMPTYTRASH = new ArrayList<Trash>(0);

    private static final long ONEDAY = 24 * 60 * 60 * 1000L;

    private Set<String> indexedFields;

    private ObjectContainer db;

    protected String fileName;

    private ObjectContainer trashCan;

    private Set<String> dirtyCollections = Collections.synchronizedSet(new HashSet<String>());

    protected Meta meta;

    private boolean transactionFailed;

    private boolean trashFailed;

    protected File file;

    protected boolean readOnly;

    protected File indexPath;

    private int dbErrorCounter;

    private boolean backupScheduled;

    protected DbFactory factory;

    private long previousDay = -1L;

    private String previousTimeline = Meta_type.timeline_no;

    private Set<String> lastFolderUris;

    private Set<String> previousLocationKeys;

    private boolean emergency;

    private EmbeddedConfiguration externalConfig;

    private File luceneLockFile;

    /**
     * Constructor
     *
     * @param factory
     *            - parent factory
     * @param fileName
     *            - catalog file name
     * @param newDb
     *            - true if new catalog
     * @param readOnly
     *            - true if opened in read only mode
     */
    public DbManager(DbFactory factory, String fileName, boolean newDb, boolean readOnly) {
        this(factory, fileName, newDb, readOnly, null);
    }

    protected DbManager(DbFactory factory, String fileName, boolean newDb, boolean readOnly,
            EmbeddedConfiguration externalConfig) {
        this.factory = factory;
        this.fileName = fileName;
        this.externalConfig = externalConfig;
        this.file = new File(fileName);
        this.readOnly = readOnly;
        if (factory.getLireServiceVersion() >= 0) {
            String ip = fileName;
            int p = ip.lastIndexOf('.');
            if (p >= 0)
                ip = ip.substring(0, p);
            ip += Constants.INDEXEXTENSION;
            indexPath = new File(ip);
        }
        boolean reOpen = false;
        if (file.exists()) {
            if (newDb)
                file.delete();
            else {
                reOpen = true;
                this.readOnly |= !file.canWrite();
            }
        }
        // Reset lucene
        if (indexPath != null && indexPath.exists()) {
            luceneLockFile = new File(indexPath, LUCENE_WRITE_LOCK);
            if (reOpen)
                removeCrashedLuceneIndex();
            else
                Core.deleteFileOrFolder(indexPath);
        }
        // long time = System.currentTimeMillis();
        this.db = createDatabase(fileName, newDb);
        // System.out.println( System.currentTimeMillis()-time);
        // Reset trash
        new File(getTrashcanName()).delete();
    }

    public int getVersion() {
        return VERSION;
    }

    protected EmbeddedConfiguration createTrashcanConfiguration() {
        EmbeddedConfiguration configuration = Db4oEmbedded.newConfiguration();
        configuration.file().reserveStorageSpace(1000000);
        configuration.file().readOnly(false);
        CommonConfiguration common = configuration.common();
        common.detectSchemaChanges(true); // Leave at true, false causes
        // problems
        common.callbacks(false);
        common.activationDepth(Integer.MAX_VALUE);
        common.stringEncoding(StringEncodings.utf8());
        ObjectClass trash = common.objectClass(Trash.class);
        trash.objectField("opId").indexed(true); //$NON-NLS-1$
        trash.objectField("name").indexed(true); //$NON-NLS-1$
        common.objectClass(HistoryItem.class).objectField("opId").indexed(true); //$NON-NLS-1$
        common.objectClass(FileLocationBackup.class).objectField("opId") //$NON-NLS-1$
                .indexed(true);
        common.objectClass(StructBackup.class).objectField("opId") //$NON-NLS-1$
                .indexed(true);
        return configuration;
    }

    /*
     * Deleting complex objects
     *
     * 1. Objects without backpointer to a parent are handeled with cascadeOnDelete.
     * Example: GroupImpl 2. Objects with collections of primitive datatypes (String
     * counts as primitive) are handled in the delete() method. Example: WallImpl 3.
     * Objects with non-primitive members and backpointers are handled through
     * specific deleteXXXX() methods. Example: deleteCollection()
     */

    protected EmbeddedConfiguration createDatabaseConfiguration(boolean allowUpgrade, String fName,
            long reservedSpace) {
        EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
        if (reservedSpace >= 0)
            config.file().reserveStorageSpace(reservedSpace);
        final CommonConfiguration common = config.common();
        if (DIAGNOSE)
            common.messageLevel(2);
        common.allowVersionUpdates(allowUpgrade);
        common.callConstructors(true);
        common.testConstructors(false);
        common.callbacks(false);
        common.queries().evaluationMode(QueryEvaluationMode.LAZY);
        common.activationDepth(Integer.MAX_VALUE);
        common.updateDepth(2);
        common.stringEncoding(StringEncodings.utf8());
        // General
        common.detectSchemaChanges(true); // Leave at true, false causes
        // problems and doesn't improve much
        common.objectClass(IdentifiableObject.class).objectField(Constants.OID).indexed(true);
        common.objectClass(AomObject.class).indexed(false);
        // Group
        common.objectClass(GroupImpl.class).cascadeOnDelete(true);
        common.objectClass(Group_typeImpl.class).indexed(false);
        // Collections
        common.objectClass(SmartCollectionImpl.class).objectField("group_rootCollection_parent").indexed(false); //$NON-NLS-1$
        common.objectClass(SmartCollectionImpl.class).objectField("lastAccessDate").indexed(true); //$NON-NLS-1$
        common.objectClass(SmartCollection_typeImpl.class).indexed(false);
        // Criteria
        common.objectClass(CriterionImpl.class).updateDepth(1);
        common.objectClass(Criterion_typeImpl.class).indexed(false);
        common.objectClass(Criterion_typeImpl.class).cascadeOnDelete(true);
        // SortCriteria
        common.objectClass(SortCriterionImpl.class).updateDepth(1);
        common.objectClass(SortCriterion_typeImpl.class).indexed(false);
        // Relations
        ObjectClass derivedBy = common.objectClass(DerivedByImpl.class);
        derivedBy.objectField("derivative") //$NON-NLS-1$
                .indexed(true);
        derivedBy.objectField("original") //$NON-NLS-1$
                .indexed(true);
        common.objectClass(DerivedBy_typeImpl.class).indexed(false);
        ObjectClass composedTo = common.objectClass(ComposedToImpl.class);
        composedTo.objectField("composite") //$NON-NLS-1$
                .indexed(true);
        composedTo.objectField("component") //$NON-NLS-1$
                .indexed(true);
        composedTo.cascadeOnDelete(true);
        common.objectClass(ComposedTo_typeImpl.class).indexed(false);
        // Asset
        common.objectClass(AssetImpl.class).cascadeOnDelete(true);
        ObjectClass assetType = common.objectClass(Asset_typeImpl.class);
        assetType.indexed(false);
        indexedFields = factory.getIndexedFields();
        for (String key : indexedFields)
            assetType.objectField(key).indexed(true);
        // Track and Ghost
        common.objectClass(TrackRecordImpl.class).objectField("asset_track_parent") //$NON-NLS-1$
                .indexed(true);
        common.objectClass(TrackRecord_typeImpl.class).indexed(false);
        common.objectClass(Ghost_typeImpl.class).objectField(QueryField.NAME.getKey()).indexed(true);
        common.objectClass(Ghost_typeImpl.class).indexed(true);
        // Region
        ObjectClass region = common.objectClass(RegionImpl.class);
        region.objectField("asset_person_parent").indexed(true); //$NON-NLS-1$
        region.objectField("album").indexed(true); //$NON-NLS-1$
        common.objectClass(Region_typeImpl.class).indexed(false);
        // Location, Artwork, Contacts
        ObjectClass locationCreated = common.objectClass(LocationCreatedImpl.class);
        locationCreated.cascadeOnDelete(true);
        locationCreated.objectField("asset").indexed(true); //$NON-NLS-1$
        locationCreated.objectField("location").indexed(true); //$NON-NLS-1$
        ObjectClass locationShown = common.objectClass(LocationShownImpl.class);
        locationShown.objectField("asset").indexed(true); //$NON-NLS-1$
        locationShown.objectField("location").indexed(true); //$NON-NLS-1$
        ObjectClass artworkOrObject = common.objectClass(ArtworkOrObjectShownImpl.class);
        artworkOrObject.objectField("asset").indexed(true); //$NON-NLS-1$
        artworkOrObject.objectField("artworkOrObject").indexed(true); //$NON-NLS-1$
        ObjectClass creatorsContact = common.objectClass(CreatorsContactImpl.class);
        creatorsContact.cascadeOnDelete(true);
        creatorsContact.objectField("asset").indexed(true); //$NON-NLS-1$
        common.objectClass(CreatorsContact_typeImpl.class).indexed(false);
        common.objectClass(LocationCreated_typeImpl.class).indexed(false);
        common.objectClass(LocationShown_typeImpl.class).indexed(false);
        common.objectClass(ArtworkOrObject_typeImpl.class).indexed(false);
        creatorsContact.objectField("contact").indexed(true); //$NON-NLS-1$
        ObjectClass location = common.objectClass(LocationImpl.class);
        location.objectField("worldRegion").indexed(true); //$NON-NLS-1$
        location.objectField("countryISOCode").indexed(true); //$NON-NLS-1$
        location.objectField("countryName").indexed(true); //$NON-NLS-1$
        location.objectField("provinceOrState").indexed(true); //$NON-NLS-1$
        location.objectField("city").indexed(true); //$NON-NLS-1$
        location.objectField("sublocation").indexed(true); //$NON-NLS-1$
        location.objectField("details").indexed(true); //$NON-NLS-1$
        // Presentations
        common.objectClass(SlideShowImpl.class).objectField("group_slideshow_parent") //$NON-NLS-1$
                .indexed(false);
        common.objectClass(SlideShowImpl.class).objectField("lastAccessDate").indexed(true); //$NON-NLS-1$

        common.objectClass(SlideShow_typeImpl.class).indexed(false);

        common.objectClass(SlideImpl.class).objectField("asset") //$NON-NLS-1$
                .indexed(true);
        common.objectClass(Slide_typeImpl.class).indexed(false);

        common.objectClass(ExhibitionImpl.class).objectField("group_exhibition_parent") //$NON-NLS-1$
                .indexed(false);
        common.objectClass(ExhibitionImpl.class).objectField("lastAccessDate").indexed(true); //$NON-NLS-1$

        common.objectClass(Exhibition_typeImpl.class).indexed(false);

        common.objectClass(ExhibitImpl.class).objectField("asset") //$NON-NLS-1$
                .indexed(true);
        common.objectClass(Exhibit_typeImpl.class).indexed(false);

        common.objectClass(WebGalleryImpl.class).objectField("group_webGallery_parent") //$NON-NLS-1$
                .indexed(false);
        common.objectClass(WebGalleryImpl.class).objectField("lastAccessDate").indexed(true); //$NON-NLS-1$

        common.objectClass(WebGallery_typeImpl.class).indexed(false);

        common.objectClass(WebExhibitImpl.class).objectField("asset") //$NON-NLS-1$
                .indexed(true);
        common.objectClass(WebExhibit_typeImpl.class).indexed(false);
        // Categories
        common.objectClass(Category_typeImpl.class).indexed(false);
        return config;
    }

    private ObjectContainer createDatabase(String fName, boolean newDb) {
        EmbeddedConfiguration config = externalConfig != null ? externalConfig
                : createDatabaseConfiguration(false, fName, newDb ? 2500000 : -1L);
        setupCache(config);
        try {
            final ObjectContainer container = Db4oEmbedded.openFile(config, fName);
            if (DIAGNOSE) {
                StoredClass[] storedClasses = container.ext().storedClasses();
                for (StoredClass sc : storedClasses) {
                    try {
                        System.out.println(Class.forName(sc.getName()).getName());
                    } catch (ClassNotFoundException ex) {
                        System.err.println(ex);
                    }
                }
                DiagnosticConfiguration diagnostic = config.common().diagnostic();
                diagnostic.addListener(new DiagnosticListener() {
                    public void onDiagnostic(Diagnostic d) {
                        System.out.println(d);
                    }
                });
            }
            return container;
        } catch (DatabaseFileLockedException e) {
            fatalError(NLS.bind(Messages.DbManager_Unable_to_open_catalog, fName));
        } catch (Exception e) {
            fatalError(NLS.bind(Messages.DbManager_Failed_to_open_catalog, fName, e));
        }
        return null;
    }

    private static void setupCache(EmbeddedConfiguration config) {
        final int cachePageCount = 64;
        final int cachePageSize = 4096;
        config.file().storage(new CachingStorage(new FileStorage(), cachePageCount, cachePageSize) {
            @SuppressWarnings({ "rawtypes", "unchecked" })
            @Override
            protected Cache4 newCache() {
                return CacheFactory.new2QXCache(cachePageCount);
            }
        });
    }

    private void fatalError(String msg) {
        if (!emergency)
            factory.getErrorHandler().fatalError(Messages.DbManager_Catalog_error, msg, this);
    }

    public ObjectContainer getDatabase() {
        return db;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#getMeta()
     */

    public Meta getMeta(boolean create) {
        if (meta == null) {
            meta = obtainMeta();
            if (create) {
                if (meta == null)
                    meta = createMeta();
                if (!meta.getCleaned())
                    new CleanupJob().schedule(5000L);
            }
        }
        return meta;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainMeta()
     */
    private Meta obtainMeta() {
        try {
            MetaImpl metaImpl = obtainObjects(MetaImpl.class).get(0);
            if (metaImpl.getPlatform() == null)
                metaImpl.setPlatform(Platform.getOS());
            return metaImpl;
        } catch (Exception e) {
            return null;
        }
    }

    protected Meta createMeta() {
        Date creationDate = new Date();
        StringBuilder sb = new StringBuilder();
        String fName = file.getName();
        if (fName.endsWith(BatchConstants.CATEXTENSION))
            fName = fName.substring(0, fName.length() - BatchConstants.CATEXTENSION.length());
        sb.append(Constants.LOCVAR).append("/backups/").append(fName).append('.').append(Constants.DATEVAR).append( //$NON-NLS-1$
                '.' + Constants.BACKUPEXT);
        return new MetaImpl(VERSION, factory.getLireServiceVersion(), creationDate, System.getProperty("user.name"), //$NON-NLS-1$
                null, "", Messages.DbManager_User_field_1, Messages.DbManager_User_field_2, //$NON-NLS-1$
                Meta_type.timeline_month, Meta_type.locationFolders_no, new Date(0L), 0, 0, creationDate, null, sb.toString(), creationDate,
                Meta_type.thumbnailResolution_medium, false, null, null, false, 30, false, readOnly, false,
                ImageConstants.SHARPEN_MEDIUM, null, Platform.getOS(), null, Constants.PICASASCANNERVERSION, false,
                false, 75, false, null, 0L);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainById(java.lang.Class,
     * java.lang.String)
     */

    public <T extends IIdentifiableObject> T obtainById(Class<T> clazz, String id) {
        while (id != null && db != null) {
            try {
                Query query = db.query();
                query.constrain(clazz);
                query.descend(Constants.OID).constrain(id);
                ObjectSet<T> set = query.<T>execute();
                T obj = set.hasNext() ? set.next() : null;
                if (obj != null && !readOnly && !(obj instanceof RegionImpl))
                    while (set.hasNext())
                        delete(set.next()); // Delete duplicates with next commit
                dbErrorCounter = 0;
                return obj;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return null;
    }

    @Override
    public boolean exists(Class<? extends IIdentifiableObject> clazz, String id) {
        while (id != null && db != null) {
            try {
                Query query = db.query();
                query.constrain(clazz);
                query.descend(Constants.OID).constrain(id);
                boolean hasNext = query.<IIdentifiableObject>execute().hasNext();
                dbErrorCounter = 0;
                return hasNext;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#obtainByIds(java.lang.Class,
     * java.lang.String[])
     */
    public <T extends IIdentifiableObject> List<T> obtainByIds(Class<T> clazz, String[] ids) {
        return obtainObjects(clazz, Constants.OID, ids);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#obtainByIds(java.lang.Class,
     * java.util.Collection)
     */
    public <T extends IIdentifiableObject> List<T> obtainByIds(Class<T> clazz, Collection<String> ids) {
        return obtainObjects(clazz, Constants.OID, ids);
    }

    /*
     * (nicht-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#obtainStructByIds(java.lang.String,
     * java.lang.Class, java.util.Collection)
     */
    public <T extends IIdentifiableObject> List<T> obtainStructByIds(String assetId, Class<T> clazz, String[] ids) {
        if (ids == null || ids.length == 0)
            return new ArrayList<>(0);
        IPeerService peerService = factory.getPeerService();
        if (peerService != null) {
            AssetOrigin assetOrigin = peerService.getAssetOrigin(assetId);
            if (assetOrigin != null)
                try {
                    return peerService.obtainObjects(assetOrigin, clazz, Constants.OID, ids);
                } catch (ConnectionLostException e) {
                    return new ArrayList<T>(0);
                }
        }
        return obtainObjects(clazz, Constants.OID, ids);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainAsset(java.lang.String)
     */

    public AssetImpl obtainAsset(final String id) {
        return obtainById(AssetImpl.class, id);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainAssets()
     */

    public List<AssetImpl> obtainAssets() {
        return obtainObjects(AssetImpl.class);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainObjects(java.lang.Class)
     */

    public <T extends IIdentifiableObject> List<T> obtainObjects(Class<T> clazz) {
        while (db != null)
            try {
                ObjectSet<T> set = db.query(clazz);
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        return new ArrayList<T>(0);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainStructForAsset(java.lang.Class,
     * java.lang.String, boolean)
     */

    public <T extends IIdentifiableObject> List<T> obtainStructForAsset(Class<T> clazz, String id,
            boolean multiple) {
        if (id == null)
            return new ArrayList<T>(0);
        IPeerService peerService = factory.getPeerService();
        if (peerService != null) {
            AssetOrigin assetOrigin = peerService.getAssetOrigin(id);
            if (assetOrigin != null)
                try {
                    return peerService.obtainStructForAsset(assetOrigin, clazz, id, multiple);
                } catch (ConnectionLostException e) {
                    return new ArrayList<T>(0);
                }
        }
        return obtainStruct(clazz, id, multiple, null, null, false);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainStruct(java.lang.Class,
     * java.lang.String, boolean, java.lang.String, java.lang.Object, boolean)
     */

    public <T extends IIdentifiableObject> List<T> obtainStruct(Class<T> clazz, String assetId,
            boolean multipleAsset, String field, Object v, boolean multipleField) {
        return obtainObjects(clazz, false, "asset", assetId, //$NON-NLS-1$
                multipleAsset ? QueryField.CONTAINS : QueryField.EQUALS, field, v,
                multipleField ? QueryField.CONTAINS : QueryField.EQUALS);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainObjects(java.lang.Class,
     * java.lang.String, java.lang.Object, int)
     */

    public <T extends IIdentifiableObject> List<T> obtainObjects(Class<T> clazz, String field1, Object v1,
            int rel1) {
        while (db != null) {
            try {
                Query query = db.query();
                query.constrain(clazz);
                if (field1 != null)
                    applyRelation(rel1, query.descend(field1).constrain(v1));
                ObjectSet<T> set = query.execute();
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return new ArrayList<T>();

    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#obtainObjects(java.lang.Class,
     * java.lang.String, java.lang.String[])
     */
    public <T extends IIdentifiableObject> List<T> obtainObjects(Class<T> clazz, String field, String[] values) {
        while (values != null && db != null) {
            if (values.length == 0)
                return new ArrayList<T>(0);
            try {
                Query query = db.query();
                query.constrain(clazz);
                Constraint or = null;
                for (String value : values) {
                    Constraint constraint = query.descend(field).constrain(value);
                    or = or != null ? constraint.or(or) : constraint;
                }
                ObjectSet<T> set = query.execute();
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return new ArrayList<T>(0);
    }

    public <T extends IIdentifiableObject> List<T> obtainParentObjects(Class<T> clazz, String field,
            Collection<? extends IIdentifiableObject> children) {
        while (children != null && db != null) {
            if (children.isEmpty())
                break;
            try {
                if (children.size() > MAXIDS) {
                    Set<T> set = new HashSet<T>(children.size());
                    for (IIdentifiableObject child : children) {
                        Query query = db.query();
                        query.constrain(clazz);
                        query.descend(field).constrain(child.getStringId());
                        set.addAll(query.<T>execute());
                    }
                    dbErrorCounter = 0;
                    return new ArrayList<T>(set);
                }
                Query query = db.query();
                query.constrain(clazz);
                Constraint or = null;
                for (IIdentifiableObject child : children) {
                    Constraint constraint = query.descend(field).constrain(child.getStringId());
                    or = or != null ? constraint.or(or) : constraint;
                }
                ObjectSet<T> set = query.execute();
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return new ArrayList<T>(0);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#obtainObjects(java.lang.Class,
     * java.lang.String, java.util.Collection)
     */
    public <T extends IIdentifiableObject> List<T> obtainObjects(Class<T> clazz, String field,
            Collection<String> ids) {
        while (ids != null && db != null) {
            if (ids.isEmpty())
                break;
            try {
                if (ids.size() > MAXIDS) {
                    List<T> set = new ArrayList<T>(ids.size());
                    for (String id : ids) {
                        Query query = db.query();
                        query.constrain(clazz);
                        query.descend(field).constrain(id);
                        set.addAll(query.<T>execute());
                    }
                    dbErrorCounter = 0;
                    return set;
                }
                Query query = db.query();
                query.constrain(clazz);
                Constraint or = null;
                for (String id : ids) {
                    Constraint constraint = query.descend(field).constrain(id);
                    or = or != null ? constraint.or(or) : constraint;
                }
                ObjectSet<T> set = query.execute();
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return new ArrayList<T>(0);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainAssetsForFile(java.net.URI)
     */

    public List<AssetImpl> obtainAssetsForFile(URI uri) {
        while (uri != null && db != null) {
            try {
                Query query = db.query();
                query.constrain(AssetImpl.class);
                query.descend(QueryField.NAME.getKey()).constrain(Core.getFileName(uri, false));
                ObjectSet<AssetImpl> set = query.execute();
                if (!set.hasNext()) {
                    dbErrorCounter = 0;
                    return set;
                }
                List<AssetImpl> matching = new ArrayList<AssetImpl>(set.size());
                IVolumeManager vm = Core.getCore().getVolumeManager();
                for (AssetImpl asset : set)
                    if (uri.equals(vm.findFile(asset)))
                        matching.add(asset);
                dbErrorCounter = 0;
                return matching;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return EMPTYASSETS;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#obtainGhostsForFile(java.net.URI)
     */

    public List<Ghost_typeImpl> obtainGhostsForFile(URI uri) {
        while (uri != null && db != null) {
            try {
                Query query = db.query();
                query.constrain(Ghost_typeImpl.class);
                query.descend(QueryField.NAME.getKey()).constrain(Core.getFileName(uri, false));
                ObjectSet<Ghost_typeImpl> set = query.execute();
                if (!set.hasNext()) {
                    dbErrorCounter = 0;
                    return set;
                }
                List<Ghost_typeImpl> matching = new ArrayList<Ghost_typeImpl>(set.size());
                IVolumeManager vm = Core.getCore().getVolumeManager();
                for (Ghost_typeImpl ghost : set)
                    if (uri.equals(vm.findFile(ghost.getUri(), ghost.getVolume())))
                        matching.add(ghost);
                dbErrorCounter = 0;
                return matching;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return EMPTYGHOSTS;
    }

    /*
     * (nicht-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#obtainObjects(java.lang.Class,
     * java.lang.String, java.lang.Object, int, java.lang.String, java.lang.Object,
     * int, boolean)
     */
    public <T extends IIdentifiableObject> List<T> obtainObjects(Class<T> clazz, String field1, Object v1, int rel1,
            String field2, Object v2, int rel2, boolean or) {
        return obtainObjects(clazz, or, field1, v1, rel1, field2, v2, rel2);
    }

    private static void applyRelation(int rel1, Constraint constraint1) {
        switch (rel1) {
        case QueryField.STARTSWITH:
            constraint1.startsWith(false);
            break;
        case QueryField.CONTAINS:
            constraint1.contains();
            break;
        case QueryField.ENDSWITH:
            constraint1.endsWith(false);
            break;
        case QueryField.GREATER:
            constraint1.greater();
            break;
        case QueryField.SMALLER:
            constraint1.smaller();
            break;
        case QueryField.NOTGREATER:
            constraint1.greater().not();
            break;
        case QueryField.NOTSMALLER:
            constraint1.smaller().not();
            break;
        case QueryField.NOTEQUAL:
            constraint1.not();
            break;
        case QueryField.DOESNOTCONTAIN:
            constraint1.contains().not();
            break;
        case QueryField.DOESNOTSTARTWITH:
            constraint1.startsWith(false).not();
            break;
        case QueryField.DOESNOTENDWITH:
            constraint1.endsWith(false).not();
            break;
        }
    }

    public <T extends IIdentifiableObject> List<T> obtainObjects(Class<T> clazz, boolean or,
            Object... namesValuesRelations) {
        while (db != null) {
            try {
                Query query = db.query();
                query.constrain(clazz);
                Constraint constraint1 = null;
                for (int i = 0; i < namesValuesRelations.length - 2; i += 3) {
                    Object field = namesValuesRelations[i];
                    Object rel = namesValuesRelations[i + 2];
                    Constraint constraint2 = null;
                    if (field instanceof String && rel instanceof Integer) {
                        constraint2 = query.descend((String) field).constrain(namesValuesRelations[i + 1]);
                        applyRelation((Integer) rel, constraint2);
                        constraint1 = constraint1 != null
                                ? or ? constraint1.or(constraint2) : constraint1.and(constraint2)
                                : constraint2;
                    }
                }
                ObjectSet<T> set = query.execute();
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return new ArrayList<T>();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#findLocation(com.bdaum.zoom.cat.model.
     * location.LocationImpl)
     */

    public List<LocationImpl> findLocation(final LocationImpl location) {
        while (location != null && db != null) {
            try {
                Query query = db.query();
                query.constrain(LocationImpl.class);
                Constraint c = null;
                if (location.getCity() != null)
                    c = query.descend("city").constrain(location.getCity()); //$NON-NLS-1$
                if (location.getCountryISOCode() != null) {
                    Constraint c2 = query.descend("countryISOCode").constrain( //$NON-NLS-1$
                            location.getCountryISOCode());
                    c = c != null ? c2.and(c) : c2;
                } else if (location.getCountryName() != null) {
                    Constraint c2 = query.descend("countryName").constrain( //$NON-NLS-1$
                            location.getCountryName());
                    c = c != null ? c2.and(c) : c2;
                }
                if (location.getProvinceOrState() != null) {
                    Constraint c2 = query.descend("provinceOrState").constrain( //$NON-NLS-1$
                            location.getProvinceOrState());
                    c = c != null ? c2.and(c) : c2;
                }
                if (location.getSublocation() != null) {
                    Constraint c2 = query.descend("sublocation").constrain( //$NON-NLS-1$
                            location.getSublocation());
                    c = c != null ? c2.and(c) : c2;
                }
                if (location.getDetails() != null) {
                    Constraint c2 = query.descend("details").constrain( //$NON-NLS-1$
                            location.getDetails());
                    c = c != null ? c2.and(c) : c2;
                }
                ObjectSet<LocationImpl> set = query.execute();
                dbErrorCounter = 0;
                return set;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectToDb(e);
            } catch (Db4oException e) {
                reconnectToDb(e);
            }
        }
        return EMPTYLOCATIONS;

    }

    public <T extends IIdentifiableObject> List<T> queryByExample(T example) {
        if (example != null) {
            String id = example.getStringId();
            example.setStringId(null);
            try {
                while (db != null) {
                    try {
                        ObjectSet<T> set = db.queryByExample(example);
                        dbErrorCounter = 0;
                        return set;
                    } catch (DatabaseClosedException e) {
                        break;
                    } catch (IllegalStateException e) {
                        reconnectToDb(e);
                    } catch (Db4oException e) {
                        reconnectToDb(e);
                    }
                }
            } finally {
                example.setStringId(id);
            }
        }
        return new ArrayList<T>(0);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#storeAndCommit(java.lang.Object)
     */

    public void storeAndCommit(Object obj) {
        store(obj);
        commit();
    }

    public void reconnectToDb(RuntimeException e) {
        // Reset lucene
        if (++dbErrorCounter > 100 || emergency)
            throw e;
        endSafeTransaction();
        removeLuceneWriteLock();
        while (!file.exists())
            promptForReconnect();
        try {
            db.close();
        } catch (Exception e2) {
            // ignore
        }
        db = createDatabase(fileName, false);
    }

    private boolean removeLuceneWriteLock() {
        return luceneLockFile == null ? false : luceneLockFile.delete();
    }

    public boolean isLuceneLocked() {
        return luceneLockFile != null && luceneLockFile.exists();
    }

    public File getIndexPath() {
        return indexPath;
    }

    public <T extends Object> List<T> getTrash(Class<T> clazz, String opId) {
        while (trashCan != null) {
            try {
                Query query = trashCan.query();
                query.constrain(clazz);
                if (opId != null)
                    query.descend("opId").constrain(opId); //$NON-NLS-1$
                return query.execute();
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectTrashcanToDb(e);
            } catch (Db4oException e) {
                reconnectTrashcanToDb(e);
            }
        }
        return new ArrayList<T>();
    }

    public List<Trash> obtainTrashToDelete(boolean withFiles) {
        while (trashCan != null) {
            try {
                ObjectSet<Trash> trash;
                if (withFiles) {
                    Query query = trashCan.query();
                    query.constrain(Trash.class);
                    query.descend("files").constrain(true); //$NON-NLS-1$
                    trash = query.execute();
                } else
                    trash = trashCan.query(Trash.class);
                dbErrorCounter = 0;
                return trash;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectTrashcanToDb(e);
            } catch (Db4oException e) {
                reconnectTrashcanToDb(e);
            }
        }
        return EMPTYTRASH;
    }

    public List<Trash> obtainTrash(boolean byName) {
        while (trashCan != null) {
            try {
                Query query = trashCan.query();
                query.constrain(Trash.class);
                if (byName)
                    query.descend("name").orderAscending(); //$NON-NLS-1$
                else
                    query.descend("date").orderDescending(); //$NON-NLS-1$
                ObjectSet<Trash> trash = query.execute();
                dbErrorCounter = 0;
                return trash;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectTrashcanToDb(e);
            } catch (Db4oException e) {
                reconnectTrashcanToDb(e);
            }
        }
        return EMPTYTRASH;
    }

    public List<Trash> obtainTrashForFile(URI uri) {
        while (uri != null && trashCan != null) {
            try {
                Query query = trashCan.query();
                query.constrain(Trash.class);
                query.descend("name").constrain(Core.getFileName(uri, false)); //$NON-NLS-1$
                ObjectSet<Trash> set = query.execute();
                dbErrorCounter = 0;
                if (!set.hasNext())
                    return set;
                List<Trash> matching = new ArrayList<Trash>(set.size());
                IVolumeManager vm = Core.getCore().getVolumeManager();
                for (Trash t : set)
                    if (uri.equals(vm.findFile(t.getUri(), t.getVolume())))
                        matching.add(t);
                return matching;
            } catch (DatabaseClosedException e) {
                break;
            } catch (IllegalStateException e) {
                reconnectTrashcanToDb(e);
            } catch (Db4oException e) {
                reconnectTrashcanToDb(e);
            }
        }
        return EMPTYTRASH;
    }

    private void reconnectTrashcanToDb(RuntimeException e) {
        if (++dbErrorCounter > 100 || emergency)
            throw e;
        String trashcanName = getTrashcanName();
        while (!new File(trashcanName).exists())
            promptForReconnect();
        try {
            trashCan.close();
        } catch (Exception e2) {
            // ignore
        }
        trashCan = Db4oEmbedded.openFile(createTrashcanConfiguration(), trashcanName);
    }

    private void promptForReconnect() {
        IInputValidator validator = new IInputValidator() {
            public String isValid(String newText) {
                return file.exists() ? null : Messages.DbManager_file_missing;
            }
        };
        factory.getErrorHandler().promptForReconnect(Messages.DbManager_lost_connection,
                Messages.DbManager_reinsert_media, validator, this);
    }

    public String getTrashcanName() {
        return fileName + ".history"; //$NON-NLS-1$
    }

    public ObjectContainer getTrashCan() {
        if (trashCan == null)
            trashCan = Db4oEmbedded.openFile(createTrashcanConfiguration(), getTrashcanName());
        return trashCan;
    }

    public boolean hasTrash() {
        return trashCan != null;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#getFileName()
     */

    public String getFileName() {
        return fileName;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#getFile()
     */

    public File getFile() {
        return file;
    }

    /*
     * (nicht-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#close(int)
     */
    public void close(int mode) {
        if (mode == CatalogListener.NORMAL)
            for (IDbListener listener : factory.getListeners()) {
                try {
                    if (!listener.databaseAboutToClose(this))
                        return;
                } catch (Exception e) {
                    // ignore
                }
            }
        else if (mode == CatalogListener.EMERGENCY)
            emergency = true;
        resetSystemCollectionCache();
        if (db != null) {
            if (!emergency) // db4o has its own shutdown hook
                try {
                    db.close();
                } catch (Db4oIOException e) {
                    // ignore
                } catch (IllegalStateException e) {
                    // ignore
                }
            db = null;
            meta = null;
            dirtyCollections.clear();
            transactionFailed = false;
        }
        for (Object listener : factory.getListeners())
            try {
                ((IDbListener) listener).databaseClosed(this, mode);
            } catch (Exception e) {
                // ignore
            }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#closeTrash()
     */

    public void closeTrash() {
        if (hasTrash()) {
            try {
                trashCan.close();
            } catch (Db4oIOException e) {
                // ignore
            } catch (IllegalStateException e) {
                // ignore
            }
            trashCan = null;
            trashFailed = false;
            new File(getTrashcanName()).delete();
        }
    }

    public boolean createFolderHierarchy(final Asset asset) {
        return createFolderHierarchy(asset, Constants.WIN32);
    }

    public boolean createFolderHierarchy(final Asset asset, final boolean win32) {
        final boolean[] changeIndicator = new boolean[] { false };
        safeTransaction(() -> {
            try {
                String path = asset.getUri();
                int p = path.lastIndexOf('/');
                if (p > 0) {
                    String folderUri = path.substring(0, p);
                    if (lastFolderUris == null)
                        lastFolderUris = new HashSet<>();
                    else if (lastFolderUris.contains(folderUri))
                        return;
                    lastFolderUris.add(folderUri);
                }
                GroupImpl directories = getCollectionGroup(Constants.GROUP_ID_FOLDERSTRUCTURE,
                        Messages.DbManager_Directories, true);
                SmartCollection parentColl = null;
                int pathFrom = 0;
                StringBuilder sb = new StringBuilder();
                String protocol = ""; //$NON-NLS-1$
                String device = ""; //$NON-NLS-1$
                p = path.indexOf(':', pathFrom);
                if (p >= pathFrom) {
                    sb.append(protocol = path.substring(pathFrom, p)).append(':');
                    pathFrom = p;
                }
                while (path.charAt(++pathFrom) == '/')
                    sb.append('/');
                String prot = sb.toString();
                if (!Constants.FILESCHEME.equals(protocol))
                    parentColl = createDirectoryCollection(protocol + ':', parentColl, directories,
                            QueryField.URI.getKey(), prot, QueryField.STARTSWITH, changeIndicator);
                else if (win32) {
                    p = path.indexOf(':', pathFrom);
                    if (p >= pathFrom) {
                        sb.append(device = path.substring(pathFrom, p + 1));
                        pathFrom = p;
                        while (path.charAt(++pathFrom) == '/')
                            sb.append('/');
                        String volume = asset.getVolume();
                        if (volume == null || volume.isEmpty())
                            volume = device;
                        parentColl = createDirectoryCollection(volume, parentColl, directories,
                                QueryField.VOLUME.getKey(), volume, QueryField.EQUALS, changeIndicator);
                    }
                }
                StringTokenizer tokenizer = new StringTokenizer(path.substring(pathFrom), "/", true); //$NON-NLS-1$
                String previousToken = null;
                while (tokenizer.hasMoreTokens()) {
                    String token = tokenizer.nextToken();
                    sb.append(token);
                    if ("/".equals(token)) { //$NON-NLS-1$
                        if (previousToken != null) {
                            try {
                                previousToken = URLDecoder.decode(previousToken, "UTF-8"); //$NON-NLS-1$
                            } catch (UnsupportedEncodingException e1) {
                                // do nothing
                            }
                            parentColl = createDirectoryCollection(previousToken, parentColl, directories,
                                    QueryField.URI.getKey(), sb.toString(), QueryField.STARTSWITH, changeIndicator);
                            previousToken = null;
                        }
                    } else
                        previousToken = token;
                }
            } catch (RuntimeException e2) {
                DbActivator.getDefault().logError(Messages.DbManager_internal_error_folder_hierarchy, e2);
            }

        });
        return changeIndicator[0];
    }

    public SmartCollection createDirectoryCollection(String name, SmartCollection parentColl, GroupImpl group,
            String fieldname, String fieldvalue, int rel, boolean[] changeIndicator) {
        String key = new StringBuilder(fieldname).append('=').append(fieldvalue).toString();
        List<SmartCollectionImpl> set = obtainObjects(SmartCollectionImpl.class, Constants.OID, key,
                QueryField.EQUALS);
        SmartCollectionImpl coll1 = null;
        if (set.isEmpty()) {
            coll1 = new SmartCollectionImpl(name, true, false, false, false, null, 0, null, 0, null,
                    Constants.INHERIT_LABEL, null, 0, null);
            coll1.setStringId(key);
            Criterion crit = new CriterionImpl(fieldname, null, fieldvalue, rel, false);
            coll1.addCriterion(crit);
            SortCriterion sortCrit = new SortCriterionImpl(fieldname, null, false);
            coll1.addSortCriterion(sortCrit);
            if (parentColl != null) {
                parentColl.addSubSelection(coll1);
                store(parentColl);
            } else {
                group.addRootCollection(key);
                coll1.setGroup_rootCollection_parent(group.getStringId());
                store(group);
            }
            store(coll1);
            store(crit);
            store(sortCrit);
            changeIndicator[0] = true;
        } else {
            for (SmartCollectionImpl sm : set) {
                if (coll1 == null)
                    coll1 = sm;
                else {
                    // remove duplicates
                    changeIndicator[0] = true;
                    transferChildren(sm, coll1);
                    SmartCollection psm = sm.getSmartCollection_subSelection_parent();
                    if (psm != null && psm != parentColl) {
                        psm.removeSubSelection(sm);
                        store(psm);
                    }
                    String gsmid = sm.getGroup_rootCollection_parent();
                    if (gsmid != null && !gsmid.equals(group.getStringId())) {
                        GroupImpl gsm = obtainById(GroupImpl.class, gsmid);
                        if (gsm != null && gsm != group) {
                            gsm.removeRootCollection(sm.getStringId());
                            store(gsm);
                        }
                    }
                    deleteCollection(sm);
                }
                if (parentColl != null)
                    parentColl.removeSubSelection(sm);
                else
                    group.removeRootCollection(sm.getStringId());
            }
            if (parentColl != null) {
                parentColl.addSubSelection(coll1);
                coll1.setSmartCollection_subSelection_parent(parentColl);
                store(parentColl);
            } else {
                group.addRootCollection(coll1.getStringId());
                coll1.setGroup_rootCollection_parent(group.getStringId());
                store(group);
            }
            store(coll1);
        }
        return coll1;
    }

    private void transferChildren(SmartCollection source, SmartCollection target) {
        SmartCollection found = null;
        for (Iterator<SmartCollection> it = source.getSubSelection().iterator(); it.hasNext();) {
            SmartCollection subcol = it.next();
            String id = subcol.getStringId();
            for (SmartCollection cand : target.getSubSelection())
                if (cand.getStringId().equals(id)) {
                    found = cand;
                    if (subcol == cand)
                        it.remove();
                    else
                        transferChildren(subcol, found);
                    break;
                }
            if (found == null) {
                target.addSubSelection(subcol);
                subcol.setSmartCollection_subSelection_parent(target);
                it.remove();
            }
        }
    }

    private void deleteCollection(SmartCollection collection) {
        if (collection != null) {
            AomList<SmartCollection> subSelections = collection.getSubSelection();
            if (subSelections != null)
                for (SmartCollection sub : subSelections)
                    deleteCollection(sub);
            AomList<Criterion> criterions = collection.getCriterion();
            if (criterions != null)
                for (Criterion crit : criterions)
                    delete(crit);
            AomList<SortCriterion> sortCriterions = collection.getSortCriterion();
            if (sortCriterions != null)
                for (SortCriterion crit : sortCriterions)
                    delete(crit);
            PostProcessor postProcessor = collection.getPostProcessor();
            if (postProcessor != null)
                delete(postProcessor);
            delete(collection);
        }
    }

    /*
     * (nicht-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#createLastImportCollection(java.util
     * .Date, boolean, java.lang.String)
     */
    public Date createLastImportCollection(Date importDate, boolean cumulate, String description,
            boolean tethered) {
        Date previousImport = null;
        GroupImpl group = getImportGroup();
        GroupImpl subgroup = null;
        int showLabel = Constants.INHERIT_LABEL;
        String labelTemplate = null;
        SmartCollectionImpl coll = obtainById(SmartCollectionImpl.class, Constants.LAST_IMPORT_ID);
        if (coll != null) {
            Criterion criterion = coll.getCriterion().isEmpty() ? null : coll.getCriterion(0);
            if (criterion != null) {
                Object value = criterion.getValue();
                if (value instanceof Range) {
                    Range range = (Range) value;
                    previousImport = (Date) range.getTo();
                    if (range instanceof TetheredRange && tethered
                            || cumulate && (description == null || description.equals(coll.getDescription()))) {
                        range.setTo(importDate);
                        store(range);
                        return previousImport;
                    }
                } else
                    previousImport = (Date) value;
                group.removeRootCollection(Constants.LAST_IMPORT_ID);
                subgroup = getRecentImportsSubgroup(group);
                subgroup.addRootCollection(Utilities.setImportKeyAndLabel(coll, value));
                coll.setGroup_rootCollection_parent(subgroup.getStringId());
                store(coll);
            } else {
                group.removeRootCollection(Constants.LAST_IMPORT_ID);
                delete(coll);
            }
            showLabel = coll.getShowLabel();
            labelTemplate = coll.getLabelTemplate();
        }
        SmartCollectionImpl newcoll = new SmartCollectionImpl(
                tethered ? Messages.DbManager_tethered
                        : cumulate ? Messages.DbManager_recent_bg_imports : Messages.DbManager_last_import,
                true, false, false, false, description, 0, null, 0, null, showLabel, labelTemplate, 0, null);
        newcoll.setStringId(Constants.LAST_IMPORT_ID);
        Criterion criterion = new CriterionImpl(QueryField.IMPORTDATE.getKey(), null, "", //$NON-NLS-1$
                cumulate ? QueryField.BETWEEN : QueryField.DATEEQUALS, false);
        newcoll.addCriterion(criterion);
        SortCriterion sortCrit = new SortCriterionImpl(QueryField.NAME.getKey(), null, false);
        newcoll.addSortCriterion(sortCrit);
        newcoll.setGroup_rootCollection_parent(group.getStringId());
        group.addRootCollection(Constants.LAST_IMPORT_ID);
        criterion.setValue(tethered ? new TetheredRange(importDate, importDate)
                : cumulate ? new Range(importDate, importDate) : importDate);
        store(criterion);
        store(sortCrit);
        store(newcoll);
        store(group);
        if (subgroup != null)
            store(subgroup);
        return previousImport;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#getImportGroup()
     */

    private GroupImpl getRecentImportsSubgroup(GroupImpl group) {
        GroupImpl subgroup = obtainById(GroupImpl.class, Constants.GROUP_ID_RECENTIMPORTS);
        if (subgroup == null) {
            subgroup = new GroupImpl(Messages.DbManager_recent_imports, true, Constants.INHERIT_LABEL, null, 0,
                    null);
            subgroup.setStringId(Constants.GROUP_ID_RECENTIMPORTS);
            subgroup.setGroup_subgroup_parent(group);
            group.addSubgroup(subgroup);
            for (Iterator<String> it = group.getRootCollection().iterator(); it.hasNext();) {
                String id = it.next();
                if (!id.equals(Constants.LAST_IMPORT_ID)) {
                    SmartCollectionImpl coll = obtainById(SmartCollectionImpl.class, id);
                    if (coll != null) {
                        coll.setGroup_rootCollection_parent(Constants.GROUP_ID_RECENTIMPORTS);
                        store(coll);
                        subgroup.addRootCollection(id);
                    }
                    it.remove();
                }
            }
        }
        return subgroup;
    }

    public GroupImpl getImportGroup() {
        return getCollectionGroup(Constants.GROUP_ID_IMPORTS, Messages.DbManager_imports, true);
    }

    private GroupImpl getCollectionGroup(String id, String label, boolean system) {
        GroupImpl group = obtainById(GroupImpl.class, id);
        if (group == null) {
            group = new GroupImpl(label, system, Constants.INHERIT_LABEL, null, 0, null);
            group.setStringId(id);
        }
        return group;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#createTimeLine(com.bdaum.zoom.cat.model
     * .asset.Asset, java.lang.String)
     */

    public boolean createTimeLine(final Asset asset, final String timeline) {
        if (timeline == null || timeline.equals(Meta_type.timeline_no))
            return false;
        Date dateCreated = asset.getDateCreated();
        if (dateCreated == null)
            dateCreated = asset.getDateTimeOriginal();
        if (dateCreated == null)
            return false;
        long day = dateCreated.getTime() / ONEDAY;
        if (previousDay == day && previousTimeline == timeline)
            return false;
        previousDay = day;
        previousTimeline = timeline;
        final GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(dateCreated);
        final boolean[] changeIndicator = new boolean[] { false };
        safeTransaction(() -> {
            try {
                GroupImpl timeLineGroup = getCollectionGroup(Constants.GROUP_ID_TIMELINE,
                        Messages.DbManager_Timeline, true);
                int year = cal.get(Calendar.YEAR);
                StringBuilder ksb = new StringBuilder(DATETIMEKEY);
                ksb.append(year);
                SmartCollectionImpl parentColl = createTimelineCollection(String.valueOf(year), null, timeLineGroup,
                        ksb.toString(), new GregorianCalendar(year, 0, 1),
                        new GregorianCalendar(year, 11, 31, 23, 59, 59), changeIndicator);
                store(timeLineGroup);
                store(parentColl);
                if (!timeline.equals(Meta_type.timeline_year)) {
                    if (timeline.equals(Meta_type.timeline_week)
                            || timeline.equals(Meta_type.timeline_weekAndDay)) {
                        int week = cal.get(Calendar.WEEK_OF_YEAR);
                        GregorianCalendar from1 = new GregorianCalendar(year, 0, 1);
                        from1.set(GregorianCalendar.WEEK_OF_YEAR, week);
                        from1.set(GregorianCalendar.DAY_OF_WEEK, 0);
                        ksb.append("-W").append(week); //$NON-NLS-1$
                        GregorianCalendar to = new GregorianCalendar(year, 0, 1);
                        to.set(Calendar.WEEK_OF_YEAR, week);
                        to.set(Calendar.DAY_OF_WEEK, 6);
                        to.set(Calendar.HOUR_OF_DAY, 23);
                        to.set(Calendar.MINUTE, 59);
                        to.set(Calendar.SECOND, 59);
                        parentColl = createTimelineCollection(
                                new SimpleDateFormat(Messages.DbManager_date_mask_week).format(from1.getTime()),
                                parentColl, timeLineGroup, ksb.toString(), from1, to, changeIndicator);
                        if (!timeline.equals(Meta_type.timeline_week)) {
                            int day11 = cal.get(Calendar.DAY_OF_WEEK);
                            ksb.append('-').append(day11);
                            from1 = new GregorianCalendar(year, 0, 1);
                            from1.set(Calendar.WEEK_OF_YEAR, week);
                            from1.set(Calendar.DAY_OF_WEEK, day11);
                            to = new GregorianCalendar(year, 0, 1);
                            to.set(Calendar.WEEK_OF_YEAR, week);
                            to.set(Calendar.DAY_OF_WEEK, day11);
                            to.set(Calendar.HOUR_OF_DAY, 23);
                            to.set(Calendar.MINUTE, 59);
                            to.set(Calendar.SECOND, 59);
                            createTimelineCollection(
                                    new SimpleDateFormat(Messages.DbManager_date_mask_day_of_week)
                                            .format(from1.getTime()),
                                    parentColl, timeLineGroup, ksb.toString(), from1, to, changeIndicator);
                        }
                    } else {
                        int month = cal.get(Calendar.MONTH);
                        GregorianCalendar from2 = new GregorianCalendar(year, month, 1);
                        ksb.append('-').append(month);
                        parentColl = createTimelineCollection(
                                new SimpleDateFormat(Messages.DbManager_date_mask_month).format(from2.getTime()),
                                parentColl, timeLineGroup, ksb.toString(), from2, new GregorianCalendar(year, month,
                                        from2.getActualMaximum(Calendar.DAY_OF_MONTH), 23, 59, 59),
                                changeIndicator);
                        if (!timeline.equals(Meta_type.timeline_month)) {
                            int day12 = cal.get(Calendar.DAY_OF_MONTH);
                            ksb.append('-').append(day12);
                            createTimelineCollection(String.valueOf(day12), parentColl, timeLineGroup,
                                    ksb.toString(), new GregorianCalendar(year, month, day12),
                                    new GregorianCalendar(year, month, day12, 23, 59, 59), changeIndicator);
                        }
                    }
                }
            } catch (RuntimeException e) {
                DbActivator.getDefault().logError(Messages.DbManager_internal_error_timeline, e);
            }
        });
        return changeIndicator[0];
    }

    public SmartCollectionImpl createTimelineCollection(String name, SmartCollection parentColl, GroupImpl timeLine,
            String id, GregorianCalendar from, GregorianCalendar to, boolean[] changeIndicator) {
        SmartCollectionImpl coll = obtainById(SmartCollectionImpl.class, id);
        if (coll == null) {
            coll = new SmartCollectionImpl(name, true, false, false, false, null, 0, null, 0, null,
                    Constants.INHERIT_LABEL, null, 0, null);
            coll.setStringId(id);
            Range value = new Range(from.getTime(), to.getTime());
            Criterion exifCrit = new CriterionImpl(QueryField.IPTC_DATECREATED.getKey(), null, value,
                    QueryField.BETWEEN, false);
            coll.addCriterion(exifCrit);
            SortCriterion sortCrit1 = new SortCriterionImpl(QueryField.IPTC_DATECREATED.getKey(), null, false);
            coll.addSortCriterion(sortCrit1);
            if (parentColl != null) {
                parentColl.addSubSelection(coll);
                store(parentColl);
            } else {
                coll.setGroup_rootCollection_parent(timeLine.getStringId());
                timeLine.addRootCollection(coll.getStringId());
            }
            store(coll);
            store(exifCrit);
            store(value);
            store(sortCrit1);
            changeIndicator[0] = true;
        }
        return coll;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#createLocationFolders(com.bdaum.zoom
     * .cat.model.asset.Asset, java.lang.String)
     */
    public boolean createLocationFolders(Asset asset, String locationOption) {
        if (locationOption == null || locationOption.equals(Meta_type.locationFolders_no))
            return false;
        for (LocationCreatedImpl locationCreated : new ArrayList<LocationCreatedImpl>(
                obtainStructForAsset(LocationCreatedImpl.class, asset.getStringId(), true))) {
            LocationImpl loc = obtainById(LocationImpl.class, locationCreated.getLocation());
            if (loc != null)
                return createLocationFolders(loc, locationOption);
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#createLocationFolders(com.bdaum.zoom
     * .cat.model.location.Location, java.lang.String)
     */
    public boolean createLocationFolders(final Location location, final String locationOption) {
        if (locationOption == null || locationOption.equals(Meta_type.locationFolders_no))
            return false;
        final StringBuilder ksb = new StringBuilder(96);
        final String regionCode = location.getWorldRegionCode();
        final String countryCode = location.getCountryISOCode();
        final String state = location.getProvinceOrState();
        final String city = location.getCity();
        ksb.append(LOCATIONKEY).append(regionCode).append('|').append(countryCode).append('|').append(state)
                .append('|').append(city);
        final String locationKey = ksb.toString();
        if (previousLocationKeys == null)
            previousLocationKeys = new HashSet<>();
        else if (previousLocationKeys.contains(locationKey))
            return false;
        previousLocationKeys.add(locationKey);
        ksb.setLength(LOCATIONKEY.length());
        final boolean[] changeIndicator = new boolean[] { false };
        safeTransaction(() -> {
            try {
                GroupImpl locationGroup = getCollectionGroup(Constants.GROUP_ID_LOCATIONS,
                        Messages.DbManager_locations, true);
                // Continent
                ksb.append(regionCode);
                String name = regionCode != null ? GeoMessages.getString(GeoMessages.PREFIX + regionCode)
                        : Messages.DbManager_unknown_world_region;
                Criterion crit = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                        QueryField.LOCATION_WORLDREGIONCODE.getKey(), regionCode,
                        regionCode == null || regionCode.isEmpty() ? QueryField.UNDEFINED : QueryField.EQUALS,
                        true);
                SmartCollectionImpl parentColl = createLocationCollection(name, null, locationGroup, ksb.toString(),
                        changeIndicator, crit);
                store(locationGroup);
                store(parentColl);
                // Country
                ksb.append('|').append(countryCode);
                name = location.getCountryName();
                if (name == null)
                    name = Messages.DbManager_unknown_country;
                crit = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                        QueryField.LOCATION_COUNTRYCODE.getKey(), countryCode,
                        countryCode == null || countryCode.isEmpty() ? QueryField.UNDEFINED : QueryField.EQUALS,
                        true);
                parentColl = createLocationCollection(name, parentColl, null, ksb.toString(), changeIndicator,
                        crit);
                if (!locationOption.equals(Meta_type.locationFolders_country)) {
                    // State
                    ksb.append('|').append(state);
                    name = state;
                    if (name == null)
                        name = Messages.DbManager_unknown_state;
                    crit = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                            QueryField.LOCATION_STATE.getKey(), state,
                            state == null || state.isEmpty() ? QueryField.UNDEFINED : QueryField.EQUALS, true);
                    CriterionImpl crit2 = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                            QueryField.LOCATION_COUNTRYCODE.getKey(), countryCode,
                            countryCode == null || countryCode.isEmpty() ? QueryField.UNDEFINED : QueryField.EQUALS,
                            true);
                    parentColl = createLocationCollection(name, parentColl, null, ksb.toString(), changeIndicator,
                            crit, crit2);
                    if (!locationOption.equals(Meta_type.locationFolders_state)) {
                        // City
                        name = city;
                        if (name == null)
                            name = Messages.DbManager_unknown_city;
                        crit = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                                QueryField.LOCATION_CITY.getKey(), city,
                                city == null || city.isEmpty() ? QueryField.UNDEFINED : QueryField.EQUALS, true);

                        crit2 = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                                QueryField.LOCATION_STATE.getKey(), state,
                                state == null || state.isEmpty() ? QueryField.UNDEFINED : QueryField.EQUALS, true);
                        CriterionImpl crit3 = new CriterionImpl(QueryField.IPTC_LOCATIONCREATED.getKey(),
                                QueryField.LOCATION_COUNTRYCODE.getKey(), countryCode,
                                countryCode == null || countryCode.isEmpty() ? QueryField.UNDEFINED
                                        : QueryField.EQUALS,
                                true);
                        parentColl = createLocationCollection(name, parentColl, null, locationKey, changeIndicator,
                                crit, crit2, crit3);
                    }
                }
            } catch (RuntimeException e) {
                DbActivator.getDefault().logError(Messages.DbManager_internal_error_creation_locations, e);
            }

        });
        return changeIndicator[0];
    }

    protected SmartCollectionImpl createLocationCollection(String name, SmartCollectionImpl parentColl,
            GroupImpl locationGroup, String id, boolean[] changeIndicator, Criterion... criteria) {
        SmartCollectionImpl coll = obtainById(SmartCollectionImpl.class, id);
        if (coll == null) {
            coll = new SmartCollectionImpl(name, true, false, false, false, null, 0, null, 0, null,
                    Constants.INHERIT_LABEL, null, 0, null);
            coll.setStringId(id);
            for (Criterion criterion : criteria)
                coll.addCriterion(criterion);
            SortCriterion sortCrit1 = new SortCriterionImpl(QueryField.IPTC_DATECREATED.getKey(), null, false);
            coll.addSortCriterion(sortCrit1);
            if (parentColl != null) {
                parentColl.addSubSelection(coll);
                store(parentColl);
            } else {
                coll.setGroup_rootCollection_parent(locationGroup.getStringId());
                locationGroup.addRootCollection(coll.getStringId());
            }
            store(coll);
            for (Criterion criterion : criteria)
                store(criterion);
            store(sortCrit1);
            changeIndicator[0] = true;
        }
        return coll;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#checkDbSanity(boolean)
     *
     * May fail due to DB4O COR-2105
     */
    public void checkDbSanity(final boolean force) {
        // MessageDialog.openInformation(null, "", systemInfo.freespaceSize()
        // + ", " + systemInfo.totalSize() + "; "
        // + systemInfo.freespaceEntryCount());
        if (!emergency)
            try {
                if (force || needsDefragmentation()) {
                    Meta meta1 = getMeta(false);
                    boolean hasBackups = meta1 == null
                            || meta1.getBackupLocation() != null && !meta1.getBackupLocation().isEmpty();
                    if (force || hasBackups
                            || factory.getErrorHandler().question(Messages.DbManager_Catalog_maintenance,
                                    Messages.DbManager_The_cat_seems_to_be_fragmented, this)) {
                        SystemInfo systemInfo = getSystemInfo();
                        long totalSize = systemInfo.totalSize();
                        long freespaceSize = systemInfo.freespaceSize();
                        long occupiedspaceSize = totalSize - freespaceSize;
                        final long reserve = isReadOnly() ? occupiedspaceSize / 100 : occupiedspaceSize / 20;
                        final long defragSpaceSize = occupiedspaceSize + reserve;
                        long requiredSpace = defragSpaceSize + (force ? 0 : totalSize);
                        long availableSpace = file.getUsableSpace();
                        if (availableSpace < requiredSpace) {
                            factory.getErrorHandler().showError(Messages.DbManager_Catalog_maintenance, NLS.bind(
                                    Messages.DbManager_not_enough_disc_space, requiredSpace, availableSpace), this);
                            return;
                        }
                        final String backupPath = fileName + ".defrag"; //$NON-NLS-1$
                        File backup = new File(backupPath);
                        ProgressMonitorDialog dialog = new ProgressMonitorDialog(null);
                        try {
                            dialog.run(true, false, new IRunnableWithProgress() {

                                public void run(IProgressMonitor monitor)
                                        throws InvocationTargetException, InterruptedException {
                                    try {
                                        monitor.beginTask(NLS.bind(Messages.DbManager_Defragmenting_, fileName),
                                                IProgressMonitor.UNKNOWN);
                                        if (!force) {
                                            monitor.subTask(Messages.DbManager_backing_up_cat);
                                            performBackup(0L, -1L, true);
                                        }

                                        db.close();
                                        db = null;
                                        monitor.subTask(Messages.DbManager_defragmenting_cat);

                                        DefragmentConfig config = new DefragmentConfig(fileName, backupPath);
                                        boolean fileNeedsUpgrade = config.fileNeedsUpgrade();
                                        EmbeddedConfiguration ef = createDatabaseConfiguration(fileNeedsUpgrade,
                                                fileName, defragSpaceSize);
                                        try {
                                            defragment(fileName, backupPath, ef);
                                            ef = createDatabaseConfiguration(false, fileName, -1L);
                                            db = Db4oEmbedded.openFile(ef, fileName);
                                            SystemInfo systemInfo = getSystemInfo();
                                            long newFreespaceSize = systemInfo.freespaceSize();
                                            long newTotalSize = systemInfo.totalSize();
                                            db.close();
                                            db = null;
                                            if (newFreespaceSize > 3 * reserve) {
                                                monitor.subTask(Messages.DbManager_defrag_cat_2);
                                                long newOccupiedspaceSize = newTotalSize - newFreespaceSize;
                                                long newReserve = isReadOnly() ? newOccupiedspaceSize / 100
                                                        : newOccupiedspaceSize / 20;
                                                long newDefragSpaceSize = newOccupiedspaceSize + newReserve;
                                                ef = createDatabaseConfiguration(fileNeedsUpgrade, fileName,
                                                        newDefragSpaceSize);
                                                defragment(fileName, backupPath, ef);
                                            }
                                        } catch (Exception e) {
                                            File target = new File(fileName);
                                            target.delete();
                                            new File(backupPath).renameTo(target);
                                            throw new InvocationTargetException(e);
                                        }
                                        if (fileNeedsUpgrade) {
                                            monitor.subTask(Messages.DbManager_updating_database_version);
                                            db = Db4oEmbedded.openFile(ef, fileName);
                                            db.close();
                                            db = null;
                                            DbActivator.getDefault()
                                                    .logInfo(NLS.bind(
                                                            Messages.DbManager_database_converted_to_version_n,
                                                            Db4o.version()));
                                        }
                                    } finally {
                                        if (db == null)
                                            db = createDatabase(fileName, false);
                                    }
                                }
                            });
                        } catch (InvocationTargetException e1) {
                            factory.getErrorHandler().showError(Messages.DbManager_Defrag_error,
                                    NLS.bind(Messages.DbManager_Defrag_failed, e1.getCause()), this);
                            DbActivator.getDefault().logError(Messages.DbManager_Defrag_error, e1);
                            return;
                        } catch (InterruptedException e1) {
                            // should never happen
                        } finally {
                            if (!file.exists()) {
                                if (backup.exists())
                                    backup.renameTo(file);
                                DbActivator.getDefault().logError(Messages.DbManager_defragmentation_failed, null);
                                return;
                            }
                        }
                        backup.delete();
                        DbActivator.getDefault().logInfo(Messages.DbManager_defragmentation_successfiul);
                    }
                }
            } catch (Throwable e) {
                DbActivator.getDefault().logError(Messages.DbManager_error_checking_sanity, e);
            }
    }

    protected void defragment(String fName, String backupPath, EmbeddedConfiguration config) {
        if (db != null) {
            db.close();
            db = null;
        }
        File backupFile = new File(backupPath);
        File newFile = new File(fName);
        if (newFile.exists()) {
            backupFile.delete();
            newFile.renameTo(backupFile);
        }
        EmbeddedConfiguration sourceConfig = createDatabaseConfiguration(false, backupPath, -1);
        final ObjectContainer sourceContainer = Db4oEmbedded.openFile(sourceConfig, backupPath);
        final ObjectContainer targetContainer = Db4oEmbedded.openFile(config, fName);
        try {
            StoredClass[] storedClasses = sourceContainer.ext().storedClasses();
            Map<String, Class<?>> typeMap = new HashMap<>();
            for (StoredClass sc : storedClasses) {
                Class<?> c = null;
                try {
                    c = Class.forName(sc.getName());
                } catch (ClassNotFoundException ex) {
                    // ignore entries from unknown packages
                    // media extension are covered via MediaExtensionImpl
                }
                if (c != null) {
                    String simpleName = c.getSimpleName();
                    if (simpleName.endsWith(TYPEIMPL)) {
                        String shortName = simpleName.substring(0, simpleName.length() - TYPEIMPL.length());
                        if (!typeMap.containsKey(shortName))
                            typeMap.put(shortName, c);
                    } else if (simpleName.endsWith(IMPL))
                        typeMap.put(simpleName.substring(0, simpleName.length() - IMPL.length()), c);
                }
            }
            int i = 0;
            for (Class<?> clazz : typeMap.values()) {
                ObjectSet<?> set = sourceContainer.query(clazz);
                for (Object object : set) {
                    if (object instanceof IIdentifiableObject) {
                        Query query = targetContainer.query();
                        query.constrain(clazz);
                        query.descend(Constants.OID).constrain(((IIdentifiableObject) object).getStringId());
                        if (query.<IIdentifiableObject>execute().hasNext())
                            continue;
                    }
                    targetContainer.store(object);
                    if (++i >= 500) {
                        targetContainer.commit();
                        i = 0;
                    }
                }
            }
            targetContainer.commit();
        } finally {
            sourceContainer.close();
            targetContainer.close();
        }
    }

    public boolean needsDefragmentation() {
        SystemInfo systemInfo = getSystemInfo();
        long totalSize = systemInfo.totalSize();
        return totalSize > 5000000 && systemInfo.freespaceSize() * 10 > totalSize
                || systemInfo.freespaceEntryCount() > 10000;
    }

    public void pruneEmptySystemCollections() {
        if (!readOnly && hasDirtyCollection()) {
            IRunnableWithProgress runnable = new IRunnableWithProgress() {
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    pruneEmptySystemCollections(monitor, false);
                }
            };
            try {
                IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
                if (workbenchWindow != null) {
                    ProgressMonitorDialog dialog = new ProgressMonitorDialog(workbenchWindow.getShell());
                    dialog.create();
                    dialog.getShell().setText(Constants.APPLICATION_NAME);
                    dialog.run(false, true, runnable);
                } else
                    runnable.run(new NullProgressMonitor());
            } catch (InvocationTargetException e) {
                // ignore
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#markSystemCollectionsForPurge(com.bdaum
     * .zoom.cat.model.asset.Asset)
     */

    public void markSystemCollectionsForPurge(Asset asset) {
        Meta meta = getMeta(true);
        boolean cleaned = meta.getCleaned();
        if (!cleaned || beginSafeTransactionWithReport()) {
            try {
                boolean notEmpty = hasDirtyCollection();
                boolean added = false;
                String uri = asset.getUri();
                StringBuilder sb = new StringBuilder();
                int p = uri.lastIndexOf('/', uri.length() - 2);
                while (p >= 0) {
                    uri = uri.substring(0, p + 1);
                    int from = uri.length() - 2;
                    p = uri.lastIndexOf('/', from);
                    if (p < 0)
                        break;
                    sb.setLength(0);
                    if (uri.lastIndexOf(':', from) > p) {
                        String volume = asset.getVolume();
                        if (volume != null && !volume.isEmpty())
                            added |= addDirtyCollection(sb.append(VOLUMEKEY).append(volume).toString());
                        break;
                    }
                    added |= addDirtyCollection(sb.append(URIKEY).append(uri).toString());
                }
                Date dateCreated = asset.getDateCreated();
                if (dateCreated == null)
                    dateCreated = asset.getDateTimeOriginal();
                if (dateCreated != null) {
                    GregorianCalendar cal = new GregorianCalendar();
                    cal.setTime(dateCreated);
                    StringBuilder ksb = new StringBuilder(DATETIMEKEY);
                    added |= addDirtyCollection(ksb.append(String.valueOf(cal.get(Calendar.YEAR))).toString());
                    int l = ksb.length();
                    added |= addDirtyCollection(
                            ksb.append('-').append(String.valueOf(cal.get(Calendar.MONTH))).toString())
                            | addDirtyCollection(
                                    ksb.append('-').append(String.valueOf(Calendar.DAY_OF_MONTH)).toString());
                    ksb.setLength(l);
                    added |= addDirtyCollection(
                            ksb.append("-W").append(String.valueOf(cal.get(Calendar.WEEK_OF_YEAR))).toString()) //$NON-NLS-1$
                            | addDirtyCollection(
                                    ksb.append('-').append(String.valueOf(Calendar.DAY_OF_WEEK)).toString());
                }
                for (LocationCreatedImpl locationCreated : obtainStructForAsset(LocationCreatedImpl.class,
                        asset.getStringId(), true))
                    added |= markSystemCollectionsForPurge(
                            obtainById(LocationImpl.class, locationCreated.getLocation()));
                Date importDate = asset.getImportDate();
                if (importDate != null)
                    added |= addDirtyCollection(IMPORTKEY + Constants.DFIMPORT.format(importDate));
                if (added && !notEmpty && cleaned) {
                    meta.setCleaned(false);
                    storeAndCommit(meta);
                }
            } catch (Exception e) {
                rollback();
                DbActivator.getDefault().logError(Messages.DbManager_internal_error_cleaning, e);
            } finally {
                if (cleaned)
                    endSafeTransaction();
            }
        }
    }

    public boolean markSystemCollectionsForPurge(Location loc) {
        if (loc != null) {
            StringBuilder ksb = new StringBuilder(LOCATIONKEY);
            return addDirtyCollection(ksb.append(loc.getWorldRegionCode()).toString())
                    | addDirtyCollection(ksb.append('|').append(loc.getCountryISOCode()).toString())
                    | addDirtyCollection(ksb.append('|').append(loc.getProvinceOrState()).toString())
                    | addDirtyCollection(ksb.append('|').append(loc.getCity()).toString());
        }
        return false;
    }

    public boolean pruneSystemCollection(final SmartCollection sm) {
        if (!readOnly && (sm.getCriterion() == null || sm.getCriterion().isEmpty()
                || sm.getCriterion().get(0) == null || createCollectionProcessor(sm, null, null).isEmpty())
                && beginSafeTransactionWithReport())
            try {
                SmartCollection parent = sm.getSmartCollection_subSelection_parent();
                if (parent != null) {
                    parent.removeSubSelection(sm);
                    store(parent);
                } else {
                    GroupImpl group = obtainById(GroupImpl.class, sm.getGroup_rootCollection_parent());
                    if (group != null) {
                        group.removeRootCollection(sm.getStringId());
                        store(group);
                    }
                }
                deleteCollection(sm);
                commit();
                return true;
            } catch (Exception e) {
                rollback();
                DbActivator.getDefault().logError(Messages.DbManager_internal_error_pruning, e);
            } finally {
                endSafeTransaction();
            }
        return false;
    }

    public void pruneEmptySystemCollections(IProgressMonitor monitor, boolean all) {
        synchronized (dirtyCollections) {
            // long time = System.currentTimeMillis();
            if (!readOnly) {
                if (all) {
                    List<SmartCollectionImpl> set = obtainObjects(SmartCollectionImpl.class);
                    for (SmartCollectionImpl sm : set)
                        if (sm.getSystem()) {
                            String id = sm.getStringId();
                            if (id.startsWith(VOLUMEKEY) || id.startsWith(URIKEY) || id.startsWith(DATETIMEKEY)
                                    || id.startsWith(LOCATIONKEY) || id.startsWith(IMPORTKEY))
                                dirtyCollections.add(id);
                        }
                }
                if (hasDirtyCollection()) {
                    Group importGroup = getImportGroup();
                    List<Group> subgroups = importGroup.getSubgroup();
                    if (!subgroups.isEmpty())
                        importGroup = subgroups.get(0);
                    List<String> importIds = new ArrayList<String>(importGroup.getRootCollection());
                    monitor.beginTask(Messages.DbManager_pruning_empty,
                            dirtyCollections.size() + 2 + importIds.size());
                    LinkedList<String> sorted = new LinkedList<String>(dirtyCollections);
                    Collections.sort(sorted, new Comparator<String>() {
                        public int compare(String o1, String o2) {
                            return o2.length() - o2.length();
                        }
                    });
                    monitor.worked(1);
                    if (db != null) {
                        int maxImports = factory.getMaxImports() - 1;
                        while (!sorted.isEmpty()) {
                            String id = sorted.poll();
                            monitor.worked(1);
                            if (id.startsWith(IDbManager.IMPORTKEY))
                                continue;
                            SmartCollectionImpl sm = obtainById(SmartCollectionImpl.class, id);
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                // ignore
                            }
                            if (sm != null && !pruneSystemCollection(sm))
                                for (Iterator<String> it = sorted.iterator(); it.hasNext();)
                                    if (id.startsWith(it.next())) {
                                        it.remove();
                                        monitor.worked(1);
                                    }
                        }
                        importIds.remove(Constants.LAST_IMPORT_ID);
                        Collections.sort(importIds, new Comparator<String>() {
                            @Override
                            public int compare(String o1, String o2) {
                                return o2.compareTo(o1);
                            }
                        });
                        List<Object> toBeDeleted = new ArrayList<>();
                        int i = 0;
                        for (String id : importIds) {
                            SmartCollectionImpl sm = obtainById(SmartCollectionImpl.class, id);
                            if (sm != null && sm.getSubSelection().isEmpty()) {
                                if (i < maxImports && !Utilities.isImportEmpty(this, sm))
                                    ++i;
                                else {
                                    importGroup.removeRootCollection(id);
                                    toBeDeleted.add(sm);
                                }
                            }
                            monitor.worked(1);
                        }
                        if (!toBeDeleted.isEmpty())
                            safeTransaction(toBeDeleted, importGroup);
                        monitor.worked(1);
                        Meta meta = getMeta(true);
                        meta.setCleaned(true);
                        safeTransaction(null, meta);
                        dirtyCollections.clear();
                    }
                    monitor.done();
                }
                // System.out.println(System.currentTimeMillis() - time);
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#createCollectionProcessor(com.bdaum.zoom
     * .cat.model.group.SmartCollection, com.bdaum.zoom.core.IAssetFilter,
     * com.bdaum.zoom.cat.model.group.SortCriterion)
     */

    public ICollectionProcessor createCollectionProcessor(SmartCollection sm, IAssetFilter[] filters,
            SortCriterion customSort) {
        return new CollectionProcessor(factory, this, sm, filters, customSort);
    }

    public boolean addDirtyCollection(String id) {
        return dirtyCollections.add(id);
    }

    private boolean hasDirtyCollection() {
        return !dirtyCollections.isEmpty();
    }

    private boolean removeCrashedLuceneIndex() {
        if (isLuceneLocked())
            return resetLuceneIndex();
        return false;
    }

    public boolean resetLuceneIndex() {
        if (indexPath == null || !indexPath.exists())
            return true;
        boolean success = Core.deleteFileOrFolder(indexPath);
        if (success && !readOnly) {
            Meta meta = obtainMeta();
            if (meta != null) {
                Set<String> postponed = meta.getPostponed();
                if (postponed != null && !postponed.isEmpty()) {
                    meta.clearPostponed();
                    store(postponed);
                    storeAndCommit(meta);
                }
            }
        }
        return success;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#performBackup(long, long)
     */
    public BackupJob performBackup(long delay, long interval) {
        return performBackup(delay, interval, false);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#performBackup(long, long, boolean)
     */
    public BackupJob performBackup(long delay, long interval, boolean block) {
        return performBackup(delay, interval, block, Integer.MAX_VALUE);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#performBackup(long, long, boolean,
     * int)
     */
    public BackupJob performBackup(long delay, long interval, boolean block, int generations) {
        Meta meta = getMeta(true);
        String backupLocation = meta.getBackupLocation();
        if (backupLocation != null && !backupLocation.isEmpty()) {
            if (checkAvailableSpace(backupLocation, getFile(), getIndexPath())) {
                Date date = meta.getLastBackup();
                Date lastSessionEnd = meta.getLastSessionEnd();
                long lastBackup = date.getTime();
                long lastSession = lastSessionEnd == null ? Long.MAX_VALUE : lastSessionEnd.getTime();
                long now = System.currentTimeMillis();
                if (interval < 0 || now - lastBackup > interval && lastSession > lastBackup) {
                    date.setTime(now);
                    meta.setLastBackupFolder(backupLocation);
                    storeAndCommit(meta);
                    BackupJob job = new BackupJob(backupLocation, getFile(), getIndexPath(), generations);
                    job.schedule(delay);
                    backupScheduled = true;
                    if (block)
                        try {
                            job.join();
                        } catch (InterruptedException e) {
                            // should never happen
                        }
                    return job;
                }
            }
        }
        return null;
    }

    private boolean checkAvailableSpace(String backupLocation, File catFile, File indexPath) {
        File backupFile = new File(backupLocation);
        long freeSpace = CoreActivator.getDefault().getFreeSpace(backupFile);
        if (freeSpace > 0L
                && catFile.length() + computeSpace(indexPath, 4096L) - computeSpace(backupFile, 0L) > freeSpace) {
            factory.getErrorHandler().showWarning(Constants.APPLICATION_NAME, Messages.DbManager_backup_impossible,
                    null);
            return false;
        }
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#store(java.lang.Object)
     */

    private long computeSpace(File file, long foldersize) {
        if (file == null)
            return 0L;
        if (file.isDirectory()) {
            long filesize = foldersize;
            File[] children = file.listFiles();
            if (children != null)
                for (File child : children)
                    filesize += computeSpace(child, foldersize);
            return filesize;
        }
        return file.length();
    }

    public void store(Object object) {
        if (!readOnly && !transactionFailed)
            try {
                db.store(object);
            } catch (Exception e) {
                transactionFailed = true;
            }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#delete(java.lang.Object)
     */

    public void delete(Object object) {
        if (!readOnly && !transactionFailed && object != null)
            try {
                if (object instanceof SmartCollection) {
                    deleteMember(((SmartCollection) object).getCriterion());
                    deleteMember(((SmartCollection) object).getSortCriterion());
                    deleteMember(((SmartCollection) object).getSubSelection());
                    resetSystemCollectionCache();
                    if (DIAGNOSE)
                        DbActivator.getDefault().logInfo("Deleted " + object); //$NON-NLS-1$
                } else if (object instanceof SlideShow)
                    deleteMember(((SlideShow) object).getEntry());
                else if (object instanceof Wall)
                    deleteMember(((Wall) object).getExhibit());
                else if (object instanceof Storyboard)
                    deleteMember(((Storyboard) object).getExhibit());
                db.delete(object);
            } catch (Exception e) {
                transactionFailed = true;
            }
    }

    private void resetSystemCollectionCache() {
        if (lastFolderUris != null)
            lastFolderUris.clear();
        if (previousLocationKeys != null)
            previousLocationKeys.clear();
        previousDay = -1L;
        previousTimeline = Meta_type.timeline_no;
    }

    private void deleteMember(Object object) {
        if (object != null)
            db.delete(object);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#storeTrash(java.lang.Object)
     */

    public void storeTrash(Object object) {
        if (!trashFailed && getTrashCan() != null) {
            try {
                trashCan.store(object);
            } catch (Exception e) {
                trashFailed = true;
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#deleteTrash(java.lang.Object)
     */

    public void deleteTrash(Object object) {
        if (trashCan != null && !trashFailed) {
            try {
                trashCan.delete(object);
            } catch (Exception e) {
                trashFailed = true;
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#deleteAllTrash(java.util.Collection)
     */

    public void deleteAllTrash(Collection<? extends Object> t) {
        if (trashCan != null) {
            try {
                for (Object o : t)
                    trashCan.delete(o);
                trashCan.commit();
            } catch (Exception e) {
                trashCan.rollback();
                trashFailed = true;
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#commit()
     */

    public void commit() {
        if (readOnly)
            return;
        if (transactionFailed)
            rollbackError();
        else
            try {
                db.commit();
            } catch (Exception e) {
                rollbackError();
            }
    }

    private void rollbackError() {
        rollbackTrash();
        rollback();
        connectionLostWarning();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#rollback()
     */

    public void rollback() {
        if (readOnly)
            return;
        try {
            db.rollback();
            transactionFailed = false;
        } catch (Exception e) {
            // do nothing
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#commitTrash()
     */

    public void commitTrash() {
        if (trashCan != null)
            if (trashFailed)
                rollbackError();
            else
                try {
                    trashCan.commit();
                } catch (Exception e) {
                    rollbackError();
                }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#rollbackTrash()
     */

    public void rollbackTrash() {
        if (trashCan != null)
            try {
                trashCan.rollback();
                trashFailed = false;
            } catch (Exception e) {
                // do nothing
            }
    }

    private void connectionLostWarning() {
        if (!emergency)
            factory.getErrorHandler().connectionLostWarning(Messages.DbManager_lost_connection,
                    Messages.DbManager_operation_was_aborted, this);
    }

    public void backup(String filename) {
        if (db != null)
            db.ext().backup(filename);
    }

    protected SystemInfo getSystemInfo() {
        return ((ExtObjectContainer) db).systemInfo();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#isReadOnly()
     */

    public boolean isReadOnly() {
        return readOnly;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#setReadOnly(boolean)
     */

    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly |= !file.canWrite();
    }

    public void resetErrorCount() {
        dbErrorCounter = 0;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#beginSafeTransaction()
     */

    public synchronized boolean beginSafeTransaction() {
        if (db != null)
            for (int i = 0; i < TIMEOUT; i += 90)
                try {
                    if (db.ext().setSemaphore(SAFE_TRANSACTION, TIMEOUT))
                        return true;
                    Thread.sleep(100);
                } catch (DatabaseClosedException e1) {
                    connectionLostWarning();
                    return false;
                } catch (InterruptedException e) {
                    // ignore
                }
        return false;
    }

    private boolean beginSafeTransactionWithReport() {
        if (beginSafeTransaction())
            return true;
        DbActivator.getDefault().logError(Messages.DbManager_time_out, null);
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#endSafeTransaction()
     */

    public void endSafeTransaction() {
        if (db != null)
            try {
                db.ext().releaseSemaphore(SAFE_TRANSACTION);
            } catch (Exception e) {
                // ignore
            }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#safeTransaction(java.lang.Runnable)
     */

    public boolean safeTransaction(Runnable runnable) {
        if (beginSafeTransactionWithReport()) {
            try {
                runnable.run();
                commit();
                return true;
            } catch (RuntimeException e) {
                rollback();
                throw e;
            } finally {
                endSafeTransaction();
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#safeTransaction(java.lang.Object,
     * java.lang.Object)
     */
    public boolean safeTransaction(Object toBeDeleted, Object toBeStored) {
        if (beginSafeTransactionWithReport()) {
            try {
                if (toBeDeleted != null) {
                    if (toBeDeleted instanceof Collection<?>)
                        for (Object o : (Collection<?>) toBeDeleted) {
                            if (o != null)
                                delete(o);
                        }
                    else
                        delete(toBeDeleted);
                }
                if (toBeStored != null) {
                    if (toBeStored instanceof Collection<?>)
                        for (Object o : (Collection<?>) toBeStored) {
                            if (o != null)
                                store(o);
                        }
                    else
                        store(toBeStored);
                }
                commit();
                return true;
            } catch (RuntimeException e) {
                rollback();
                throw e;
            } finally {
                endSafeTransaction();
            }
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#isBackupScheduled()
     */

    public boolean isBackupScheduled() {
        return backupScheduled;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#createCollectionProcessor(com.bdaum.zoom
     * .cat.model.group.SmartCollection)
     */

    public ICollectionProcessor createCollectionProcessor(SmartCollection sm) {
        return createCollectionProcessor(sm, null, null);
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.IDbManager#getStatistics()
     */

    public Map<String, Long> getStatistics() {
        Map<String, Long> results = new HashMap<String, Long>(5);
        SystemInfo systemInfo = getSystemInfo();
        results.put(IDbManager.TOTALSIZE, systemInfo.totalSize());
        results.put(IDbManager.FREESPACE, systemInfo.freespaceSize());
        results.put(IDbManager.FREESPACEENTRIES, (long) systemInfo.freespaceEntryCount());
        return results;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#isEmbedded()
     */
    public boolean isEmbedded() {
        return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Object getAdapter(Class adapter) {
        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.bdaum.zoom.core.db.IDbManager#repairCatalog()
     */
    public void repairCatalog() {
        Set<Object> toBeStored = new HashSet<Object>();
        List<Object> toBeDeleted = new ArrayList<Object>();
        List<SmartCollectionImpl> collections = obtainObjects(SmartCollectionImpl.class);
        for (SmartCollectionImpl sm : collections) {
            AomList<Criterion> crits = sm.getCriterion();
            if (crits == null)
                removeCollection(toBeStored, toBeDeleted, sm);
            else {
                for (Iterator<Criterion> it = crits.iterator(); it.hasNext();)
                    if (it.next() == null)
                        it.remove();
                if (crits.isEmpty())
                    removeCollection(toBeStored, toBeDeleted, sm);
            }
        }
        safeTransaction(toBeDeleted, toBeStored);
    }

    private void removeCollection(Set<Object> toBeStored, List<Object> toBeDeleted, SmartCollectionImpl sm) {
        SmartCollection parent = sm.getSmartCollection_subSelection_parent();
        if (parent != null) {
            parent.removeSubSelection(sm);
            toBeStored.add(parent);
        }
        String groupId = sm.getGroup_rootCollection_parent();
        if (groupId != null) {
            GroupImpl group = obtainById(GroupImpl.class, groupId);
            if (group != null) {
                group.removeRootCollection(sm.getStringId());
                toBeStored.add(group);
            }
        }
        toBeDeleted.add(sm);
    }

    protected boolean isIndexed(String field) {
        return indexedFields.contains(field);
    }

}