/** * This file is part of ObjectFabric (http://objectfabric.org). * * ObjectFabric is licensed under the Apache License, Version 2.0, the terms * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html. * * Copyright ObjectFabric Inc. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ package org.objectfabric; import java.util.concurrent.ConcurrentHashMap; import org.objectfabric.CloseCounter.Callback; import com.google.gwt.core.client.JavaScriptObject; final class FileSystemQueue extends BlockQueue implements Runnable { // TODO limit pending reads too? private static final int MAX_ONGOING = 100; private final Location _location; private final ConcurrentHashMap<String, Object> _ongoing = new ConcurrentHashMap<String, Object>(); FileSystemQueue(Location location) { _location = location; if (Debug.THREADS) ThreadAssert.exchangeGive(this, this); onStarted(); } @Override void onClose(Callback callback) { Object key; if (Debug.ENABLED) { ThreadAssert.suspend(key = new Object()); ThreadAssert.resume(this, false); } if (Debug.THREADS) { ThreadAssert.exchangeTake(this); ThreadAssert.removePrivate(this); } if (Debug.ENABLED) ThreadAssert.resume(key); super.onClose(callback); } final ConcurrentHashMap<String, Object> ongoing() { return _ongoing; } @Override protected void enqueue() { Platform.get().execute(this); } @Override public void run() { if (onRunStarting()) { if (Debug.ENABLED) ThreadAssert.resume(this, false); runMessages(false); while (_ongoing.size() < MAX_ONGOING) { final Block block = nextBlock(); if (block == null) break; final FileSystemView view = (FileSystemView) block.URI.getOrCreate(_location); final String file = view.folder() + "/" + Utils.getTickHex(block.Tick); _ongoing.put(file, block); Uint8Array buffer = ((GWTBuff) block.Buffs[0]).subarray(); open(view, block, file, buffer); if (Debug.PERSISTENCE_LOG) Log.write("File write " + file); if (Stats.ENABLED) Stats.Instance.BlockWriteCount.incrementAndGet(); } if (Debug.ENABLED) ThreadAssert.suspend(this); onRunEnded(false); } } private final native void open(FileSystemView view, Block block, String file, Uint8Array buffer) /*-{ var this_ = this; fs.open(file, 'wx', function(err, fd) { if (err) throw err; this_.@org.objectfabric.FileSystemQueue::onWrite(Lorg/objectfabric/FileSystemView;Lorg/objectfabric/BlockQueue$Block;Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;III) // (view, block, file, fd, -1, 0, 0); }); }-*/; private final native void write(FileSystemView view, Block block, String file, JavaScriptObject fd, int index, Uint8Array buffer, int position) /*-{ var this_ = this; // writeFile params seem ignored, slice beforehand var slice = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.length); fs.writeFile(file, slice, 0,slice.length, position, function(err, written_, buffer_) { if (err) throw err; this_.@org.objectfabric.FileSystemQueue::onWrite(Lorg/objectfabric/FileSystemView;Lorg/objectfabric/BlockQueue$Block;Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;III) // (view, block, file, fd, index, position, buffer.length); }); }-*/; private void onWrite(FileSystemView view, Block block, String file, JavaScriptObject fd, int index, int position, int written) { index++; if (index < block.Buffs.length) write(view, block, file, fd, index, ((GWTBuff) block.Buffs[index]).subarray(), position + written); else fsync(view, block, file, fd); } private final native void fsync(FileSystemView view, Block block, String file, JavaScriptObject fd) /*-{ var this_ = this; fs.fsync(fd, function(err) { if (err) throw err; fs.close(fd); this_.@org.objectfabric.FileSystemQueue::onFsync(Lorg/objectfabric/FileSystemView;Lorg/objectfabric/BlockQueue$Block;Ljava/lang/String;)(view, block, file); }); }-*/; private void onFsync(FileSystemView view, Block block, String file) { block.URI.onAck(view, block.Tick); view.add(block.Tick, block.Removals); _ongoing.remove(file, block); for (int i = 0; i < block.Buffs.length; i++) block.Buffs[i].recycle(); // In case blocks left in queue requestRun(); if (block.Removals != null) for (int i = 0; i < block.Removals.length; i++) if (!Tick.isNull(block.Removals[i])) delete(view.folder() + "/" + Utils.getTickHex(block.Removals[i]), 0); } private final native void delete(String file, int attempt) /*-{ var this_ = this; fs.unlink(file, function(err) { this_.@org.objectfabric.FileSystemQueue::onDelete(Ljava/lang/String;ZI)(file, err, attempt); }); }-*/; private final void onDelete(final String file, boolean error, final int attempt) { if (error) { if (attempt < 3) { Platform.get().schedule(new Runnable() { @Override public void run() { delete(file, attempt + 1); } }, 100); } else Log.write("Could not delete " + file); } else if (Debug.PERSISTENCE_LOG) Log.write("File delete " + file); } }