mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
Add copyFile/copyFileSync (#863)
This commit is contained in:
parent
eceeabdab2
commit
50a9c2b575
7 changed files with 207 additions and 0 deletions
1
BUILD.gn
1
BUILD.gn
|
@ -101,6 +101,7 @@ ts_sources = [
|
||||||
"js/util.ts",
|
"js/util.ts",
|
||||||
"js/v8_source_maps.ts",
|
"js/v8_source_maps.ts",
|
||||||
"js/write_file.ts",
|
"js/write_file.ts",
|
||||||
|
"js/copy_file.ts",
|
||||||
"tsconfig.json",
|
"tsconfig.json",
|
||||||
|
|
||||||
# Listing package.json and yarn.lock as sources ensures the bundle is rebuilt
|
# Listing package.json and yarn.lock as sources ensures the bundle is rebuilt
|
||||||
|
|
46
js/copy_file.ts
Normal file
46
js/copy_file.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import * as fbs from "gen/msg_generated";
|
||||||
|
import { flatbuffers } from "flatbuffers";
|
||||||
|
import * as dispatch from "./dispatch";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the contents of a file to another by name synchronously.
|
||||||
|
* Creates a new file if target does not exists, and if target exists,
|
||||||
|
* overwrites original content of the target file.
|
||||||
|
* It would also copy the permission of the original file
|
||||||
|
* to the destination.
|
||||||
|
*
|
||||||
|
* import { copyFileSync } from "deno";
|
||||||
|
* copyFileSync("from.txt", "to.txt");
|
||||||
|
*/
|
||||||
|
export function copyFileSync(from: string, to: string): void {
|
||||||
|
dispatch.sendSync(...req(from, to));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the contents of a file to another by name.
|
||||||
|
* Creates a new file if target does not exists, and if target exists,
|
||||||
|
* overwrites original content of the target file.
|
||||||
|
* It would also copy the permission of the original file
|
||||||
|
* to the destination.
|
||||||
|
*
|
||||||
|
* import { copyFile } from "deno";
|
||||||
|
* await copyFile("from.txt", "to.txt");
|
||||||
|
*/
|
||||||
|
export async function copyFile(from: string, to: string): Promise<void> {
|
||||||
|
await dispatch.sendAsync(...req(from, to));
|
||||||
|
}
|
||||||
|
|
||||||
|
function req(
|
||||||
|
from: string,
|
||||||
|
to: string
|
||||||
|
): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] {
|
||||||
|
const builder = new flatbuffers.Builder();
|
||||||
|
const from_ = builder.createString(from);
|
||||||
|
const to_ = builder.createString(to);
|
||||||
|
fbs.CopyFile.startCopyFile(builder);
|
||||||
|
fbs.CopyFile.addFrom(builder, from_);
|
||||||
|
fbs.CopyFile.addTo(builder, to_);
|
||||||
|
const msg = fbs.CopyFile.endCopyFile(builder);
|
||||||
|
return [builder, fbs.Any.CopyFile, msg];
|
||||||
|
}
|
130
js/copy_file_test.ts
Normal file
130
js/copy_file_test.ts
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
import { testPerm, assert, assertEqual } from "./test_util.ts";
|
||||||
|
import * as deno from "deno";
|
||||||
|
|
||||||
|
function readFileString(filename: string): string {
|
||||||
|
const dataRead = deno.readFileSync(filename);
|
||||||
|
const dec = new TextDecoder("utf-8");
|
||||||
|
return dec.decode(dataRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFileString(filename: string, s: string) {
|
||||||
|
const enc = new TextEncoder();
|
||||||
|
const data = enc.encode(s);
|
||||||
|
deno.writeFileSync(filename, data, 0o666);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertSameContent(filename1: string, filename2: string) {
|
||||||
|
const data1 = deno.readFileSync(filename1);
|
||||||
|
const data2 = deno.readFileSync(filename2);
|
||||||
|
assertEqual(data1, data2);
|
||||||
|
}
|
||||||
|
|
||||||
|
testPerm({ write: true }, function copyFileSyncSuccess() {
|
||||||
|
const tempDir = deno.makeTempDirSync();
|
||||||
|
const fromFilename = tempDir + "/from.txt";
|
||||||
|
const toFilename = tempDir + "/to.txt";
|
||||||
|
writeFileString(fromFilename, "Hello world!");
|
||||||
|
deno.copyFileSync(fromFilename, toFilename);
|
||||||
|
// No change to original file
|
||||||
|
assertEqual(readFileString(fromFilename), "Hello world!");
|
||||||
|
// Original == Dest
|
||||||
|
assertSameContent(fromFilename, toFilename);
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: true }, function copyFileSyncFailure() {
|
||||||
|
const tempDir = deno.makeTempDirSync();
|
||||||
|
const fromFilename = tempDir + "/from.txt";
|
||||||
|
const toFilename = tempDir + "/to.txt";
|
||||||
|
// We skip initial writing here, from.txt does not exist
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
deno.copyFileSync(fromFilename, toFilename);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assert(!!err);
|
||||||
|
// Rust deem non-existent path as invalid input
|
||||||
|
assertEqual(err.kind, deno.ErrorKind.InvalidInput);
|
||||||
|
assertEqual(err.name, "InvalidInput");
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: true }, function copyFileSyncOverwrite() {
|
||||||
|
const tempDir = deno.makeTempDirSync();
|
||||||
|
const fromFilename = tempDir + "/from.txt";
|
||||||
|
const toFilename = tempDir + "/to.txt";
|
||||||
|
writeFileString(fromFilename, "Hello world!");
|
||||||
|
// Make Dest exist and have different content
|
||||||
|
writeFileString(toFilename, "Goodbye!");
|
||||||
|
deno.copyFileSync(fromFilename, toFilename);
|
||||||
|
// No change to original file
|
||||||
|
assertEqual(readFileString(fromFilename), "Hello world!");
|
||||||
|
// Original == Dest
|
||||||
|
assertSameContent(fromFilename, toFilename);
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: false }, function copyFileSyncPerm() {
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
deno.copyFileSync("/from.txt", "/to.txt");
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assert(!!err);
|
||||||
|
assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
|
||||||
|
assertEqual(err.name, "PermissionDenied");
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: true }, async function copyFileSuccess() {
|
||||||
|
const tempDir = deno.makeTempDirSync();
|
||||||
|
const fromFilename = tempDir + "/from.txt";
|
||||||
|
const toFilename = tempDir + "/to.txt";
|
||||||
|
writeFileString(fromFilename, "Hello world!");
|
||||||
|
await deno.copyFile(fromFilename, toFilename);
|
||||||
|
// No change to original file
|
||||||
|
assertEqual(readFileString(fromFilename), "Hello world!");
|
||||||
|
// Original == Dest
|
||||||
|
assertSameContent(fromFilename, toFilename);
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: true }, async function copyFileFailure() {
|
||||||
|
const tempDir = deno.makeTempDirSync();
|
||||||
|
const fromFilename = tempDir + "/from.txt";
|
||||||
|
const toFilename = tempDir + "/to.txt";
|
||||||
|
// We skip initial writing here, from.txt does not exist
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await deno.copyFile(fromFilename, toFilename);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assert(!!err);
|
||||||
|
// Rust deem non-existent path as invalid input
|
||||||
|
assertEqual(err.kind, deno.ErrorKind.InvalidInput);
|
||||||
|
assertEqual(err.name, "InvalidInput");
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: true }, async function copyFileOverwrite() {
|
||||||
|
const tempDir = deno.makeTempDirSync();
|
||||||
|
const fromFilename = tempDir + "/from.txt";
|
||||||
|
const toFilename = tempDir + "/to.txt";
|
||||||
|
writeFileString(fromFilename, "Hello world!");
|
||||||
|
// Make Dest exist and have different content
|
||||||
|
writeFileString(toFilename, "Goodbye!");
|
||||||
|
await deno.copyFile(fromFilename, toFilename);
|
||||||
|
// No change to original file
|
||||||
|
assertEqual(readFileString(fromFilename), "Hello world!");
|
||||||
|
// Original == Dest
|
||||||
|
assertSameContent(fromFilename, toFilename);
|
||||||
|
});
|
||||||
|
|
||||||
|
testPerm({ write: false }, async function copyFilePerm() {
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
await deno.copyFile("/from.txt", "/to.txt");
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
assert(!!err);
|
||||||
|
assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
|
||||||
|
assertEqual(err.name, "PermissionDenied");
|
||||||
|
});
|
|
@ -9,6 +9,7 @@ export { makeTempDirSync, makeTempDir } from "./make_temp_dir";
|
||||||
export { removeSync, remove, removeAllSync, removeAll } from "./remove";
|
export { removeSync, remove, removeAllSync, removeAll } from "./remove";
|
||||||
export { renameSync, rename } from "./rename";
|
export { renameSync, rename } from "./rename";
|
||||||
export { readFileSync, readFile } from "./read_file";
|
export { readFileSync, readFile } from "./read_file";
|
||||||
|
export { copyFileSync, copyFile } from "./copy_file";
|
||||||
export { readlinkSync, readlink } from "./read_link";
|
export { readlinkSync, readlink } from "./read_link";
|
||||||
export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat";
|
export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat";
|
||||||
export { symlinkSync, symlink } from "./symlink";
|
export { symlinkSync, symlink } from "./symlink";
|
||||||
|
|
|
@ -8,6 +8,7 @@ import "./os_test.ts";
|
||||||
import "./files_test.ts";
|
import "./files_test.ts";
|
||||||
import "./read_file_test.ts";
|
import "./read_file_test.ts";
|
||||||
import "./write_file_test.ts";
|
import "./write_file_test.ts";
|
||||||
|
import "./copy_file_test.ts";
|
||||||
import "./mkdir_test.ts";
|
import "./mkdir_test.ts";
|
||||||
import "./make_temp_dir_test.ts";
|
import "./make_temp_dir_test.ts";
|
||||||
import "./stat_test.ts";
|
import "./stat_test.ts";
|
||||||
|
|
|
@ -80,6 +80,7 @@ pub fn msg_from_js(
|
||||||
msg::Any::Truncate => handle_truncate,
|
msg::Any::Truncate => handle_truncate,
|
||||||
msg::Any::WriteFile => handle_write_file,
|
msg::Any::WriteFile => handle_write_file,
|
||||||
msg::Any::Exit => handle_exit,
|
msg::Any::Exit => handle_exit,
|
||||||
|
msg::Any::CopyFile => handle_copy_file,
|
||||||
_ => panic!(format!(
|
_ => panic!(format!(
|
||||||
"Unhandled message {}",
|
"Unhandled message {}",
|
||||||
msg::enum_name_any(msg_type)
|
msg::enum_name_any(msg_type)
|
||||||
|
@ -747,6 +748,27 @@ fn handle_read_file(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_copy_file(
|
||||||
|
state: Arc<IsolateState>,
|
||||||
|
base: &msg::Base,
|
||||||
|
data: &'static mut [u8],
|
||||||
|
) -> Box<Op> {
|
||||||
|
assert_eq!(data.len(), 0);
|
||||||
|
let msg = base.msg_as_copy_file().unwrap();
|
||||||
|
let from = PathBuf::from(msg.from().unwrap());
|
||||||
|
let to = PathBuf::from(msg.to().unwrap());
|
||||||
|
|
||||||
|
if !state.flags.allow_write {
|
||||||
|
return odd_future(permission_denied());
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("handle_copy_file {} {}", from.display(), to.display());
|
||||||
|
blocking!(base.sync(), || {
|
||||||
|
fs::copy(&from, &to)?;
|
||||||
|
Ok(empty_buf())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! to_seconds {
|
macro_rules! to_seconds {
|
||||||
($time:expr) => {{
|
($time:expr) => {{
|
||||||
// Unwrap is safe here as if the file is before the unix epoch
|
// Unwrap is safe here as if the file is before the unix epoch
|
||||||
|
|
|
@ -20,6 +20,7 @@ union Any {
|
||||||
ReadFile,
|
ReadFile,
|
||||||
ReadFileRes,
|
ReadFileRes,
|
||||||
WriteFile,
|
WriteFile,
|
||||||
|
CopyFile,
|
||||||
Rename,
|
Rename,
|
||||||
Readlink,
|
Readlink,
|
||||||
ReadlinkRes,
|
ReadlinkRes,
|
||||||
|
@ -214,6 +215,11 @@ table WriteFile {
|
||||||
// perm specified by https://godoc.org/os#FileMode
|
// perm specified by https://godoc.org/os#FileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table CopyFile {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
table Rename {
|
table Rename {
|
||||||
oldpath: string;
|
oldpath: string;
|
||||||
newpath: string;
|
newpath: string;
|
||||||
|
|
Loading…
Add table
Reference in a new issue