我试图写入位于$ HOME目录中的文件。写入该文件的代码已打包到jar文件中。当我运行单元测试来打包jar文件时,一切都按预期工作 - 即文件已填充并可以再次读取。
当我尝试从包含jar文件的另一个应用程序运行此代码时,它失败了lib
目录。该文件已创建 - 但永远不会写入该文件。当应用程序去读取文件时,它无法解析它,因为它是空的。
以下是写入文件的代码:
logger.warn("TestNet wallet does not exist creating one now in the directory: " + walletPath)
testNetFileName.createNewFile()
logger.warn("Wallet file name: " + testNetFileName.getAbsolutePath)
logger.warn("Can write: "+ testNetFileName.canWrite())
logger.warn("Can read: " + testNetFileName.canRead)
val w = Wallet.fromWatchingKey(TestNet3Params.get(), testNetSeed)
w.autosaveToFile(testNetFileName, savingInterval, TimeUnit.MILLISECONDS, null)
w
}
这里是上述相关方法的日志形式:
2015-12-30 15:11:46,416 - [WARN] - from class com.suredbits.core.wallet.ColdStorageWallet$ in play-akka.actor.default-dispatcher-9
TestNet wallet exists, reading in the one from disk
2015-12-30 15:11:46,416 - [WARN] - from class com.suredbits.core.wallet.ColdStorageWallet$ in play-akka.actor.default-dispatcher-9
Wallet file name: /home/chris/testnet-cold-storage.wallet
然后就是炸弹。
以下是autoSaveToFile
public WalletFiles autosaveToFile(File f, long delayTime, TimeUnit timeUnit,
@Nullable WalletFiles.Listener eventListener) {
lock.lock();
try {
checkState(vFileManager == null, "Already auto saving this wallet.");
WalletFiles manager = new WalletFiles(this, f, delayTime, timeUnit);
if (eventListener != null)
manager.setListener(eventListener);
vFileManager = manager;
return manager;
} finally {
lock.unlock();
}
}
以及WalletFiles
public WalletFiles(final Wallet wallet, File file, long delay, TimeUnit delayTimeUnit) {
// An executor that starts up threads when needed and shuts them down later.
this.executor = new ScheduledThreadPoolExecutor(1, new ContextPropagatingThreadFactory("Wallet autosave thread", Thread.MIN_PRIORITY));
this.executor.setKeepAliveTime(5, TimeUnit.SECONDS);
this.executor.allowCoreThreadTimeOut(true);
this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.wallet = checkNotNull(wallet);
// File must only be accessed from the auto-save executor from now on, to avoid simultaneous access.
this.file = checkNotNull(file);
this.savePending = new AtomicBoolean();
this.delay = delay;
this.delayTimeUnit = checkNotNull(delayTimeUnit);
this.saver = new Callable<Void>() {
@Override public Void call() throws Exception {
// Runs in an auto save thread.
if (!savePending.getAndSet(false)) {
// Some other scheduled request already beat us to it.
return null;
}
log.info("Background saving wallet, last seen block is {}/{}", wallet.getLastBlockSeenHeight(), wallet.getLastBlockSeenHash());
saveNowInternal();
return null;
}
};
}
我猜这是某种权限问题,但我似乎无法解决这个问题。
编辑:这一切都在完全相同的Ubuntu 14.04机器上运行 - 没有增加不同操作系统的复杂性。答案 0 :(得分:3)
您通常不能依赖$HOME
的存在或可写性。实际上只有两种可移植方式可以识别(即提供路径)外部文件。
使用$HOME
的问题在于您无法知道运行该应用程序的用户标识。用户甚至可能拥有主目录,即使用户这样做,该目录也可能是可写的,也可能是不可写的。在您的特定情况下,您的进程可能能够创建文件(对目录本身进行写访问),但对文件的写访问权可能受到umask和/或ACL(在Windows上)或selinux(在Linux上)的限制。
换句话说,库的安装程序/用户必须明确地为应用程序提供已知的可写路径。
另一种思考方式是你正在编写可能在完全未知的环境中使用的库代码。除了您和用户之间的明确合同之外,您不能假设任何有关外部环境的内容。您可以在接口规范中声明$HOME
必须是可写的,但对于环境没有$HOME
可写的某些用户来说,这可能非常不方便。
更好,更便携的解决方案就是说
在命令行上指定
-Dcom.xyz.workdir=[path]
以指示要使用的工作路径
或
xyz库将在
XYZ_WORK
环境变量指定的路径中查找其工作目录
理想情况下,您可以同时为这些用户提供一些灵活性。
答案 1 :(得分:1)
savePending
总是假的。在call
开头,您检查它是false
,然后返回null
。永远不会执行实际的保存代码。我猜您打算检查它是否true
,并将其设置为true
,而不是false
。然后,您还需要将其重置为false
。
现在,为什么这在单元测试中起作用是另一回事。测试必须执行不同的代码。