/** * Sencha GXT 3.0.0b - Sencha for GWT * Copyright(c) 2007-2012, Sencha, Inc. * licensing@sencha.com * * http://www.sencha.com/products/gxt/license/ */ package com.sencha.gxt.desktopapp.client.persistence; import java.util.ArrayList; import java.util.Date; import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.web.bindery.autobean.shared.AutoBean; import com.google.web.bindery.autobean.shared.AutoBeanCodex; import com.google.web.bindery.autobean.shared.AutoBeanFactory; import com.google.web.bindery.autobean.shared.AutoBeanFactory.Category; import com.google.web.bindery.autobean.shared.AutoBeanUtils; import com.google.web.bindery.autobean.shared.Splittable; import com.sencha.gxt.data.shared.TreeStore; import com.sencha.gxt.data.shared.event.StoreAddEvent; import com.sencha.gxt.data.shared.event.StoreClearEvent; import com.sencha.gxt.data.shared.event.StoreDataChangeEvent; import com.sencha.gxt.data.shared.event.StoreFilterEvent; import com.sencha.gxt.data.shared.event.StoreHandlers; import com.sencha.gxt.data.shared.event.StoreRecordChangeEvent; import com.sencha.gxt.data.shared.event.StoreRemoveEvent; import com.sencha.gxt.data.shared.event.StoreSortEvent; import com.sencha.gxt.data.shared.event.StoreUpdateEvent; import com.sencha.gxt.data.shared.event.TreeStoreRemoveEvent; import com.sencha.gxt.desktopapp.client.persistence.FileModel.FileType; import com.sencha.gxt.desktopapp.client.utility.Utility; public class FileSystem { public interface StorageItem { List<String> getChildIds(); Splittable getData(); String getId(); void setChildIds(List<String> children); void setData(Splittable data); void setId(String id); } @Category(AutoBeanToString.class) public interface StorageItemFactory extends AutoBeanFactory { AutoBean<StorageItem> storageItem(); } public class TreeStoreHandlers implements StoreHandlers<FileModel> { @Override public void onAdd(StoreAddEvent<FileModel> event) { List<FileModel> files = event.getItems(); FileModel parent = treeStore.getParent(files.get(0)); if (parent == null) { persistRoot(); } else { parent.setLastModified(new Date()); update(parent); } persist(files); } @Override public void onClear(StoreClearEvent<FileModel> event) { FileSystem.this.backingStore.clear(); } @Override public void onDataChange(StoreDataChangeEvent<FileModel> event) { // no-op, we won't be replacing whole sets of items } @Override public void onFilter(StoreFilterEvent<FileModel> event) { // no-op, we're going to ignore this } @Override public void onRecordChange(StoreRecordChangeEvent<FileModel> event) { // no-op, can't happen since we are autocommit=true } @Override public void onRemove(StoreRemoveEvent<FileModel> event) { TreeStoreRemoveEvent<FileModel> treeStoreRemoveEvent = (TreeStoreRemoveEvent<FileModel>) event; FileModel parent = treeStoreRemoveEvent.getParent(); if (parent == null) { persistRoot(); } else { parent.setLastModified(new Date()); update(parent); } // get the old child and clean it and its children from storage remove(getStorageItem(event.getItem().getId())); } @Override public void onSort(StoreSortEvent<FileModel> event) { // no-op, we're going to ignore this } @Override public void onUpdate(StoreUpdateEvent<FileModel> event) { for (FileModel item : event.getItems()) { update(item); } } } private static final String ROOT_ID = "0"; private static final String ID_KEY = "id"; private static final String PT_PREFIX = "p"; private BackingStore backingStore; private TreeStore<FileModel> treeStore; private FileModelProperties fileModelProperties; private FileModelFactory dataFactory; private StorageItemFactory storageFactory; public FileSystem(BackingStore backingStore) { this.backingStore = backingStore; } public FileModel createFileModel(FileModel parentFileModel, String name, FileType fileType) { FileModel childFileModel = getDataFactory().fileModel().as(); childFileModel.setName(name); childFileModel.setFileType(fileType); childFileModel.setLastModified(new Date()); childFileModel.setId(allocateId(PT_PREFIX)); childFileModel.setSize(0l); if (parentFileModel == null) { getTreeStore().add(childFileModel); } else { getTreeStore().add(parentFileModel, childFileModel); } return childFileModel; } public FileModelProperties getFileModelProperties() { if (fileModelProperties == null) { fileModelProperties = GWT.create(FileModelProperties.class); } return fileModelProperties; } public String getNextUntitledFileName(FileModel parentFileModel, FileType fileType) { String nextUntitledFileName; List<FileModel> children; if (parentFileModel == null) { children = getTreeStore().getRootItems(); } else { children = getTreeStore().getChildren(parentFileModel); } int index = 1; if (fileType == FileType.BOOKMARK) { nextUntitledFileName = "http://www.sencha.com"; } else { String fileNameTemplate = Utility.capitalize(fileType.toString()) + " "; do { nextUntitledFileName = fileNameTemplate + index; index++; } while (containsName(children, nextUntitledFileName)); } return nextUntitledFileName; } /** * Returns the parent of the specified file model or null if the parent is * root or the file model does not exist. This method is not necessary if * TreeStore.getParent is modified so that it does not assert the fileModel * exists. * * @param fileModel the file model to return the parent of * @return the parent of the file model or null if the parent is root or the * file model does not exist */ public FileModel getParent(FileModel fileModel) { return getTreeStore().findModel(fileModel) == null ? null : getTreeStore().getParent(fileModel); } public String getPath(FileModel fileModel) { StringBuilder s = new StringBuilder(); while (fileModel != null) { String name = fileModel.getName(); s.insert(0, "/" + name); fileModel = getParent(fileModel); } return s.toString(); } public StorageItem getStorageItem(FileModel model) { String id = model.getId(); StorageItem item = createItem(id); item.setData(AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(model))); for (FileModel child : getTreeStore().getChildren(model)) { item.getChildIds().add(child.getId()); } return item; } public TreeStore<FileModel> getTreeStore() { if (treeStore == null) { treeStore = new TreeStore<FileModel>(getFileModelProperties().key()); treeStore.setAutoCommit(true); addChildrenToStore(getRootStorageItem(), null, treeStore); treeStore.addStoreHandlers(new TreeStoreHandlers()); } return treeStore; } /** * Removes the specified file model if it exists. This method is not necessary * if TreeStore.remove is modified so that it does not assert the fileModel * exists. * * @param fileModel the file model to remove * @return true if the file model existed and was removed */ public boolean remove(FileModel fileModel) { return getTreeStore().findModel(fileModel) == null ? false : getTreeStore().remove(fileModel); } protected String allocateId(String prefix) { String thisId = backingStore.getItem(ID_KEY); if (thisId == null) { thisId = ROOT_ID; } String nextId = Integer.toString(Integer.parseInt(thisId) + 1); backingStore.setItem(ID_KEY, nextId); return prefix + thisId; } protected boolean containsName(List<FileModel> children, String name) { for (FileModel fileModel : children) { if (name.equals(fileModel.getName())) { return true; } } return false; } private void addChildrenToStore(StorageItem item, FileModel parent, TreeStore<FileModel> treeStore) { for (String childId : item.getChildIds()) { StorageItem child = getStorageItem(childId); FileModel model = AutoBeanCodex.decode(getDataFactory(), FileModel.class, child.getData()).as(); if (parent != null) { treeStore.add(parent, model); } else { treeStore.add(model); } addChildrenToStore(child, model, treeStore); } } private StorageItem createItem(String id) { StorageItem item = getStorageFactory().storageItem().as(); item.setId(id); item.setChildIds(new ArrayList<String>()); return item; } private FileModelFactory getDataFactory() { if (dataFactory == null) { dataFactory = GWT.create(FileModelFactory.class); } return dataFactory; } private StorageItem getRootStorageItem() { StorageItem root = getStorageItem(PT_PREFIX + ROOT_ID); if (root == null) { root = createItem(allocateId(PT_PREFIX)); persist(root); } return root; } private StorageItemFactory getStorageFactory() { if (storageFactory == null) { storageFactory = GWT.create(StorageItemFactory.class); } return storageFactory; } private StorageItem getStorageItem(String string) { String payload = backingStore.getItem(string); if (payload == null) { return null; } return AutoBeanCodex.decode(getStorageFactory(), StorageItem.class, payload).as(); } private void persist(FileModel model) { persist(getStorageItem(model)); } private void persist(List<FileModel> files) { for (FileModel file : files) { persist(file); persist(getTreeStore().getChildren(file)); } } private void persist(StorageItem item) { assert item.getData() != null || item.getId().equals(PT_PREFIX + ROOT_ID) : "No data in non-root item"; String payload = AutoBeanCodex.encode(AutoBeanUtils.getAutoBean(item)).getPayload(); backingStore.setItem(item.getId(), payload); } private void persistRoot() { StorageItem item = getRootStorageItem(); item.getChildIds().clear(); for (FileModel child : getTreeStore().getRootItems()) { item.getChildIds().add(child.getId()); } persist(item); } private void remove(StorageItem item) { backingStore.removeItem(item.getId()); for (String child : item.getChildIds()) { remove(getStorageItem(child)); } } private void update(final FileModel fileModel) { final long length; if (fileModel.getFileType() == FileType.FOLDER) { length = (long) getTreeStore().getChildCount(fileModel); } else { String content = fileModel.getContent(); length = (long) (content == null ? 0 : content.length()); } if (length != fileModel.getSize()) { fileModel.setSize(length); // prevents recursive update via onUpdate handler Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { getTreeStore().update(fileModel); } }); } persist(fileModel); } }