зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1654295: Reduce boilerplate in IOUtils failure paths r=barret
This change completely overhauls the way errors are handled in `IOUtils`. With the introduction of the new `IOError` type, a useful error message is paired with an `nsresult` at the error site. This provides erroneous callers with more information about what went wrong, while improving consistency with mapping errors to `DOMExceptions`. For error sites where it is not immediately clear what went wrong, messages can be omitted. A default error message will be filled in corresponding with the wrapped `nsresult` when the operation is rejected to the calling JavaScript. Differential Revision: https://phabricator.services.mozilla.com/D84736
This commit is contained in:
Родитель
970f76ccd9
Коммит
50a79e72d0
|
@ -9,22 +9,15 @@
|
|||
#include "mozilla/dom/IOUtils.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/ErrorNames.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/ResultExtensions.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/Span.h"
|
||||
#include "mozilla/TextUtils.h"
|
||||
#include "nspr/prio.h"
|
||||
#include "nspr/private/pprio.h"
|
||||
#include "nspr/prtypes.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsNativeCharsetUtils.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "nsThreadManager.h"
|
||||
#include "SpecialSystemDirectory.h"
|
||||
|
||||
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
|
||||
# include <fcntl.h>
|
||||
|
@ -218,7 +211,7 @@ already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
|
|||
[path = nsAutoString(aPath), toRead]() {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
auto rv = IOUtils::ReadSync(path, toRead);
|
||||
return ToMozPromise<IOReadMozPromise, nsTArray<uint8_t>, nsresult>(
|
||||
return ToMozPromise<IOReadMozPromise, nsTArray<uint8_t>, IOError>(
|
||||
rv, __func__);
|
||||
})
|
||||
->Then(
|
||||
|
@ -227,26 +220,8 @@ already_AddRefed<Promise> IOUtils::Read(GlobalObject& aGlobal,
|
|||
const TypedArrayCreator<Uint8Array> arrayCreator(aBuf);
|
||||
promise->MaybeResolve(arrayCreator);
|
||||
},
|
||||
[promise = RefPtr(promise),
|
||||
path = NS_ConvertUTF16toUTF8(aPath)](const nsresult& aError) {
|
||||
switch (aError) {
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
promise->MaybeRejectWithNotFoundError(
|
||||
nsPrintfCString("Could not open file at %s", path.get()));
|
||||
break;
|
||||
case NS_ERROR_FILE_ACCESS_DENIED:
|
||||
promise->MaybeRejectWithNotReadableError(nsPrintfCString(
|
||||
"Could not get info for file at %s", path.get()));
|
||||
break;
|
||||
case NS_ERROR_FILE_TOO_BIG:
|
||||
promise->MaybeRejectWithNotReadableError(nsPrintfCString(
|
||||
"File at %s is too large to be read", path.get()));
|
||||
break;
|
||||
default:
|
||||
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
||||
aError, "Unexpected error reading file at %s", path.get()));
|
||||
}
|
||||
[promise = RefPtr(promise)](const IOError& aError) {
|
||||
RejectJSPromise(promise, aError);
|
||||
});
|
||||
|
||||
return promise.forget();
|
||||
|
@ -278,49 +253,15 @@ already_AddRefed<Promise> IOUtils::WriteAtomic(
|
|||
[destPath = nsString(aPath), toWrite = std::move(toWrite), aOptions]() {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
auto rv = WriteAtomicSync(destPath, toWrite, aOptions);
|
||||
return ToMozPromise<IOWriteMozPromise, uint32_t, nsresult>(rv,
|
||||
__func__);
|
||||
return ToMozPromise<IOWriteMozPromise, uint32_t, IOError>(rv, __func__);
|
||||
})
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[promise = RefPtr(promise)](const uint32_t& aBytesWritten) {
|
||||
promise->MaybeResolve(aBytesWritten);
|
||||
},
|
||||
[promise = RefPtr(promise),
|
||||
path = NS_ConvertUTF16toUTF8(aPath)](const nsresult& aError) {
|
||||
switch (aError) {
|
||||
case NS_ERROR_FILE_ALREADY_EXISTS:
|
||||
promise->MaybeRejectWithNoModificationAllowedError(
|
||||
nsPrintfCString("Refusing to overwrite the file at %s",
|
||||
path.get()));
|
||||
break;
|
||||
// TODO: It would be nice to be able to distinguish between an
|
||||
// error with backing up the original file and moving the
|
||||
// temp file to overwrite the dest.
|
||||
case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
|
||||
promise->MaybeRejectWithOperationError(
|
||||
nsPrintfCString("Atomic write failed, but the destination "
|
||||
"at %s should not have been changed",
|
||||
path.get()));
|
||||
break;
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
promise->MaybeRejectWithNotFoundError(
|
||||
nsPrintfCString("Could not open file at %s", path.get()));
|
||||
break;
|
||||
case NS_ERROR_FILE_ACCESS_DENIED:
|
||||
promise->MaybeRejectWithNotReadableError(nsPrintfCString(
|
||||
"Could not open the file at %s", path.get()));
|
||||
break;
|
||||
case NS_ERROR_FILE_TOO_BIG:
|
||||
promise->MaybeRejectWithNotReadableError(nsPrintfCString(
|
||||
"File at %s is too large to be read", path.get()));
|
||||
break;
|
||||
default:
|
||||
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
||||
aError, "Unexpected error writing to file at %s",
|
||||
path.get()));
|
||||
}
|
||||
[promise = RefPtr(promise)](const IOError& aError) {
|
||||
RejectJSPromise(promise, aError);
|
||||
});
|
||||
|
||||
return promise.forget();
|
||||
|
@ -352,44 +293,15 @@ already_AddRefed<Promise> IOUtils::Move(GlobalObject& aGlobal,
|
|||
destPathString = nsAutoString(aDestPath), noOverwrite]() {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
auto rv = MoveSync(srcPathString, destPathString, noOverwrite);
|
||||
return ToMozPromise<IOMozPromise, Ok, nsresult>(rv, __func__);
|
||||
return ToMozPromise<IOMozPromise, Ok, IOError>(rv, __func__);
|
||||
})
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[promise = RefPtr(promise)](const Ok&) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
},
|
||||
[promise = RefPtr(promise)](const nsresult& aError) {
|
||||
switch (aError) {
|
||||
case NS_ERROR_FILE_ACCESS_DENIED:
|
||||
promise->MaybeRejectWithInvalidAccessError("Access denied");
|
||||
break;
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
promise->MaybeRejectWithNotFoundError(
|
||||
"Source file does not exist");
|
||||
break;
|
||||
case NS_ERROR_FILE_ALREADY_EXISTS:
|
||||
promise->MaybeRejectWithNoModificationAllowedError(
|
||||
"Destination file exists and overwrites are not allowed");
|
||||
break;
|
||||
case NS_ERROR_FILE_READ_ONLY:
|
||||
promise->MaybeRejectWithNoModificationAllowedError(
|
||||
"Destination is read only");
|
||||
break;
|
||||
case NS_ERROR_FILE_DESTINATION_NOT_DIR:
|
||||
promise->MaybeRejectWithInvalidAccessError(
|
||||
"Source is a directory but destination is not");
|
||||
break;
|
||||
case NS_ERROR_FILE_UNRECOGNIZED_PATH:
|
||||
promise->MaybeRejectWithOperationError(
|
||||
"Only absolute file paths are permitted");
|
||||
break;
|
||||
default: {
|
||||
promise->MaybeRejectWithUnknownError(
|
||||
FormatErrorMessage(aError, "Unexpected error moving file"));
|
||||
}
|
||||
}
|
||||
[promise = RefPtr(promise)](const IOError& aError) {
|
||||
RejectJSPromise(promise, aError);
|
||||
});
|
||||
|
||||
return promise.forget();
|
||||
|
@ -413,29 +325,15 @@ already_AddRefed<Promise> IOUtils::Remove(GlobalObject& aGlobal,
|
|||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
auto rv = RemoveSync(path, aOptions.mIgnoreAbsent,
|
||||
aOptions.mRecursive);
|
||||
return ToMozPromise<IOMozPromise, Ok, nsresult>(rv, __func__);
|
||||
return ToMozPromise<IOMozPromise, Ok, IOError>(rv, __func__);
|
||||
})
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[promise = RefPtr(promise)](const Ok&) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
},
|
||||
[promise = RefPtr(promise)](const nsresult& aError) {
|
||||
switch (aError) {
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
promise->MaybeRejectWithNotFoundError(
|
||||
"Target file does not exist");
|
||||
break;
|
||||
case NS_ERROR_FILE_DIR_NOT_EMPTY:
|
||||
promise->MaybeRejectWithOperationError(
|
||||
"Could not remove non-empty directory, specify the "
|
||||
"`recursive: true` option to mitigate this error");
|
||||
break;
|
||||
default:
|
||||
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
||||
aError, "Unexpected error removing file"));
|
||||
}
|
||||
[promise = RefPtr(promise)](const IOError& aError) {
|
||||
RejectJSPromise(promise, aError);
|
||||
});
|
||||
|
||||
return promise.forget();
|
||||
|
@ -459,32 +357,15 @@ already_AddRefed<Promise> IOUtils::MakeDirectory(
|
|||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
auto rv = CreateDirectorySync(path, aOptions.mCreateAncestors,
|
||||
aOptions.mIgnoreExisting);
|
||||
return ToMozPromise<IOMozPromise, Ok, nsresult>(rv, __func__);
|
||||
return ToMozPromise<IOMozPromise, Ok, IOError>(rv, __func__);
|
||||
})
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[promise = RefPtr(promise)](const Ok&) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
},
|
||||
[promise = RefPtr(promise)](const nsresult& aError) {
|
||||
switch (aError) {
|
||||
case NS_ERROR_FILE_ALREADY_EXISTS:
|
||||
promise->MaybeRejectWithNoModificationAllowedError(
|
||||
"Could not create directory because file already exists");
|
||||
break;
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
promise->MaybeRejectWithNotFoundError(
|
||||
"Target path has missing ancestors");
|
||||
break;
|
||||
case NS_ERROR_FILE_NOT_DIRECTORY:
|
||||
promise->MaybeRejectWithOperationError(
|
||||
"Target exists and is not a directory");
|
||||
break;
|
||||
default:
|
||||
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
||||
aError, "Unexpected error creating directory"));
|
||||
}
|
||||
[promise = RefPtr(promise)](const IOError& aError) {
|
||||
RejectJSPromise(promise, aError);
|
||||
});
|
||||
return promise.forget();
|
||||
}
|
||||
|
@ -506,7 +387,7 @@ already_AddRefed<Promise> IOUtils::Stat(GlobalObject& aGlobal,
|
|||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
auto rv = StatSync(path);
|
||||
return ToMozPromise<IOStatMozPromise, InternalFileInfo, nsresult>(
|
||||
return ToMozPromise<IOStatMozPromise, InternalFileInfo, IOError>(
|
||||
rv, __func__);
|
||||
})
|
||||
->Then(
|
||||
|
@ -514,17 +395,8 @@ already_AddRefed<Promise> IOUtils::Stat(GlobalObject& aGlobal,
|
|||
[promise = RefPtr(promise)](const InternalFileInfo& aInfo) {
|
||||
promise->MaybeResolve(aInfo);
|
||||
},
|
||||
[promise = RefPtr(promise)](const nsresult& aError) {
|
||||
switch (aError) {
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
promise->MaybeRejectWithNotFoundError(
|
||||
"Target file does not exist");
|
||||
break;
|
||||
default:
|
||||
promise->MaybeRejectWithUnknownError(FormatErrorMessage(
|
||||
aError, "Unexpected error accessing file"));
|
||||
}
|
||||
[promise = RefPtr(promise)](const IOError& aError) {
|
||||
RejectJSPromise(promise, aError);
|
||||
});
|
||||
|
||||
return promise.forget();
|
||||
|
@ -602,6 +474,55 @@ already_AddRefed<Promise> IOUtils::CreateJSPromise(GlobalObject& aGlobal) {
|
|||
return do_AddRef(promise);
|
||||
}
|
||||
|
||||
/* static */
|
||||
void IOUtils::RejectJSPromise(const RefPtr<Promise>& aPromise,
|
||||
const IOError& aError) {
|
||||
const auto& errMsg = aError.Message();
|
||||
|
||||
switch (aError.Code()) {
|
||||
case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
|
||||
case NS_ERROR_FILE_NOT_FOUND:
|
||||
aPromise->MaybeRejectWithNotFoundError(errMsg.refOr("File not found"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_ACCESS_DENIED:
|
||||
aPromise->MaybeRejectWithNotAllowedError(
|
||||
errMsg.refOr("Access was denied to the target file"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_TOO_BIG:
|
||||
aPromise->MaybeRejectWithNotReadableError(
|
||||
errMsg.refOr("Target file is too big"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_ALREADY_EXISTS:
|
||||
aPromise->MaybeRejectWithNoModificationAllowedError(
|
||||
errMsg.refOr("Target file already exists"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_COPY_OR_MOVE_FAILED:
|
||||
aPromise->MaybeRejectWithOperationError(
|
||||
errMsg.refOr("Failed to copy or move the target file"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_READ_ONLY:
|
||||
aPromise->MaybeRejectWithReadOnlyError(
|
||||
errMsg.refOr("Target file is read only"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_NOT_DIRECTORY:
|
||||
case NS_ERROR_FILE_DESTINATION_NOT_DIR:
|
||||
aPromise->MaybeRejectWithInvalidAccessError(
|
||||
errMsg.refOr("Target file is not a directory"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_UNRECOGNIZED_PATH:
|
||||
aPromise->MaybeRejectWithOperationError(
|
||||
errMsg.refOr("Target file path is not recognized"_ns));
|
||||
break;
|
||||
case NS_ERROR_FILE_DIR_NOT_EMPTY:
|
||||
aPromise->MaybeRejectWithOperationError(
|
||||
errMsg.refOr("Target directory is not empty"_ns));
|
||||
break;
|
||||
default:
|
||||
aPromise->MaybeRejectWithUnknownError(
|
||||
errMsg.refOr(FormatErrorMessage(aError.Code(), "Unexpected error")));
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::OpenExistingSync(
|
||||
const nsAString& aPath, int32_t aFlags) {
|
||||
|
@ -641,25 +562,34 @@ UniquePtr<PRFileDesc, PR_CloseDelete> IOUtils::CreateFileSync(
|
|||
}
|
||||
|
||||
/* static */
|
||||
Result<nsTArray<uint8_t>, nsresult> IOUtils::ReadSync(
|
||||
Result<nsTArray<uint8_t>, IOUtils::IOError> IOUtils::ReadSync(
|
||||
const nsAString& aPath, const Maybe<uint32_t>& aMaxBytes) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
UniquePtr<PRFileDesc, PR_CloseDelete> fd = OpenExistingSync(aPath, PR_RDONLY);
|
||||
if (!fd) {
|
||||
return Err(NS_ERROR_FILE_NOT_FOUND);
|
||||
return Err(IOError(NS_ERROR_FILE_NOT_FOUND)
|
||||
.WithMessage("Could not open the file at %s",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
uint32_t bufSize;
|
||||
if (aMaxBytes.isNothing()) {
|
||||
// Limitation: We cannot read files that are larger than the max size of a
|
||||
// TypedArray (UINT32_MAX bytes). Reject if the file is too big
|
||||
// to be read.
|
||||
// TypedArray (UINT32_MAX bytes). Reject if the file is too
|
||||
// big to be read.
|
||||
PRFileInfo64 info;
|
||||
if (PR_FAILURE == PR_GetOpenFileInfo64(fd.get(), &info)) {
|
||||
return Err(NS_ERROR_FILE_ACCESS_DENIED);
|
||||
return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED)
|
||||
.WithMessage("Could not get info for the file at %s",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
if (static_cast<uint64_t>(info.size) > UINT32_MAX) {
|
||||
return Err(NS_ERROR_FILE_TOO_BIG);
|
||||
return Err(
|
||||
IOError(NS_ERROR_FILE_TOO_BIG)
|
||||
.WithMessage("Could not read the file at %s because it is too "
|
||||
"large(size=%" PRIu64 " bytes)",
|
||||
NS_ConvertUTF16toUTF8(aPath).get(),
|
||||
static_cast<uint64_t>(info.size)));
|
||||
}
|
||||
bufSize = static_cast<uint32_t>(info.size);
|
||||
} else {
|
||||
|
@ -668,7 +598,7 @@ Result<nsTArray<uint8_t>, nsresult> IOUtils::ReadSync(
|
|||
|
||||
nsTArray<uint8_t> buffer;
|
||||
if (!buffer.SetCapacity(bufSize, fallible)) {
|
||||
return Err(NS_ERROR_OUT_OF_MEMORY);
|
||||
return Err(IOError(NS_ERROR_OUT_OF_MEMORY));
|
||||
}
|
||||
|
||||
// If possible, advise the operating system that we will be reading the file
|
||||
|
@ -687,7 +617,10 @@ Result<nsTArray<uint8_t>, nsresult> IOUtils::ReadSync(
|
|||
break;
|
||||
}
|
||||
if (nRead < 0) {
|
||||
return Err(NS_ERROR_UNEXPECTED);
|
||||
return Err(
|
||||
IOError(NS_ERROR_UNEXPECTED)
|
||||
.WithMessage("Encountered an unexpected error while reading %s",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
totalRead += nRead;
|
||||
DebugOnly<bool> success = buffer.SetLength(totalRead, fallible);
|
||||
|
@ -697,7 +630,7 @@ Result<nsTArray<uint8_t>, nsresult> IOUtils::ReadSync(
|
|||
}
|
||||
|
||||
/* static */
|
||||
Result<uint32_t, nsresult> IOUtils::WriteAtomicSync(
|
||||
Result<uint32_t, IOUtils::IOError> IOUtils::WriteAtomicSync(
|
||||
const nsAString& aDestPath, const nsTArray<uint8_t>& aByteArray,
|
||||
const WriteAtomicOptions& aOptions) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
@ -712,13 +645,22 @@ Result<uint32_t, nsresult> IOUtils::WriteAtomicSync(
|
|||
}
|
||||
|
||||
if (noOverwrite && exists) {
|
||||
return Err(NS_ERROR_FILE_ALREADY_EXISTS);
|
||||
return Err(IOError(NS_ERROR_FILE_ALREADY_EXISTS)
|
||||
.WithMessage("Refusing to overwrite the file at %s\n"
|
||||
"Specify `noOverwrite: false` to allow "
|
||||
"overwriting the destination",
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
||||
}
|
||||
|
||||
// If backupFile was specified, perform the backup as a move.
|
||||
if (exists && aOptions.mBackupFile.WasPassed() &&
|
||||
MoveSync(aDestPath, aOptions.mBackupFile.Value(), noOverwrite).isErr()) {
|
||||
return Err(NS_ERROR_FILE_COPY_OR_MOVE_FAILED);
|
||||
return Err(
|
||||
IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
||||
.WithMessage(
|
||||
"Failed to backup the source file(%s) to %s",
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get(),
|
||||
NS_ConvertUTF16toUTF8(aOptions.mBackupFile.Value()).get()));
|
||||
}
|
||||
|
||||
// If tmpPath was specified, we will write to there first, then perform
|
||||
|
@ -746,12 +688,14 @@ Result<uint32_t, nsresult> IOUtils::WriteAtomicSync(
|
|||
fd = CreateFileSync(tmpPath, flags);
|
||||
}
|
||||
if (!fd) {
|
||||
return Err(NS_ERROR_FILE_ACCESS_DENIED);
|
||||
return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED)
|
||||
.WithMessage("Could not open the file at %s for writing",
|
||||
NS_ConvertUTF16toUTF8(tmpPath).get()));
|
||||
}
|
||||
|
||||
auto rv = WriteSync(fd.get(), aByteArray);
|
||||
auto rv = WriteSync(fd.get(), NS_ConvertUTF16toUTF8(tmpPath), aByteArray);
|
||||
if (rv.isErr()) {
|
||||
return rv;
|
||||
return rv.propagateErr();
|
||||
}
|
||||
result = rv.unwrap();
|
||||
}
|
||||
|
@ -759,15 +703,20 @@ Result<uint32_t, nsresult> IOUtils::WriteAtomicSync(
|
|||
// If tmpPath was specified and different from the destPath, then the
|
||||
// operation is finished by performing a move.
|
||||
if (aDestPath != tmpPath && MoveSync(tmpPath, aDestPath, false).isErr()) {
|
||||
return Err(NS_ERROR_FILE_COPY_OR_MOVE_FAILED);
|
||||
return Err(
|
||||
IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED)
|
||||
.WithMessage("Could not move temporary file(%s) to destination(%s)",
|
||||
NS_ConvertUTF16toUTF8(tmpPath).get(),
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* static */
|
||||
Result<uint32_t, nsresult> IOUtils::WriteSync(PRFileDesc* aFd,
|
||||
const nsTArray<uint8_t>& aBytes) {
|
||||
// aBytes comes from a JavaScript TypedArray, which has UINT32_MAX max length.
|
||||
Result<uint32_t, IOUtils::IOError> IOUtils::WriteSync(
|
||||
PRFileDesc* aFd, const nsACString& aPath, const nsTArray<uint8_t>& aBytes) {
|
||||
// aBytes comes from a JavaScript TypedArray, which has UINT32_MAX max
|
||||
// length.
|
||||
MOZ_ASSERT(aBytes.Length() <= UINT32_MAX);
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
|
@ -788,7 +737,11 @@ Result<uint32_t, nsresult> IOUtils::WriteSync(PRFileDesc* aFd,
|
|||
}
|
||||
int32_t rv = PR_Write(aFd, aBytes.Elements() + bytesWritten, chunkSize);
|
||||
if (rv < 0) {
|
||||
return Err(NS_ERROR_FILE_CORRUPTED);
|
||||
return Err(IOError(NS_ERROR_FILE_CORRUPTED)
|
||||
.WithMessage("Could not write chunk(size=%" PRIu32
|
||||
") to %s\n"
|
||||
"The file may be corrupt",
|
||||
chunkSize, aPath.BeginReading()));
|
||||
}
|
||||
pendingBytes -= rv;
|
||||
bytesWritten += rv;
|
||||
|
@ -798,25 +751,38 @@ Result<uint32_t, nsresult> IOUtils::WriteSync(PRFileDesc* aFd,
|
|||
}
|
||||
|
||||
/* static */
|
||||
Result<Ok, nsresult> IOUtils::MoveSync(const nsAString& aSourcePath,
|
||||
const nsAString& aDestPath,
|
||||
bool noOverwrite) {
|
||||
Result<Ok, IOUtils::IOError> IOUtils::MoveSync(const nsAString& aSourcePath,
|
||||
const nsAString& aDestPath,
|
||||
bool noOverwrite) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// Assess the source file.
|
||||
nsCOMPtr<nsIFile> srcFile = new nsLocalFile();
|
||||
MOZ_TRY(srcFile->InitWithPath(aSourcePath)); // Fails if not absolute.
|
||||
MOZ_TRY(srcFile->Normalize()); // Fails if path does not exist.
|
||||
bool srcExists;
|
||||
// Ensure the source file exists before continuing. If it doesn't exist, the
|
||||
// following operations can fail in different ways on different platforms.
|
||||
MOZ_TRY(srcFile->Exists(&srcExists));
|
||||
if (!srcExists) {
|
||||
return Err(
|
||||
IOError(NS_ERROR_FILE_NOT_FOUND)
|
||||
.WithMessage(
|
||||
"Could not move source file(%s) because it does not exist",
|
||||
NS_ConvertUTF16toUTF8(aSourcePath).get()));
|
||||
}
|
||||
MOZ_TRY(srcFile->Normalize());
|
||||
|
||||
// Prepare the destination file.
|
||||
nsCOMPtr<nsIFile> destFile = new nsLocalFile();
|
||||
MOZ_TRY(destFile->InitWithPath(aDestPath));
|
||||
MOZ_TRY(destFile->InitWithPath(aDestPath)); // Fails if not absolute.
|
||||
rv = destFile->Normalize();
|
||||
// Normalize can fail for a number of reasons, including if the file doesn't
|
||||
// exist. It is expected that the file might not exist for a number of calls
|
||||
// (e.g. if we want to rename a file to a new location).
|
||||
if (NS_FAILED(rv) && !IsFileNotFound(rv)) {
|
||||
// Deliberately ignore "not found" errors, but propagate all others.
|
||||
return Err(rv);
|
||||
return Err(IOError(rv));
|
||||
}
|
||||
|
||||
// Case 1: Destination is an existing directory. Move source into dest.
|
||||
|
@ -825,21 +791,38 @@ Result<Ok, nsresult> IOUtils::MoveSync(const nsAString& aSourcePath,
|
|||
|
||||
rv = destFile->IsDirectory(&destIsDir);
|
||||
if (NS_SUCCEEDED(rv) && destIsDir) {
|
||||
return ToResult(srcFile->MoveTo(destFile, EmptyString()));
|
||||
rv = srcFile->MoveTo(destFile, EmptyString());
|
||||
if (NS_FAILED(rv)) {
|
||||
return Err(IOError(rv).WithMessage(
|
||||
"Could not move source file(%s) to destination(%s)",
|
||||
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
if (NS_FAILED(rv) && IsFileNotFound(rv)) {
|
||||
// It's ok if the file doesn't exist. Case 2 handles this below.
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
if (!IsFileNotFound(rv)) {
|
||||
// It's ok if the dest file doesn't exist. Case 2 handles this below.
|
||||
// Bail out early for any other kind of error though.
|
||||
return Err(IOError(rv));
|
||||
}
|
||||
destExists = false;
|
||||
} else if (NS_FAILED(rv)) {
|
||||
// Bail out early for any other kind of error though.
|
||||
return Err(rv);
|
||||
}
|
||||
|
||||
// Case 2: Destination is a file which may or may not exist. Try to rename the
|
||||
// source to the destination. This will fail if the source is a not a
|
||||
// regular file.
|
||||
// source to the destination. If the destination exists and the source
|
||||
// is not a regular file, then this may fail.
|
||||
if (noOverwrite && destExists) {
|
||||
return Err(NS_ERROR_FILE_ALREADY_EXISTS);
|
||||
return Err(
|
||||
IOError(NS_ERROR_FILE_ALREADY_EXISTS)
|
||||
.WithMessage(
|
||||
"Could not move source file(%s) to destination(%s) because the "
|
||||
"destination already exists and overwrites are not allowed\n"
|
||||
"Specify the `noOverwrite: false` option to mitigate this "
|
||||
"error",
|
||||
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
||||
}
|
||||
if (destExists && !destIsDir) {
|
||||
// If the source file is a directory, but the target is a file, abort early.
|
||||
|
@ -848,7 +831,12 @@ Result<Ok, nsresult> IOUtils::MoveSync(const nsAString& aSourcePath,
|
|||
bool srcIsDir = false;
|
||||
MOZ_TRY(srcFile->IsDirectory(&srcIsDir));
|
||||
if (srcIsDir) {
|
||||
return Err(NS_ERROR_FILE_DESTINATION_NOT_DIR);
|
||||
return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR)
|
||||
.WithMessage("Could not move the source directory(%s) to "
|
||||
"the destination(%s) "
|
||||
"because the destination is not a directory",
|
||||
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -858,12 +846,20 @@ Result<Ok, nsresult> IOUtils::MoveSync(const nsAString& aSourcePath,
|
|||
MOZ_TRY(destFile->GetParent(getter_AddRefs(destDir)));
|
||||
|
||||
// NB: if destDir doesn't exist, then MoveTo will create it.
|
||||
return ToResult(srcFile->MoveTo(destDir, destName));
|
||||
rv = srcFile->MoveTo(destDir, destName);
|
||||
if (NS_FAILED(rv)) {
|
||||
return Err(IOError(rv).WithMessage(
|
||||
"Could not move the source file(%s) to the destination(%s)",
|
||||
NS_ConvertUTF16toUTF8(aSourcePath).get(),
|
||||
NS_ConvertUTF16toUTF8(aDestPath).get()));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/* static */
|
||||
Result<Ok, nsresult> IOUtils::RemoveSync(const nsAString& aPath,
|
||||
bool aIgnoreAbsent, bool aRecursive) {
|
||||
Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(const nsAString& aPath,
|
||||
bool aIgnoreAbsent,
|
||||
bool aRecursive) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
RefPtr<nsLocalFile> file = new nsLocalFile();
|
||||
|
@ -873,51 +869,88 @@ Result<Ok, nsresult> IOUtils::RemoveSync(const nsAString& aPath,
|
|||
if (aIgnoreAbsent && IsFileNotFound(rv)) {
|
||||
return Ok();
|
||||
}
|
||||
return ToResult(rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
IOError err(rv);
|
||||
if (IsFileNotFound(rv)) {
|
||||
return Err(err.WithMessage(
|
||||
"Could not remove the file at %s because it does not exist.\n"
|
||||
"Specify the `ignoreAbsent: true` option to mitigate this error",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY) {
|
||||
return Err(err.WithMessage(
|
||||
"Could not remove the non-empty directory at %s.\n"
|
||||
"Specify the `recursive: true` option to mitigate this error",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
return Err(err.WithMessage("Could not remove the file at %s",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/* static */
|
||||
Result<Ok, nsresult> IOUtils::CreateDirectorySync(const nsAString& aPath,
|
||||
bool aCreateAncestors,
|
||||
bool aIgnoreExisting,
|
||||
int32_t aMode) {
|
||||
Result<Ok, IOUtils::IOError> IOUtils::CreateDirectorySync(
|
||||
const nsAString& aPath, bool aCreateAncestors, bool aIgnoreExisting,
|
||||
int32_t aMode) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
RefPtr<nsLocalFile> targetFile = new nsLocalFile();
|
||||
MOZ_TRY(targetFile->InitWithPath(aPath));
|
||||
|
||||
// nsIFile::Create will create ancestor directories by default.
|
||||
// If the caller does not want this behaviour, then check and possibly return
|
||||
// an error.
|
||||
// If the caller does not want this behaviour, then check and possibly
|
||||
// return an error.
|
||||
if (!aCreateAncestors) {
|
||||
nsCOMPtr<nsIFile> parent;
|
||||
MOZ_TRY(targetFile->GetParent(getter_AddRefs(parent)));
|
||||
bool parentExists;
|
||||
MOZ_TRY(parent->Exists(&parentExists));
|
||||
if (!parentExists) {
|
||||
return Err(NS_ERROR_FILE_NOT_FOUND);
|
||||
return Err(IOError(NS_ERROR_FILE_NOT_FOUND)
|
||||
.WithMessage("Could not create directory at %s because "
|
||||
"the path has missing "
|
||||
"ancestor components",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = targetFile->Create(nsIFile::DIRECTORY_TYPE, aMode);
|
||||
if (NS_FAILED(rv) && rv == NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
// NB: We may report a success only if the target is an existing directory.
|
||||
// We don't want to silence errors that occur if the target is an existing
|
||||
// file, since trying to create a directory where a regular file exists
|
||||
// may be indicative of a logic error.
|
||||
bool isDirectory;
|
||||
MOZ_TRY(targetFile->IsDirectory(&isDirectory));
|
||||
if (!isDirectory) {
|
||||
return Err(NS_ERROR_FILE_NOT_DIRECTORY);
|
||||
}
|
||||
if (aIgnoreExisting) {
|
||||
return Ok();
|
||||
if (NS_FAILED(rv)) {
|
||||
IOError err(rv);
|
||||
if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
|
||||
// NB: We may report a success only if the target is an existing
|
||||
// directory. We don't want to silence errors that occur if the target is
|
||||
// an existing file, since trying to create a directory where a regular
|
||||
// file exists may be indicative of a logic error.
|
||||
bool isDirectory;
|
||||
MOZ_TRY(targetFile->IsDirectory(&isDirectory));
|
||||
if (!isDirectory) {
|
||||
return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY)
|
||||
.WithMessage("Could not create directory because the "
|
||||
"target file(%s) exists "
|
||||
"and is not a directory",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
// The directory exists.
|
||||
// The caller may suppress this error.
|
||||
if (aIgnoreExisting) {
|
||||
return Ok();
|
||||
}
|
||||
// Otherwise, forward it.
|
||||
return Err(err.WithMessage(
|
||||
"Could not create directory because it already exists at %s\n"
|
||||
"Specify the `ignoreExisting: true` option to mitigate this "
|
||||
"error",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
return Err(err.WithMessage("Could not create directory at %s",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
return ToResult(rv);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<IOUtils::InternalFileInfo, nsresult> IOUtils::StatSync(
|
||||
Result<IOUtils::InternalFileInfo, IOUtils::IOError> IOUtils::StatSync(
|
||||
const nsAString& aPath) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
|
@ -927,9 +960,23 @@ Result<IOUtils::InternalFileInfo, nsresult> IOUtils::StatSync(
|
|||
InternalFileInfo info;
|
||||
info.mPath = nsString(aPath);
|
||||
|
||||
info.mType = FileType::Regular;
|
||||
bool isRegular;
|
||||
MOZ_TRY(file->IsFile(&isRegular));
|
||||
// IsFile will stat and cache info in the file object. If the file doesn't
|
||||
// exist, or there is an access error, we'll discover it here.
|
||||
// Any subsequent errors are unexpected and will just be forwarded.
|
||||
nsresult rv = file->IsFile(&isRegular);
|
||||
if (NS_FAILED(rv)) {
|
||||
IOError err(rv);
|
||||
if (IsFileNotFound(rv)) {
|
||||
return Err(
|
||||
err.WithMessage("Could not stat file(%s) because it does not exist",
|
||||
NS_ConvertUTF16toUTF8(aPath).get()));
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Now we can populate the info object by querying the file.
|
||||
info.mType = FileType::Regular;
|
||||
if (!isRegular) {
|
||||
bool isDir;
|
||||
MOZ_TRY(file->IsDirectory(&isDir));
|
||||
|
|
|
@ -12,12 +12,14 @@
|
|||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/IOUtilsBinding.h"
|
||||
#include "mozilla/dom/TypedArray.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "nspr/prio.h"
|
||||
#include "nsIAsyncShutdown.h"
|
||||
#include "nsISerialEventTarget.h"
|
||||
#include "nsLocalFile.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -76,6 +78,8 @@ class IOUtils final {
|
|||
friend class IOUtilsShutdownBlocker;
|
||||
struct InternalFileInfo;
|
||||
|
||||
class IOError;
|
||||
|
||||
static StaticDataMutex<StaticRefPtr<nsISerialEventTarget>>
|
||||
sBackgroundEventTarget;
|
||||
static StaticRefPtr<nsIAsyncShutdownClient> sBarrier;
|
||||
|
@ -99,6 +103,12 @@ class IOUtils final {
|
|||
const InternalFileInfo& aInternalFileInfo,
|
||||
JS::MutableHandle<JS::Value> aValue);
|
||||
|
||||
/**
|
||||
* Rejects |aPromise| with an appropriate |DOMException| describing |aError|.
|
||||
*/
|
||||
static void RejectJSPromise(const RefPtr<Promise>& aPromise,
|
||||
const IOError& aError);
|
||||
|
||||
/**
|
||||
* Opens an existing file at |aPath|.
|
||||
*
|
||||
|
@ -133,7 +143,7 @@ class IOUtils final {
|
|||
*
|
||||
* @return A byte array of the entire file contents, or an error.
|
||||
*/
|
||||
static Result<nsTArray<uint8_t>, nsresult> ReadSync(
|
||||
static Result<nsTArray<uint8_t>, IOError> ReadSync(
|
||||
const nsAString& aPath, const Maybe<uint32_t>& aMaxBytes);
|
||||
|
||||
/**
|
||||
|
@ -148,7 +158,7 @@ class IOUtils final {
|
|||
* @return The number of bytes written to the file, or an error if the write
|
||||
* failed or was incomplete.
|
||||
*/
|
||||
static Result<uint32_t, nsresult> WriteAtomicSync(
|
||||
static Result<uint32_t, IOError> WriteAtomicSync(
|
||||
const nsAString& aDestPath, const nsTArray<uint8_t>& aByteArray,
|
||||
const WriteAtomicOptions& aOptions);
|
||||
|
||||
|
@ -162,8 +172,9 @@ class IOUtils final {
|
|||
* @return The number of bytes written to the file, or an error if the write
|
||||
* failed or was incomplete.
|
||||
*/
|
||||
static Result<uint32_t, nsresult> WriteSync(PRFileDesc* aFd,
|
||||
const nsTArray<uint8_t>& aBytes);
|
||||
static Result<uint32_t, IOError> WriteSync(PRFileDesc* aFd,
|
||||
const nsACString& aPath,
|
||||
const nsTArray<uint8_t>& aBytes);
|
||||
|
||||
/**
|
||||
* Attempts to move the file located at |aSource| to |aDest|.
|
||||
|
@ -177,9 +188,8 @@ class IOUtils final {
|
|||
*
|
||||
* @return Ok if the file was moved successfully, or an error.
|
||||
*/
|
||||
static Result<Ok, nsresult> MoveSync(const nsAString& aSource,
|
||||
const nsAString& aDest,
|
||||
bool noOverwrite);
|
||||
static Result<Ok, IOError> MoveSync(const nsAString& aSource,
|
||||
const nsAString& aDest, bool noOverwrite);
|
||||
|
||||
/**
|
||||
* Attempts to remove the file located at |aPath|.
|
||||
|
@ -192,8 +202,8 @@ class IOUtils final {
|
|||
*
|
||||
* @return Ok if the file was removed successfully, or an error.
|
||||
*/
|
||||
static Result<Ok, nsresult> RemoveSync(const nsAString& aPath,
|
||||
bool aIgnoreAbsent, bool aRecursive);
|
||||
static Result<Ok, IOError> RemoveSync(const nsAString& aPath,
|
||||
bool aIgnoreAbsent, bool aRecursive);
|
||||
|
||||
/**
|
||||
* Attempts to create a new directory at |aPath|.
|
||||
|
@ -212,10 +222,10 @@ class IOUtils final {
|
|||
*
|
||||
* @return Ok if the directory was created successfully, or an error.
|
||||
*/
|
||||
static Result<Ok, nsresult> CreateDirectorySync(const nsAString& aPath,
|
||||
bool aCreateAncestors,
|
||||
bool aIgnoreExisting,
|
||||
int32_t aMode = 0777);
|
||||
static Result<Ok, IOError> CreateDirectorySync(const nsAString& aPath,
|
||||
bool aCreateAncestors,
|
||||
bool aIgnoreExisting,
|
||||
int32_t aMode = 0777);
|
||||
|
||||
/**
|
||||
* Attempts to stat a file at |aPath|.
|
||||
|
@ -224,24 +234,70 @@ class IOUtils final {
|
|||
*
|
||||
* @return An |InternalFileInfo| struct if successful, or an error.
|
||||
*/
|
||||
static Result<IOUtils::InternalFileInfo, nsresult> StatSync(
|
||||
static Result<IOUtils::InternalFileInfo, IOError> StatSync(
|
||||
const nsAString& aPath);
|
||||
|
||||
using IOReadMozPromise =
|
||||
mozilla::MozPromise<nsTArray<uint8_t>, const nsresult,
|
||||
/* IsExclusive */ true>;
|
||||
using IOReadMozPromise = mozilla::MozPromise<nsTArray<uint8_t>, const IOError,
|
||||
/* IsExclusive */ true>;
|
||||
|
||||
using IOWriteMozPromise =
|
||||
mozilla::MozPromise<uint32_t, const nsresult, /* IsExclusive */ true>;
|
||||
mozilla::MozPromise<uint32_t, const IOError, /* IsExclusive */ true>;
|
||||
|
||||
using IOStatMozPromise =
|
||||
mozilla::MozPromise<struct InternalFileInfo, const nsresult,
|
||||
mozilla::MozPromise<struct InternalFileInfo, const IOError,
|
||||
/* IsExclusive */ true>;
|
||||
|
||||
using IOMozPromise = mozilla::MozPromise<Ok /* ignored */, const nsresult,
|
||||
using IOMozPromise = mozilla::MozPromise<Ok /* ignored */, const IOError,
|
||||
/* IsExclusive */ true>;
|
||||
};
|
||||
|
||||
/**
|
||||
* An error class used with the |Result| type returned by most private |IOUtils|
|
||||
* methods.
|
||||
*/
|
||||
class IOUtils::IOError {
|
||||
public:
|
||||
MOZ_IMPLICIT IOError(nsresult aCode) : mCode(aCode), mMessage(Nothing()) {}
|
||||
|
||||
/**
|
||||
* Replaces the message associated with this error.
|
||||
*/
|
||||
template <typename... Args>
|
||||
IOError WithMessage(const char* const aMessage, Args... aArgs) {
|
||||
mMessage.emplace(nsPrintfCString(aMessage, aArgs...));
|
||||
return *this;
|
||||
}
|
||||
IOError WithMessage(const char* const aMessage) {
|
||||
mMessage.emplace(nsCString(aMessage));
|
||||
return *this;
|
||||
}
|
||||
IOError WithMessage(nsCString aMessage) {
|
||||
mMessage.emplace(aMessage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the |nsresult| associated with this error.
|
||||
*/
|
||||
nsresult Code() const { return mCode; }
|
||||
|
||||
/**
|
||||
* Maybe returns a message associated with this error.
|
||||
*/
|
||||
const Maybe<nsCString>& Message() const { return mMessage; }
|
||||
|
||||
using IOStatMozPromise =
|
||||
mozilla::MozPromise<struct InternalFileInfo, const IOError,
|
||||
/* IsExclusive */ true>;
|
||||
|
||||
using IOMozPromise = mozilla::MozPromise<Ok /* ignored */, const IOError,
|
||||
/* IsExclusive */ true>;
|
||||
|
||||
private:
|
||||
nsresult mCode;
|
||||
Maybe<nsCString> mMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is an easier to work with representation of a |mozilla::dom::FileInfo|
|
||||
* for private use in the IOUtils implementation.
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
const doesNotExist = OS.Path.join(tmpDir, "does_not_exist.tmp");
|
||||
await Assert.rejects(
|
||||
window.IOUtils.read(doesNotExist),
|
||||
/Could not open file/,
|
||||
/Could not open the file at .*/,
|
||||
"IOUtils::read rejects when file does not exist"
|
||||
);
|
||||
});
|
||||
|
@ -56,7 +56,7 @@
|
|||
);
|
||||
await Assert.rejects(
|
||||
window.IOUtils.makeDirectory(newDirectoryName, { ignoreExisting: false }),
|
||||
/Could not create directory because file already exists/,
|
||||
/Could not create directory because it already exists at .*/,
|
||||
"IOUtils::makeDirectory can throw if the target dir exists"
|
||||
)
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
|||
);
|
||||
await Assert.rejects(
|
||||
window.IOUtils.makeDirectory(nestedDirName, { createAncestors: false }),
|
||||
/Target path has missing ancestors/,
|
||||
/Could not create directory at .* because the path has missing ancestor components/,
|
||||
"IOUtils::makeDirectory can fail if the target is missing parents"
|
||||
);
|
||||
ok(!await OS.File.exists(nestedDirName), `Expected ${nestedDirName} not to exist`);
|
||||
|
@ -88,14 +88,14 @@
|
|||
|
||||
await Assert.rejects(
|
||||
window.IOUtils.makeDirectory(notADirFileName, { ignoreExisting: false }),
|
||||
/Target exists and is not a directory/,
|
||||
/Could not create directory because the target file\(.*\) exists and is not a directory/,
|
||||
"IOUtils::makeDirectory [ignoreExisting: false] throws when the target is an existing file"
|
||||
);
|
||||
ok(await fileExists(notADirFileName), `Expected ${notADirFileName} to exist`);
|
||||
|
||||
await Assert.rejects(
|
||||
window.IOUtils.makeDirectory(notADirFileName, { ignoreExisting: true }),
|
||||
/Target exists and is not a directory/,
|
||||
/Could not create directory because the target file\(.*\) exists and is not a directory/,
|
||||
"IOUtils::makeDirectory [ignoreExisting: true] throws when the target is an existing file"
|
||||
);
|
||||
ok(await fileExists(notADirFileName), `Expected ${notADirFileName} to exist`);
|
||||
|
@ -130,7 +130,7 @@
|
|||
|
||||
await Assert.rejects(
|
||||
window.IOUtils.remove(tmpFileName, { ignoreAbsent: false }),
|
||||
/Target file does not exist/,
|
||||
/Could not remove the file at .* because it does not exist/,
|
||||
"IOUtils::remove can throw an error when target file is missing"
|
||||
);
|
||||
ok(!await fileExists(tmpFileName), `Expected file ${tmpFileName} not to exist`);
|
||||
|
@ -156,7 +156,7 @@
|
|||
|
||||
await Assert.rejects(
|
||||
window.IOUtils.remove(tmpParentDir, { recursive: false }),
|
||||
/Could not remove non-empty directory.*/,
|
||||
/Could not remove the non-empty directory at .*/,
|
||||
"IOUtils::remove fails if non-recursively removing directory with contents"
|
||||
);
|
||||
|
||||
|
@ -444,7 +444,7 @@
|
|||
// Test.
|
||||
await Assert.rejects(
|
||||
window.IOUtils.move(tmpFileName, destFileName, { noOverwrite: true }),
|
||||
/Destination file exists and overwrites are not allowed/,
|
||||
/Could not move source file\(.*\) to destination\(.*\) because the destination already exists and overwrites are not allowed/,
|
||||
"IOUtils::move will refuse to move a file if overwrites are disabled"
|
||||
);
|
||||
ok(
|
||||
|
@ -549,7 +549,7 @@
|
|||
// Test.
|
||||
await Assert.rejects(
|
||||
window.IOUtils.move(notExistsSrc, notExistsDest),
|
||||
/Source file does not exist/,
|
||||
/Could not move source file\(.*\) because it does not exist/,
|
||||
"IOUtils::move throws if source file does not exist"
|
||||
);
|
||||
ok(
|
||||
|
@ -566,7 +566,7 @@
|
|||
// Test.
|
||||
await Assert.rejects(
|
||||
window.IOUtils.move(srcDir, destFile),
|
||||
/Source is a directory but destination is not/,
|
||||
/Could not move the source directory\(.*\) to the destination\(.*\) because the destination is not a directory/,
|
||||
"IOUtils::move throws if try to move dir into an existing file"
|
||||
);
|
||||
|
||||
|
@ -627,7 +627,7 @@
|
|||
|
||||
await Assert.rejects(
|
||||
window.IOUtils.stat(notExistsFile),
|
||||
/Target file does not exist/,
|
||||
/Could not stat file\(.*\) because it does not exist/,
|
||||
"IOUtils::stat throws if the target file does not exist"
|
||||
);
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче