Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement second pipe function to 'download' from nextcloud #10

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions source/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
getFolderFileDetails,
getFolderProperties,
checkConnectivity,
downloadToStream,
uploadFromStream,
getWriteStream,
getReadStream,
touchFolder,
Expand Down Expand Up @@ -74,6 +76,8 @@ export class NextcloudClient extends NextcloudClientProperties implements Nextcl
getFolderFileDetails = getFolderFileDetails;
getFolderProperties = getFolderProperties;
checkConnectivity = checkConnectivity;
downloadToStream = downloadToStream;
uploadFromStream = uploadFromStream;
getWriteStream = getWriteStream;
getReadStream = getReadStream;
touchFolder = touchFolder;
Expand Down
4 changes: 3 additions & 1 deletion source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export interface NextcloudClientInterface extends NextcloudClientProperties {
getFolderProperties(path: string, extraProperties?: FileDetailProperty[]): Promise<FolderProperties>;
configureWebdavConnection(options: ConnectionOptions): void;
configureOcsConnection(options: ConnectionOptions): void;
pipeStream(path: string, stream: Stream.Readable): Promise<void>;
pipeStream(path: string, readStream: Stream.Readable): Promise<void>;
uploadFromStream(targetPath: string, readStream: Stream.Readable): Promise<void>;
downloadToStream(sourcePath: string, writeStream: Stream.Writable): Promise<void>;
rename(fromFullPath: string, toFileName: string): Promise<void>;
move(fromFullPath: string, toFullPath: string): Promise<void>;
as(username: string, password: string): NextcloudClientInterface;
Expand Down
48 changes: 36 additions & 12 deletions source/webdav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,22 +187,30 @@ export async function checkConnectivity(): Promise<boolean> {
return true;
}

async function rawPipeStream(sanePath: string, stream: Stream): Promise<void> {
async function rawPipeStream(saneTargetPath: string, readStream: Stream.Readable): Promise<void> {
process.emitWarning('pipeStream has been deprecated and will be removed in version 2, use uploadFromStream or downloadToStream depending on the desired behavior');

const self: NextcloudClientInterface = this;

const writeStream = await rawGetWriteStream.call(self, sanePath);
const writeStream = await rawGetWriteStream.call(self, saneTargetPath);

await new Promise((resolve, reject) => {
stream.on('error', wrapError);
writeStream.on('end', resolve);
writeStream.on('error', wrapError);
await pipeStreams(readStream, writeStream);
}

stream.pipe(writeStream);
async function rawUploadFromStream(saneTargetPath: string, readStream: Stream.Readable): Promise<void> {
const self: NextcloudClientInterface = this;

function wrapError(error) {
reject(NextcloudError(error));
}
});
const writeStream = await rawGetWriteStream.call(self, saneTargetPath);

await pipeStreams(readStream, writeStream);
}

async function rawDownloadToStream(saneSourcePath: string, writeStream: Stream.Writable): Promise<void> {
const self: NextcloudClientInterface = this;

const readStream = await rawGetReadStream.call(self, saneSourcePath);

await pipeStreams(readStream, writeStream);
}

export const createFolderHierarchy = clientFunction(rawCreateFolderHierarchy);
Expand All @@ -211,7 +219,9 @@ export const getFolderProperties = clientFunction(rawGetFolderProperties);
export const getWriteStream = clientFunction(rawGetWriteStream);
export const getReadStream = clientFunction(rawGetReadStream);
export const touchFolder = clientFunction(rawTouchFolder);
export const pipeStream = clientFunction(rawPipeStream);
export const pipeStream = clientFunction(rawPipeStream); // deprecated
export const uploadFromStream = clientFunction(rawUploadFromStream);
export const downloadToStream = clientFunction(rawDownloadToStream);
export const getFiles = clientFunction(rawGetFiles);
export const rename = clientFunction(rawRename);
export const remove = clientFunction(rawRemove);
Expand Down Expand Up @@ -242,3 +252,17 @@ function nextcloudRoot(url, username) {

return `${terminatedUrl}remote.php/dav/files/${username}`;
}

async function pipeStreams(readStream: Stream.Readable, writeStream: Stream.Writable): Promise<void> {
return new Promise((resolve, reject) => {
readStream.on('error', wrapError);
writeStream.on('error', wrapError);
writeStream.on('end', resolve);

readStream.pipe(writeStream);

function wrapError(error) {
reject(NextcloudError(error));
}
});
}
80 changes: 56 additions & 24 deletions tests/webdav-jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,19 @@ describe('Webdav integration', function testWebdavIntegration() {
const path = randomRootPath();
const path2 = randomRootPath();

const nested = `${path}${path2}`;

const stream = new Stream.Readable();

try { await client.get(path); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.getFiles(path); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.put(nested, ''); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.rename(path, path2); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.getReadStream(path); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.getWriteStream(nested); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.pipeStream(nested, stream); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
const nestedPath = `${path}${path2}`;

const readStream = new Stream.Readable();
const writeStream = new Stream.Writable();

try { await client.get(path); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.getFiles(path); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.put(nestedPath, ''); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.rename(path, path2); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.getReadStream(path); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.getWriteStream(nestedPath); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.uploadFromStream(nestedPath, readStream); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
try { await client.downloadToStream(nestedPath, writeStream); } catch (error) { expect(error instanceof NotFoundError).toBeTruthy(); }
});
});

Expand Down Expand Up @@ -421,30 +423,50 @@ describe('Webdav integration', function testWebdavIntegration() {
});
});

describe('pipeStream(path, stream)', () => {
it('should pipe readable streams to the Nextcloud instance', async () => {
describe('uploadFromStream(targetPath, readStream)', () => {
it('should pipe from readable streams to the Nextcloud instance', async () => {
const string = 'test';
const path = randomRootPath();

const stream = getStream(string);
const readStream = getReadStream(string);

await client.pipeStream(path, stream);
await client.uploadFromStream(path, readStream);

expect(await client.get(path)).toBe(string);

await client.remove(path);
});
});

describe('downloadToSream(sourcePath, writeStream)', () => {
it('should pipe into provided writable streams from the Nextcloud instance', async (done) => {
const path = randomRootPath();
const string = 'test';
const readStream = getReadStream(string);
await client.uploadFromStream(path, readStream);

const writeStream = getWriteStream();

writeStream.on('testchunk', (...args) => {
expect(args[0].toJSON()).toEqual({ data: [116, 101, 115, 116], type: 'Buffer' });
done();
});

await client.downloadToStream(path, writeStream);

await client.remove(path);
});
});

describe('Path reservation', () => {
it('should allow saving a file with empty contents, then getting a write stream for it immediately', async () => {
const path = randomRootPath();

await client.put(path, '');

const writeStream : Request = await client.getWriteStream(path);
const writeStream: Request = await client.getWriteStream(path);

const writtenStream = getStream('test');
const writtenStream = getReadStream('test');

const completionPromise = new Promise((resolve, reject) => {
writeStream.on('end', resolve);
Expand Down Expand Up @@ -1030,14 +1052,24 @@ function randomRootPath(): string {
return `/${Math.floor(Math.random() * 1000000000)}`;
}

function getStream(string): Stream.Readable {
let stream = new Stream.Readable();
function getReadStream(string): Stream.Readable {
let readStream = new Stream.Readable();

readStream._read = () => {};

readStream.push(string);
readStream.push(null);

return readStream;
}

// See https://stackoverflow.com/questions/12755997/how-to-create-streams-from-string-in-node-js
stream._read = () => {};
function getWriteStream(): Stream.Writable {
let writeStream = new Stream.Writable();

stream.push(string);
stream.push(null);
writeStream._write = (chunk, _, done) => {
writeStream.emit('testchunk', chunk);
done();
};

return stream;
return writeStream;
}