package org.geogebra.web.touch; import java.util.ArrayList; import org.geogebra.common.main.App; import org.geogebra.common.main.MaterialsManager; import org.geogebra.common.move.ggtapi.models.JSONParserGGT; import org.geogebra.common.move.ggtapi.models.Material; import org.geogebra.common.move.ggtapi.models.Material.MaterialType; import org.geogebra.common.move.ggtapi.models.MaterialFilter; import org.geogebra.common.move.ggtapi.models.SyncEvent; import org.geogebra.common.plugin.Event; import org.geogebra.common.plugin.EventType; import org.geogebra.common.util.debug.Log; import org.geogebra.web.html5.main.AppW; import org.geogebra.web.html5.main.StringHandler; import org.geogebra.web.web.gui.dialog.DialogManagerW; import org.geogebra.web.web.main.FileManager; import org.geogebra.web.web.util.SaveCallback; import com.google.gwt.core.client.Callback; import com.google.gwt.storage.client.Storage; import com.googlecode.gwtphonegap.client.PhoneGap; import com.googlecode.gwtphonegap.client.file.DirectoryEntry; import com.googlecode.gwtphonegap.client.file.DirectoryReader; import com.googlecode.gwtphonegap.client.file.EntryBase; import com.googlecode.gwtphonegap.client.file.File; import com.googlecode.gwtphonegap.client.file.FileCallback; import com.googlecode.gwtphonegap.client.file.FileEntry; import com.googlecode.gwtphonegap.client.file.FileError; import com.googlecode.gwtphonegap.client.file.FileObject; import com.googlecode.gwtphonegap.client.file.FileReader; import com.googlecode.gwtphonegap.client.file.FileSystem; import com.googlecode.gwtphonegap.client.file.FileWriter; import com.googlecode.gwtphonegap.client.file.Flags; import com.googlecode.gwtphonegap.client.file.ReaderCallback; import com.googlecode.gwtphonegap.collection.shared.LightArray; //import geogebra.web.gui.dialog.DialogManagerW; public class FileManagerT extends FileManager { private static final String META_PREFIX = "meta_"; private static final String GGB_DIR = "GeoGebra"; private static final String META_DIR = "meta"; private static final String FILE_EXT = ".ggb"; // to convert files from older ggbVersions private static final String OLD_FILE_PREFIX = "file#"; private static final String OLD_THUMB_PREFIX = "img#"; private static final String OLD_META_PREFIX = "meta#"; Storage stockStore = Storage.getLocalStorageIfSupported(); // PhoneGap phonegap; Flags createIfNotExist = new Flags(true, false); Flags dontCreateIfNotExist = new Flags(false, false); public FileManagerT(final AppW app) { super(app); init(); } protected void init(){ this.phonegap = PhoneGapManager.getPhoneGap(); convertToNewFileFormat(); } void getGgbDir(final Callback<DirectoryEntry, FileError> callback) { this.phonegap.getFile().requestFileSystem( File.LocalFileSystem_PERSISTENT, 0, new FileCallback<FileSystem, FileError>() { @Override public void onSuccess(final FileSystem entry) { final DirectoryEntry directoryEntry = entry.getRoot(); directoryEntry.getDirectory(GGB_DIR, createIfNotExist, new FileCallback<DirectoryEntry, FileError>() { @Override public void onSuccess( final DirectoryEntry ggbDir) { callback.onSuccess(ggbDir); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } private void getMetaDir(final Callback<DirectoryEntry, FileError> callback) { getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry ggbDir) { ggbDir.getDirectory(META_DIR, createIfNotExist, new FileCallback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry metaDir) { callback.onSuccess(metaDir); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } /** * Gets access to the ggb-File * * @param key * of file * @param flags * (true, false) to create file, if it doesn't exist. (false, * false) don't create file if it doesn't exist * @param callback * Callback */ void getGgbFile(final String key, final Flags flags, final Callback<FileEntry, FileError> callback) { getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry ggbDir) { ggbDir.getFile(key, flags, new FileCallback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry entry) { callback.onSuccess(entry); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } /** * Gets access to the metaFile * * @param key * of file * @param flags * (true, false) to create file, if it doesn't exist. (false, * false) don't create file if it doesn't exist * @param callback * Callback */ void getMetaFile(final String key, final Flags flags, final Callback<FileEntry, FileError> callback) { getMetaDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry metaDir) { metaDir.getFile(key, flags, new FileCallback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry metaFile) { callback.onSuccess(metaFile); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } @Override public void onFailure(final FileError error) { callback.onFailure(error); } }); } /** * Deletes the ggbFile and metaFile from the device. Updates the BrowseView. * * @param mat * {@link Material} */ @Override public void delete(final Material mat, boolean permanent, final Runnable onSuccess) { if (!permanent) { mat.setDeleted(true); this.createMetaData(getFileKey(mat), mat, null); return; } final String key = getFileKey(mat); getGgbFile(key + FILE_EXT, dontCreateIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry ggbFile) { ggbFile.remove(new FileCallback<Boolean, FileError>() { @Override public void onSuccess(final Boolean entryDeleted) { removeFile(mat); onSuccess.run(); deleteMetaData(key); Log.debug("DELETE success: " + key + FILE_EXT); } @Override public void onFailure(final FileError error) { Log.debug("DELETE Could not remove file: " + key + FILE_EXT); } }); } @Override public void onFailure(final FileError reason) { Log.debug("DELETE Could not get file: " + key + FILE_EXT); } }); } /** * deletes the metaFile with given filename. * * @param key * String */ void deleteMetaData(final String key) { getMetaFile(META_PREFIX + key, dontCreateIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry metaFile) { metaFile.remove(new FileCallback<Boolean, FileError>() { @Override public void onSuccess(final Boolean entryDeleted) { // } @Override public void onFailure(final FileError error) { Log.debug("Could not delete metafile"); } }); } @Override public void onFailure(final FileError reason) { Log.debug("Could not get metafile"); } }); } /** * loads every file of the device depending on the {@link MaterialFilter * filter} into the BrowseView. * * @param filter * {@link MaterialFilter} */ @Override protected void getFiles(final MaterialFilter filter) { getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry ggbDir) { final DirectoryReader directoryReader = ggbDir.createReader(); directoryReader .readEntries(new FileCallback<LightArray<EntryBase>, FileError>() { @Override public void onSuccess( final LightArray<EntryBase> entries) { for (int i = 0; i < entries.length(); i++) { final EntryBase entryBase = entries.get(i); if (entryBase.isFile()) { final FileEntry fileEntry = entryBase .getAsFileEntry(); // get key without ending (.ggb) final String key = fileEntry.getName() .substring( 0, fileEntry.getName() .indexOf(".")); readMetaData( key, dontCreateIfNotExist, new Callback<String, FileError>() { @Override public void onSuccess( String result) { Material mat = JSONParserGGT .parseMaterial(result); if (mat == null) { mat = new Material( 0, MaterialType.ggb); mat.setTitle(getTitleFromKey(key)); } final Material mat1 = mat; mat1.setLocalID( MaterialsManager .getIDFromKey(key)); if (filter.check(mat1)) { addMaterial(mat1); } } @Override public void onFailure( FileError reason) { Log.debug( "Could not read meta data "); } }); } } } @Override public void onFailure(final FileError error) { Log.debug( "Could not read the file entries"); } }); } @Override public void onFailure(final FileError reason) { Log.debug("Could not read GGBDir"); } }); } @Override public void rename(final String newTitle, final Material mat){ rename(newTitle, mat, null); } @Override public void rename(final String newTitle, final Material mat, final Runnable callback) { Log.debug("RENAME" + mat.getTitle() + "->" + newTitle); final String newKey = MaterialsManager.createKeyString(mat.getLocalID(), newTitle); final String oldKey = getFileKey(mat); Log.debug("RENAME local fn" + oldKey + "," + mat.getModified()); getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry ggbDir) { ggbDir.getFile(oldKey + FILE_EXT, dontCreateIfNotExist, new FileCallback<FileEntry, FileError>() { @Override public void onSuccess(FileEntry ggbFile) { Log.debug("RENAME accessed fn" + oldKey); ggbFile.moveTo( ggbDir, newKey + FILE_EXT, new FileCallback<FileEntry, FileError>() { @Override public void onSuccess( FileEntry entry) { mat.setTitle(newTitle); renameMetaData(oldKey, newKey, mat, callback); } @Override public void onFailure( FileError error) { Log.debug("RENAME cannotMove fn" + oldKey + "/" + newKey); } }); } @Override public void onFailure(FileError error) { Log.debug("RENAME not found fn" + oldKey); } }); } @Override public void onFailure(FileError reason) { // TODO Auto-generated method stub } }); } /** * Deletes the old metaData and creates a new one * * @param oldKey * String * @param newKey * String * @param mat * {@link Material} * @param timestamp * tube timestamp of the change */ void renameMetaData(final String oldKey, final String newKey, final Material mat, final Runnable callback) { getMetaFile(META_PREFIX + newKey, createIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry metaFile) { metaFile.createWriter(new FileCallback<FileWriter, FileError>() { @Override public void onSuccess(final FileWriter writer) { mat.setBase64(""); deleteMetaData(oldKey); writer.write(mat.toJson().toString()); if (callback != null) { callback.run(); } } @Override public void onFailure(final FileError error) { // TODO } }); } @Override public void onFailure(final FileError error) { // TODO } }); } /** * @param oldKey * String * @param flag * Flags * @param cb * Callback */ void readMetaData(String oldKey, Flags flag, final Callback<String, FileError> cb) { getMetaFile(META_PREFIX + oldKey, dontCreateIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onFailure(FileError reason) { cb.onFailure(reason); } @Override public void onSuccess(FileEntry metaFile) { metaFile.getFile(new FileCallback<FileObject, FileError>() { @Override public void onSuccess(FileObject entry) { final FileReader reader = PhoneGapManager .getPhoneGap().getFile().createReader(); reader.setOnloadCallback(new ReaderCallback<FileReader>() { @Override public void onCallback(FileReader result) { cb.onSuccess(result.getResult()); } }); reader.setOnErrorCallback(new ReaderCallback<FileReader>() { @Override public void onCallback(FileReader result) { cb.onFailure(result.getError()); } }); reader.readAsText(entry); } @Override public void onFailure(FileError error) { cb.onFailure(error); } }); } }); } /** * Saves the active file (with metaData) on the device into directory * "GeoGebra". * * @param cb * {@link SaveCallback} * @param base64 String */ @Override public void saveFile(final String base64, final long modified, final SaveCallback cb) { if (getApp().getLocalID() >= 0) { doSaveFile(getApp().getLocalID(), base64, modified, cb); return; } createID(new Callback<Integer, String>() { @Override public void onFailure(String reason) { Log.error("SAVE FAILED " + reason); } @Override public void onSuccess(Integer id) { doSaveFile(id, base64, modified, cb); } }); } void doSaveFile(final int localID, final String base64, final long modified, final SaveCallback cb) { if (getApp().getLocalID() == -1) { getApp().setLocalID(localID); } final String key = MaterialsManager.createKeyString(localID, getApp() .getKernel().getConstruction().getTitle()); getGgbFile(key + FILE_EXT, createIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry ggbFile) { ggbFile.createWriter(new FileCallback<FileWriter, FileError>() { @Override public void onSuccess(final FileWriter writer) { writer.write(base64); Material mat = createMaterial("", modified); // ensure material has the correct local id mat.setLocalID(app.getLocalID()); createMetaData(key, mat , cb); } @Override public void onFailure(final FileError error) { cb.onError(); } }); } @Override public void onFailure(final FileError error) { cb.onError(); } }); } int nextFreeID = 1; /** * @param cb * Callback */ void createID(final Callback<Integer, String> cb) { getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(DirectoryEntry ggbDir) { final DirectoryReader directoryReader = ggbDir.createReader(); directoryReader .readEntries(new FileCallback<LightArray<EntryBase>, FileError>() { @Override public void onSuccess( final LightArray<EntryBase> entries) { for (int i = 0; i < entries.length(); i++) { if (entries.get(i).isFile()) { final FileEntry fileEntry = entries .get(i).getAsFileEntry(); final String key = fileEntry.getName() .substring( 0, fileEntry.getName() .indexOf(".")); if (key.startsWith( MaterialsManager.FILE_PREFIX)) { int fileID = MaterialsManager .getIDFromKey(key); if (fileID >= nextFreeID) { nextFreeID = fileID + 1; } } } } cb.onSuccess(nextFreeID); nextFreeID++; } @Override public void onFailure(final FileError error) { // } }); } @Override public void onFailure(FileError reason) { // TODO Auto-generated method stub } }); } /** * create metaData. * * @param key * String * @param mat * metadata * @param cb * {@link SaveCallback} */ void createMetaData(final String key, final Material mat, final SaveCallback cb) { if (mat.isDeleted()) { Log.debug("DELETE flag" + mat.getId()); } getMetaFile(META_PREFIX + key, createIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry metaFile) { metaFile.createWriter(new FileCallback<FileWriter, FileError>() { @Override public void onSuccess(final FileWriter writer) { // mat.setTitle(FileManager.getTitleFromKey(key)); mat.setBase64(""); writer.write(mat.toJson().toString()); if (cb != null) { cb.onSaved(mat, true); } } @Override public void onFailure(final FileError error) { if (cb != null) { cb.onError(); } } }); } @Override public void onFailure(final FileError error) { if (cb != null) { cb.onError(); } } }); } @Override public void uploadUsersMaterials(final ArrayList<SyncEvent> events) { getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onSuccess(final DirectoryEntry ggbDir) { final DirectoryReader directoryReader = ggbDir.createReader(); directoryReader .readEntries(new FileCallback<LightArray<EntryBase>, FileError>() { @Override public void onSuccess( final LightArray<EntryBase> entries) { setNotSyncedFileCount(entries.length(), events); for (int i = 0; i < entries.length(); i++) { final EntryBase entryBase = entries.get(i); if (entryBase.isFile()) { final FileEntry fileEntry = entryBase .getAsFileEntry(); // get name without ending (.ggb) final String key = fileEntry.getName() .substring( 0, fileEntry.getName() .indexOf(".")); readMetaData( key, dontCreateIfNotExist, new Callback<String, FileError>() { @Override public void onSuccess( String result) { final Material mat = JSONParserGGT .parseMaterial(result); mat.setLocalID( MaterialsManager .getIDFromKey(key)); sync(mat, events); } @Override public void onFailure( FileError reason) { ignoreNotSyncedFile(events); Log.debug( "Could not read meta data"); } }); } else { ignoreNotSyncedFile(events); } } } @Override public void onFailure(final FileError error) { // } }); } @Override public void onFailure(final FileError reason) { // TODO Auto-generated method stub } }); } @Override public void autoSave(int counter) { // TODO Auto-generated method stub } @Override public String getAutosaveJSON() { // TODO Auto-generated method stub return null; } @Override public void restoreAutoSavedFile(String json) { // TODO Auto-generated method stub } @Override public void deleteAutoSavedFile() { // TODO Auto-generated method stub } @Override public void saveLoggedOut(App app) { ((DialogManagerW) app.getDialogManager()).showSaveDialog(); } @Override public void setTubeID(String localID, Material mat) { this.createMetaData(localID, mat, null); } @Override public void openMaterial(final Material material) { this.getBase64(getFileKey(material), new Callback<String, FileError>() { @Override public void onFailure(FileError reason) { // TODO Auto-generated method stub } @Override public void onSuccess(String result) { material.setBase64(result); doOpenMaterial(material); } }); } private void getBase64(final String fileKey, final Callback<String, FileError> cb) { getGgbDir(new Callback<DirectoryEntry, FileError>() { @Override public void onFailure(FileError reason) { Log.error(fileKey + " not found"); } @Override public void onSuccess(DirectoryEntry result) { result.getFile(fileKey + FileManagerT.FILE_EXT, dontCreateIfNotExist, new FileCallback<FileEntry, FileError>() { @Override public void onSuccess(FileEntry entry) { readFile(entry, cb); } @Override public void onFailure(FileError error) { Log.error(fileKey + " not readable"); } }); } }); } /** * @param entry {@link FileEntry} * @param cb {@link Callback} to get content of file as String */ protected void readFile(FileEntry entry, final Callback<String, FileError> cb) { entry.getFile(new FileCallback<FileObject, FileError>() { @Override public void onSuccess(FileObject fileObject) { final FileReader reader = PhoneGapManager.getPhoneGap() .getFile().createReader(); reader.setOnloadCallback(new ReaderCallback<FileReader>() { @Override public void onCallback(FileReader result) { cb.onSuccess(result.getResult()); } }); reader.setOnErrorCallback(new ReaderCallback<FileReader>() { @Override public void onCallback(FileReader result) { cb.onFailure(result.getError()); } }); reader.readAsText(fileObject); } @Override public void onFailure(FileError error) { // TODO Auto-generated method stub } }); } /** * @param m {@link Material} */ protected void doOpenMaterial(Material m) { super.openMaterial(m); } /** * @param m {@link Material} */ protected void doUpload(Material m) { super.upload(m); } @Override protected void updateFile(final String key, final long modified, final Material material) { if (key == null) { this.createID(new Callback<Integer, String>() { @Override public void onFailure(String reason) { // TODO Auto-generated method stub } @Override public void onSuccess(Integer id) { String key2 = MaterialsManager.createKeyString(id, material.getTitle()); updateFile(key2, modified, material); } }); return; } getGgbFile(key + FILE_EXT, createIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry ggbFile) { ggbFile.createWriter(new FileCallback<FileWriter, FileError>() { @Override public void onSuccess(final FileWriter writer) { writer.write(material.getBase64()); material.setModified(modified); material.setLocalID(MaterialsManager .getIDFromKey(key)); String newKey = MaterialsManager .createKeyString( material.getLocalID(), material.getTitle()); if (key.equals(newKey)) { createMetaData(key, material, null); }else{ String newTitle = material.getTitle(); Log.debug("incoming rename " + newTitle); material.setTitle(MaterialsManager .getTitleFromKey(key)); material.setSyncStamp(material .getModified()); FileManagerT.this.rename(newTitle, material); } } @Override public void onFailure(final FileError error) { Log.error("Cannot write to file" + key + ", error " + error.getErrorCode()); } }); } @Override public void onFailure(final FileError error) { Log.error("Cannot create file" + key + ", error " + error.getErrorCode()); } }); } @Override public void upload(final Material mat) { getBase64(getFileKey(mat), new Callback<String, FileError>() { @Override public void onSuccess(String fileID) { mat.setBase64(fileID); doUpload(mat); } @Override public void onFailure(FileError fe) { // TODO Auto-generated method stub } }); } /** * convert old files (saved to localStorage) to a locale file of * the device and delete them from localStorage. */ private void convertToNewFileFormat() { if (this.stockStore == null || this.stockStore.getLength() <= 0) { return; } for (int i = 0; i < this.stockStore.getLength(); i++) { final String key = this.stockStore.key(i); if (!key.startsWith(OLD_FILE_PREFIX)) { break; } final String keyStem = key.substring(OLD_FILE_PREFIX.length()); final String base64 = this.stockStore.getItem(key); createID(new Callback<Integer, String>() { @Override public void onSuccess(Integer id) { final String keyString = MaterialsManager .createKeyString(id, keyStem); getGgbFile(keyString + FILE_EXT, createIfNotExist, new Callback<FileEntry, FileError>() { @Override public void onSuccess(final FileEntry ggbFile) { ggbFile.createWriter(new FileCallback<FileWriter, FileError>() { @Override public void onSuccess(final FileWriter writer) { writer.write(base64); createMetaData( keyString, createMaterial("", System.currentTimeMillis() / 1000), null); stockStore.removeItem(OLD_FILE_PREFIX + keyStem); stockStore.removeItem(OLD_META_PREFIX + keyStem); stockStore.removeItem(OLD_THUMB_PREFIX + keyStem); } @Override public void onFailure(final FileError error) { // } }); } @Override public void onFailure(final FileError error) { // } }); } @Override public void onFailure(String reason) { // TODO Auto-generated method stub } }); } } @Override public void export(final App app) { ((AppW) app).getGgbApi().getBase64(true, new StringHandler() { @Override public void handle(String s) { nativeShare(s, app.getExportTitle()); } }); } @Override public native void exportImage(String url, String title, String extension) /*-{ if ($wnd.android) { $wnd.android.share(url, title, extension); } }-*/; @Override public void showExportAsPictureDialog(String url, String filename, App app) { String extension = "png"; if (url.startsWith("data:text/")) { // eg Tizk export extension = "txt"; // could use: // TitleExportPstricks // TitleExportPgf } exportImage(url, filename, extension); // TODO check if it really happened app.dispatchEvent( new Event(EventType.EXPORT, null, "[\"" + extension + "\"]")); } @Override public boolean hasBase64(Material material) { return true; } @Override public void refreshAutosaveTimestamp() { // TODO Auto-generated method stub } }