nextcloud-node-client
Version:
Nextcloud client API for node.js TypeScript applications
1,105 lines (1,098 loc) • 119 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandStatus = exports.FileSystemFolder = exports.UploadFolderCommand = exports.UploadFilesCommand = exports.GetFilesRecursivelyCommand = exports.UserProperty = exports.UserGroup = exports.User = exports.RequestResponseLogEntry = exports.RequestResponseLog = exports.SharePermission = exports.Share = exports.Server = exports.FakeServer = exports.Tag = exports.FileSystemElement = exports.File = exports.Folder = exports.Environment = exports.ClientError = exports.Client = exports.UserGroupDoesNotExistError = exports.UserGroupDeletionFailedError = exports.UserGroupAlreadyExistsError = exports.UserUpdateError = exports.UserResendWelcomeEmailError = exports.UserCreateError = exports.UserAlreadyExistsError = exports.UserNotFoundError = exports.QueryOffsetError = exports.QueryLimitError = exports.OperationFailedError = exports.InsufficientPrivilegesError = exports.InvalidServiceResponseFormatError = exports.CommandAlreadyExecutedError = void 0;
// this must be the first
const dotenv_1 = require("dotenv");
dotenv_1.config();
const uploadFilesCommand_1 = __importDefault(require("./uploadFilesCommand"));
exports.UploadFilesCommand = uploadFilesCommand_1.default;
const uploadFolderCommand_1 = __importDefault(require("./uploadFolderCommand"));
exports.UploadFolderCommand = uploadFolderCommand_1.default;
const getFilesRecursivelyCommand_1 = __importDefault(require("./getFilesRecursivelyCommand"));
exports.GetFilesRecursivelyCommand = getFilesRecursivelyCommand_1.default;
const command_1 = require("./command");
Object.defineProperty(exports, "CommandStatus", { enumerable: true, get: function () { return command_1.CommandStatus; } });
const debug_1 = __importDefault(require("debug"));
const fast_xml_parser_1 = __importDefault(require("fast-xml-parser"));
const node_fetch_1 = require("node-fetch");
const path_1 = __importStar(require("path"));
const environment_1 = __importDefault(require("./environment"));
exports.Environment = environment_1.default;
const environmentVcapServices_1 = __importDefault(require("./environmentVcapServices"));
const error_1 = __importStar(require("./error"));
exports.ClientError = error_1.default;
Object.defineProperty(exports, "CommandAlreadyExecutedError", { enumerable: true, get: function () { return error_1.CommandAlreadyExecutedError; } });
Object.defineProperty(exports, "QueryLimitError", { enumerable: true, get: function () { return error_1.QueryLimitError; } });
Object.defineProperty(exports, "QueryOffsetError", { enumerable: true, get: function () { return error_1.QueryOffsetError; } });
Object.defineProperty(exports, "InsufficientPrivilegesError", { enumerable: true, get: function () { return error_1.InsufficientPrivilegesError; } });
Object.defineProperty(exports, "InvalidServiceResponseFormatError", { enumerable: true, get: function () { return error_1.InvalidServiceResponseFormatError; } });
Object.defineProperty(exports, "OperationFailedError", { enumerable: true, get: function () { return error_1.OperationFailedError; } });
Object.defineProperty(exports, "UserGroupAlreadyExistsError", { enumerable: true, get: function () { return error_1.UserGroupAlreadyExistsError; } });
Object.defineProperty(exports, "UserGroupDeletionFailedError", { enumerable: true, get: function () { return error_1.UserGroupDeletionFailedError; } });
Object.defineProperty(exports, "UserResendWelcomeEmailError", { enumerable: true, get: function () { return error_1.UserResendWelcomeEmailError; } });
Object.defineProperty(exports, "UserGroupDoesNotExistError", { enumerable: true, get: function () { return error_1.UserGroupDoesNotExistError; } });
Object.defineProperty(exports, "UserNotFoundError", { enumerable: true, get: function () { return error_1.UserNotFoundError; } });
Object.defineProperty(exports, "UserAlreadyExistsError", { enumerable: true, get: function () { return error_1.UserAlreadyExistsError; } });
Object.defineProperty(exports, "UserCreateError", { enumerable: true, get: function () { return error_1.UserCreateError; } });
Object.defineProperty(exports, "UserUpdateError", { enumerable: true, get: function () { return error_1.UserUpdateError; } });
const fakeServer_1 = __importDefault(require("./fakeServer"));
exports.FakeServer = fakeServer_1.default;
const file_1 = __importDefault(require("./file"));
exports.File = file_1.default;
const fileSystemElement_1 = __importDefault(require("./fileSystemElement"));
exports.FileSystemElement = fileSystemElement_1.default;
const fileSystemFolder_1 = __importDefault(require("./fileSystemFolder"));
exports.FileSystemFolder = fileSystemFolder_1.default;
const folder_1 = __importDefault(require("./folder"));
exports.Folder = folder_1.default;
const httpClient_1 = require("./httpClient");
const requestResponseLog_1 = __importDefault(require("./requestResponseLog"));
exports.RequestResponseLog = requestResponseLog_1.default;
const requestResponseLogEntry_1 = __importDefault(require("./requestResponseLogEntry"));
exports.RequestResponseLogEntry = requestResponseLogEntry_1.default;
const server_1 = __importDefault(require("./server"));
exports.Server = server_1.default;
const share_1 = __importStar(require("./share"));
exports.Share = share_1.default;
Object.defineProperty(exports, "SharePermission", { enumerable: true, get: function () { return share_1.SharePermission; } });
const tag_1 = __importDefault(require("./tag"));
exports.Tag = tag_1.default;
const userGroup_1 = __importDefault(require("./userGroup"));
exports.UserGroup = userGroup_1.default;
const user_1 = __importStar(require("./user"));
exports.User = user_1.default;
Object.defineProperty(exports, "UserProperty", { enumerable: true, get: function () { return user_1.UserProperty; } });
const debug = debug_1.default("NCClient");
/**
* The nextcloud client is the root object to access the remote api of the nextcloud server.<br>
*/
class Client {
/**
* Creates a new instance of a nextcloud client.<br/>
* Use the server to provide server connectivity information to the client.<br/>
* (The FakeServer is only used for testing and code coverage)<br/><br/>
* If the server is not provided the client tries to find the connectivity information
* in the environment.<br/>
* If a <b>VCAP_SERVICES</b> environment variable is available, the client tries to find
* a service with the name <b>"nextcloud"</b> in the user-provides-services section.<br/>
* If no VCAP_SERVICES are available, the client uses the following variables
* from the envirnonment for the connectivity:<br/>
* <ul>
* <li>NEXTCLOUD_URL - the WebDAV url of the nextcloud server</li>
* <li>NEXTCLOUD_USERNAME - the user name</li>
* <li>NEXTCLOUD_PASSWORD - the application password</li>
* </ul>
* @param server optional server information to connection to a nextcloud server
* @constructor
*/
constructor(server) {
this.logRequestResponse = false;
debug("constructor");
this.nextcloudOrigin = "";
this.nextcloudAuthHeader = "";
this.nextcloudRequestToken = "";
this.webDAVUrl = "";
this.userId = "";
// if no server is provided, try to get a server from VCAP_S environment "nextcloud" instance
// If no VCAP_S environment exists try from environment
if (!server) {
try {
const env = new environmentVcapServices_1.default("nextcloud");
server = env.getServer();
}
catch (e) {
const env = new environment_1.default();
server = env.getServer();
}
}
if (server instanceof server_1.default) {
this.proxy = server.proxy;
debug("constructor: webdav url %s", server.url);
if (server.url.indexOf(Client.webDavUrlPath) === -1) {
// not a valid nextcloud url
throw new error_1.default(`The provided nextcloud url "${server.url}" does not comply to the nextcloud url standard, "${Client.webDavUrlPath}" is missing`, "ERR_INVALID_NEXTCLOUD_WEBDAV_URL");
}
this.nextcloudOrigin = server.url.substr(0, server.url.indexOf(Client.webDavUrlPath));
debug("constructor: nextcloud url %s", this.nextcloudOrigin);
this.userId = server.basicAuth.username;
this.nextcloudAuthHeader = "Basic " + Buffer.from(server.basicAuth.username + ":" + server.basicAuth.password).toString("base64");
this.nextcloudRequestToken = "";
if (server.url.slice(-1) === "/") {
this.webDAVUrl = server.url.slice(0, -1);
}
else {
this.webDAVUrl = server.url;
}
this.logRequestResponse = server.logRequestResponse;
const options = {
authorizationHeader: this.nextcloudAuthHeader,
logRequestResponse: this.logRequestResponse,
origin: this.nextcloudOrigin,
proxy: this.proxy,
};
this.httpClient = new httpClient_1.HttpClient(options);
}
if (server instanceof fakeServer_1.default) {
this.fakeServer = server;
this.webDAVUrl = "https://fake.server" + Client.webDavUrlPath;
}
}
/**
* returns the used and free quota of the nextcloud account
*/
getQuota() {
return __awaiter(this, void 0, void 0, function* () {
debug("getQuota");
const requestInit = {
method: "PROPFIND",
};
const response = yield this.getHttpResponse(this.webDAVUrl + "/", requestInit, [207], { description: "Client get quota" });
const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, Client.webDavUrlPath + "/");
let quota = null;
for (const prop of properties) {
if (prop["quota-available-bytes"]) {
quota = {
available: "unlimited",
used: prop["quota-used-bytes"],
};
if (prop["quota-available-bytes"] > 0) {
quota.available = prop["quota-available-bytes"];
}
}
}
if (!quota) {
debug("Error, quota not available: %s ", JSON.stringify(properties, null, 4));
throw new error_1.default(`Error, quota not available`, "ERR_QUOTA_NOT_AVAILABLE");
}
debug("getQuota = %O", quota);
return quota;
});
}
// ***************************************************************************************
// tags
// ***************************************************************************************
/**
* creates a new tag, if not already existing
* this function will fail with http 403 if the user does not have admin privileges
* @param tagName the name of the tag
* @returns tagId
*/
createTag(tagName) {
return __awaiter(this, void 0, void 0, function* () {
debug("createTag");
let tag;
// is the tag already existing?
tag = yield this.getTagByName(tagName);
if (tag) {
return tag;
}
// tag does not exist, create tag
const requestInit = {
body: `{ "name": "${tagName}", "userVisible": true, "userAssignable": true, "canAssign": true }`,
headers: new node_fetch_1.Headers({ "Content-Type": "application/json" }),
method: "POST",
};
const response = yield this.getHttpResponse(this.nextcloudOrigin + "/remote.php/dav/systemtags/", requestInit, [201], { description: "Tag create" });
const tagString = response.headers.get("Content-Location");
debug("createTag new tagId %s, tagName %s", tagString, tagName);
if (tagString === "" || tagString === null) {
throw new error_1.default(`Error, tag with name '${tagName}' could not be created`, "ERR_TAG_CREATE_FAILED");
}
// the number id of the tag is the last element in the id (path)
const tagId = this.getTagIdFromHref(tagString);
tag = new tag_1.default(this, tagId, tagName, true, true, true);
return tag;
});
}
/**
* returns a tag identified by the name or null if not found
* @param tagName the name of the tag
* @returns tag or null
*/
getTagByName(tagName) {
return __awaiter(this, void 0, void 0, function* () {
debug("getTag");
const tags = yield this.getTags();
for (const tag of tags) {
if (tag.name === tagName) {
return tag;
}
}
return null;
});
}
/**
* returns a tag identified by the id or null if not found
* @param tagId the id of the tag
* @returns tag or null
*/
getTagById(tagId) {
return __awaiter(this, void 0, void 0, function* () {
debug("getTagById");
const tags = yield this.getTags();
for (const tag of tags) {
if (tag.id === tagId) {
return tag;
}
}
return null;
});
}
/**
* deletes the tag by id
* this function will fail with http 403 if the user does not have admin privileges
* @param tagId the id of the tag like "/remote.php/dav/systemtags/234"
*/
deleteTag(tagId) {
return __awaiter(this, void 0, void 0, function* () {
debug("deleteTag tagId: $s", tagId);
const requestInit = {
method: "DELETE",
};
const response = yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/systemtags/${tagId}`, requestInit, [204, 404], { description: "Tag delete" });
});
}
/**
* deletes all visible assignable tags
* @throws Error
*/
deleteAllTags() {
return __awaiter(this, void 0, void 0, function* () {
debug("deleteAllTags");
const tags = yield this.getTags();
for (const tag of tags) {
// debug("deleteAllTags tag: %O", tag);
yield tag.delete();
}
});
}
/**
* returns a list of tags
* @returns array of tags
*/
getTags() {
return __awaiter(this, void 0, void 0, function* () {
debug("getTags PROPFIND %s", this.nextcloudOrigin + "/remote.php/dav/systemtags/");
const requestInit = {
body: `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:id />
<oc:display-name />
<oc:user-visible />
<oc:user-assignable />
<oc:can-assign />
</d:prop>
</d:propfind>`,
method: "PROPFIND",
};
const relUrl = `/remote.php/dav/systemtags/`;
const response = yield this.getHttpResponse(this.nextcloudOrigin + relUrl, requestInit, [207], { description: "Tags get" });
const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, relUrl + "/*");
const tags = [];
for (const prop of properties) {
tags.push(new tag_1.default(this, this.getTagIdFromHref(prop._href), prop["display-name"], prop["user-visible"], prop["user-assignable"], prop["can-assign"]));
}
return tags;
});
}
/**
* returns the list of tag names and the tag ids
* @param fileId the id of the file
*/
getTagsOfFile(fileId) {
return __awaiter(this, void 0, void 0, function* () {
debug("getTagsOfFile");
const requestInit = {
body: `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<oc:id />
<oc:display-name />
<oc:user-visible />
<oc:user-assignable />
<oc:can-assign />
</d:prop>
</d:propfind>`,
method: "PROPFIND",
};
const relUrl = `/remote.php/dav/systemtags-relations/files/${fileId}`;
const response = yield this.getHttpResponse(`${this.nextcloudOrigin}${relUrl}`, requestInit, [207], { description: "File get tags" });
const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, relUrl + "/*");
const tagMap = new Map();
for (const prop of properties) {
tagMap.set(prop["display-name"], prop.id);
}
debug("tags of file %O", tagMap);
return tagMap;
});
}
/**
* removes the tag from the file
* @param fileId the file id
* @param tagId the tag id
*/
removeTagOfFile(fileId, tagId) {
return __awaiter(this, void 0, void 0, function* () {
debug("removeTagOfFile tagId: $s fileId:", tagId, fileId);
const requestInit = {
method: "DELETE",
};
const response = yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/systemtags-relations/files/${fileId}/${tagId}`, requestInit, [204, 404], { description: "File remove tag" });
return;
});
}
/**
* returns the id of the file or -1 of not found
* @returns id of the file or -1 if not found
*/
getFileId(fileUrl) {
return __awaiter(this, void 0, void 0, function* () {
debug("getFileId");
const requestInit = {
body: `
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
<d:prop>
<oc:fileid />
</d:prop>
</d:propfind>`,
method: "PROPFIND",
};
const response = yield this.getHttpResponse(fileUrl, requestInit, [207], { description: "File get id" });
const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, "");
for (const prop of properties) {
if (prop.fileid) {
return prop.fileid;
}
}
debug("getFileId no file id found for %s", fileUrl);
return -1;
});
}
getFolderContents(folderName) {
return __awaiter(this, void 0, void 0, function* () {
debug("getFolderContents");
const requestInit = {
body: `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns" xmlns:ocs="http://open-collaboration-services.org/ns">
<d:prop>
<d:getlastmodified />
<d:getetag />
<d:getcontenttype />
<d:resourcetype />
<oc:fileid />
<oc:permissions />
<oc:size />
<d:getcontentlength />
<nc:has-preview />
<nc:mount-type />
<nc:is-encrypted />
<ocs:share-permissions />
<oc:tags />
<oc:favorite />
<oc:comments-unread />
<oc:owner-id />
<oc:owner-display-name />
<oc:share-types />
</d:prop>
</d:propfind>`,
method: "PROPFIND",
};
const url = `${this.webDAVUrl}${folderName}`;
const response = yield this.getHttpResponse(url, requestInit, [207], { description: "Folder get contents" });
const folderContents = [];
let properties;
try {
properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, "");
}
catch (e) {
return folderContents;
}
// tslint:disable-next-line:no-empty
for (const prop of properties) {
let fileName = decodeURI(prop._href.substr(prop._href.indexOf(Client.webDavUrlPath) + 18));
if (fileName.endsWith("/")) {
fileName = fileName.slice(0, -1);
}
if ((url + "/").endsWith(decodeURI(prop._href))) {
continue;
}
const folderContentsEntry = {};
folderContentsEntry.lastmod = prop.getlastmodified;
folderContentsEntry.fileid = prop.fileid;
folderContentsEntry.basename = fileName.split("/").reverse()[0];
folderContentsEntry.filename = fileName;
if (prop.getcontenttype) {
folderContentsEntry.mime = prop.getcontenttype;
folderContentsEntry.size = prop.getcontentlength;
folderContentsEntry.type = "file";
}
else {
folderContentsEntry.type = "directory";
}
if (folderContentsEntry.basename !== "") {
folderContents.push(folderContentsEntry);
}
}
// debug("folderContentsEntry $s", JSON.stringify(folderContents, null, 4));
return folderContents;
});
}
/**
* creates a folder and all parent folders in the path if they do not exist
* @param folderName name of the folder /folder/subfolder/subfolder
* @returns a folder object
*/
createFolder(folderName) {
return __awaiter(this, void 0, void 0, function* () {
folderName = this.sanitizeFolderName(folderName);
debug("createFolder: folderName=%s", folderName);
const parts1 = folderName.split("/");
for (const p of parts1) {
if ((p) === "." || p === "..") {
throw new error_1.default(`Error creating folder, folder name "${folderName}" invalid`, "ERR_CREATE_FOLDER_INVALID_FOLDER_NAME");
}
}
let folder;
folder = yield this.getFolder(folderName);
if (folder) {
debug("createFolder: folder already available %O", folder.name);
return folder;
}
else {
// try to do a simple create with the complete path
try {
debug("createFolder: folder = %s", folderName);
yield this.createFolderInternal(folderName);
}
catch (e) {
// create all folders in the path
const parts = folderName.split("/");
parts.shift();
let folderPath = "";
debug("createFolder: parts = %O", parts);
for (const part of parts) {
debug("createFolder: part = %O", part);
folderPath += "/" + part;
folder = yield this.getFolder(folderPath);
if (folder === null) {
debug("createFolder: folder not available");
// folder not available
debug("createFolder: folder = %s", folderPath);
yield this.createFolderInternal(folderPath);
}
else {
debug("createFolder: folder already available %s", folderPath);
}
}
}
}
folder = yield this.getFolder(folderName);
if (folder) {
debug("createFolder: new folder %O", folder.name);
return folder;
}
else {
throw new error_1.default(`Error creating folder, folder name "${folderName}"
`, "ERR_CREATE_FOLDER_FAILED");
}
});
}
/**
* deletes a file
* @param fileName name of folder "/f1/f2/f3/x.txt"
*/
deleteFile(fileName) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.webDAVUrl + fileName;
debug("deleteFile %s", url);
const requestInit = {
method: "DELETE",
};
try {
yield this.getHttpResponse(url, requestInit, [204], { description: "File delete" });
}
catch (err) {
debug("Error in deleteFile %s %s %s", err.message, requestInit.method, url);
throw err;
}
});
}
/**
* deletes a folder
* @param folderName name of folder "/f1/f2/f3"
*/
deleteFolder(folderName) {
return __awaiter(this, void 0, void 0, function* () {
folderName = this.sanitizeFolderName(folderName);
debug("deleteFolder:");
const folder = yield this.getFolder(folderName);
if (folder) {
yield this.deleteFile(folderName);
}
});
}
/**
* get the root folder object
* @returns {Promise<Folder>} the root folder
*/
getRootFolder() {
return new folder_1.default(this, "/", "", "");
}
/**
* returns an array of file system objects that have all given tags assigned (AND)
* @param {Tag[]} tags array of tags
* @async
* @returns {Promise<FileSystemElement[]>} returns an array of file system objects
*/
getFileSystemElementByTags(tags) {
return __awaiter(this, void 0, void 0, function* () {
debug("getFileSystemElementByTags %s", tags.join(", "));
let filterRule = "";
for (const tag of tags) {
filterRule += `<oc:systemtag>${tag.id}</oc:systemtag>`;
}
const urlSuffix = `/remote.php/dav/files/`;
const url = `${this.nextcloudOrigin}${urlSuffix}${this.userId}`;
const body = `<?xml version="1.0"?>
<oc:filter-files xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\" xmlns:nc=\"http://nextcloud.org/ns\" xmlns:ocs=\"http://open-collaboration-services.org/ns\">
<d:prop>
<d:getcontenttype />
<oc:fileid />
</d:prop>
<oc:filter-rules>
${filterRule}
</oc:filter-rules>
</oc:filter-files>`;
const requestInit = {
body,
// headers: new Headers({ Depth: "0" }),
method: "REPORT",
};
let response;
try {
response = yield this.getHttpResponse(url, requestInit, [207], { description: "Get FileSystemElements by tags" });
}
catch (err) {
debug("Error in stat %s %s %s %s", err.message, requestInit.method, url);
throw err;
}
const result = [];
let properties = [];
try {
properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, "");
}
catch (e) {
return result;
}
for (const prop of properties) {
let fse = null;
let name = prop._href;
// remove the first two elements from the path
name = name.replace(urlSuffix, "");
const a = name.split("/");
a.shift();
name = "/" + a.join("/");
// console.log(name);
if (prop.getcontenttype) {
fse = yield this.getFile(name);
}
else {
fse = yield this.getFolder(name);
}
result.push(fse);
}
return result;
});
}
/**
* get a folder object from a path string
* @param {string} folderName Name of the folder like "/company/branches/germany"
* @returns {Promise<Folder | null>} null if the folder does not exist or an folder object
*/
getFolder(folderName) {
return __awaiter(this, void 0, void 0, function* () {
folderName = this.sanitizeFolderName(folderName);
debug("getFolder %s", folderName);
// return root folder
if (folderName === "/" || folderName === "") {
return this.getRootFolder();
}
try {
const stat = yield this.stat(folderName);
debug(": SUCCESS!!");
if (stat.type !== "file") {
return new folder_1.default(this, stat.filename.replace(/\\/g, "/"), stat.basename, stat.lastmod, stat.fileid);
}
else {
debug("getFolder: found object is file not a folder");
return null;
}
}
catch (e) {
debug("getFolder: exception occurred calling stat %O", e.message);
return null;
}
});
}
/**
* get a array of folders from a folder path string
* @param folderName Name of the folder like "/company/branches/germany"
* @returns array of folder objects
*/
getSubFolders(folderName) {
return __awaiter(this, void 0, void 0, function* () {
debug("getSubFolders: folder %s", folderName);
const folders = [];
folderName = this.sanitizeFolderName(folderName);
const folderElements = yield this.Contents(folderName, true);
for (const folderElement of folderElements) {
debug("getSubFolders: adding subfolders %s", folderElement.filename);
folders.push(new folder_1.default(this, folderElement.filename.replace(/\\/g, "/"), folderElement.basename, folderElement.lastmod, folderElement.fileid));
}
return folders;
});
}
/**
* get files of a folder
* @param {string} folderName Name of the folder like "/company/branches/germany"
* @param {FolderGetFilesOptions} options options for filtering and paging
* @returns array of file objects
*/
getFiles(folderName, options) {
return __awaiter(this, void 0, void 0, function* () {
debug("getFiles: folder %s", folderName);
const files = [];
folderName = this.sanitizeFolderName(folderName);
const fileElements = yield this.Contents(folderName, false);
for (const folderElement of fileElements) {
debug("getFiles: adding file %s", folderElement.filename);
// debug("getFiles: adding file %O", folderElement);
let file = new file_1.default(this, folderElement.filename.replace(/\\/g, "/"), folderElement.basename, folderElement.lastmod, folderElement.size, folderElement.mime, folderElement.fileid);
if (options && options.filterFile) {
file = options.filterFile(file);
}
if (file) {
files.push(file);
}
}
return files;
});
}
/**
* create a new file of overwrites an existing file
* @param fileName the file name /folder1/folder2/filename.txt
* @param data the buffer object
*/
createFile(fileName, data) {
return __awaiter(this, void 0, void 0, function* () {
if (fileName.startsWith("./")) {
fileName = fileName.replace("./", "/");
}
const baseName = path_1.default.basename(fileName);
const folderName = path_1.default.dirname(fileName);
debug("createFile folder name %s base name %s", folderName, baseName);
// ensure that we have a folder
yield this.createFolder(folderName);
yield this.putFileContents(fileName, data);
let file;
file = yield this.getFile(fileName);
if (!file) {
throw new error_1.default(`Error creating file, file name "${fileName}"`, "ERR_CREATE_FILE_FAILED");
}
return file;
});
}
/**
* returns a nextcloud file object
* @param fileName the full file name /folder1/folder2/file.pdf
*/
getFile(fileName) {
return __awaiter(this, void 0, void 0, function* () {
debug("getFile fileName = %s", fileName);
try {
const stat = yield this.stat(fileName);
debug(": SUCCESS!!");
if (stat.type === "file") {
return new file_1.default(this, stat.filename.replace(/\\/g, "/"), stat.basename, stat.lastmod, stat.size, stat.mime || "", stat.fileid || -1);
}
else {
debug("getFile: found object is a folder not a file");
return null;
}
}
catch (e) {
debug("getFile: exception occurred calling stat %O", e.message);
return null;
}
});
}
/**
* renames the file or moves it to an other location
* @param sourceFileName source file name
* @param targetFileName target file name
*/
moveFile(sourceFileName, targetFileName) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.webDAVUrl + sourceFileName;
const destinationUrl = this.webDAVUrl + targetFileName;
debug("moveFile from '%s' to '%s'", url, destinationUrl);
const requestInit = {
headers: new node_fetch_1.Headers({ Destination: destinationUrl }),
method: "MOVE",
};
try {
yield this.getHttpResponse(url, requestInit, [201], { description: "File move" });
}
catch (err) {
debug("Error in move file %s %s source: %s destination: %s", err.message, requestInit.method, url, destinationUrl);
throw new error_1.default("Error: moving file failed: source=" + sourceFileName + " target=" + targetFileName + " - " + err.message, "ERR_FILE_MOVE_FAILED");
}
const targetFile = yield this.getFile(targetFileName);
if (!targetFile) {
throw new error_1.default("Error: moving file failed: source=" + sourceFileName + " target=" + targetFileName, "ERR_FILE_MOVE_FAILED");
}
return targetFile;
});
}
/**
* renames the folder or moves it to an other location
* @param sourceFolderName source folder name
* @param tarName target folder name
*/
moveFolder(sourceFolderName, tarName) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.webDAVUrl + sourceFolderName;
const destinationUrl = this.webDAVUrl + tarName;
debug("moveFolder from '%s' to '%s'", url, destinationUrl);
const requestInit = {
headers: new node_fetch_1.Headers({ Destination: destinationUrl }),
method: "MOVE",
};
try {
yield this.getHttpResponse(url, requestInit, [201], { description: "Folder move" });
}
catch (err) {
debug("Error in move folder %s %s source: %s destination: %s", err.message, requestInit.method, url, destinationUrl);
throw new error_1.default("Error: moving folder failed: source=" + sourceFolderName + " target=" + tarName + " - " + err.message, "ERR_FOLDER_MOVE_FAILED");
}
const tar = yield this.getFolder(tarName);
if (!tar) {
throw new error_1.default("Error: moving folder failed: source=" + sourceFolderName + " target=" + tarName, "ERR_FOLDER_MOVE_FAILED");
}
return tar;
});
}
/**
* returns the content of a file
* @param fileName name of the file /d1/file1.txt
* @returns Buffer with file content
*/
getContent(fileName) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.webDAVUrl + fileName;
debug("getContent GET %s", url);
const requestInit = {
method: "GET",
};
let response;
try {
response = yield this.getHttpResponse(url, requestInit, [200], { description: "File get content" });
}
catch (err) {
debug("Error getContent %s - error %s", url, err.message);
throw err;
}
return Buffer.from(yield response.buffer());
});
}
/**
* returns the content of a file
* @param fileName name of the file /d1/file1.txt
* @returns Buffer with file content
*/
pipeContentStream(fileName, destination) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.webDAVUrl + fileName;
debug("getContent GET %s", url);
const requestInit = {
method: "GET",
};
let response;
try {
response = yield this.getHttpResponse(url, requestInit, [200], { description: "File pipe content stream" });
}
catch (err) {
debug("Error getContent %s - error %s", url, err.message);
throw err;
}
response.body.pipe(destination);
});
}
/**
* returns the link to a file for downloading
* @param fileName name of the file /folder1/folder1.txt
* @returns url
*/
getLink(fileName) {
debug("getLink of %s", fileName);
return this.webDAVUrl + fileName;
}
/**
* returns the url to the file in the nextcloud UI
* @param fileId the id of the file
*/
getUILink(fileId) {
debug("getUILink of %s", fileId);
return `${this.nextcloudOrigin}/apps/files/?fileid=${fileId}`;
}
/**
* adds a tag to a file or folder
* if the tag does not exist, it is automatically created
* if the tag is created, the user must have damin privileges
* @param fileId the id of the file
* @param tagName the name of the tag
* @returns nothing
* @throws Error
*/
addTagToFile(fileId, tagName) {
return __awaiter(this, void 0, void 0, function* () {
debug("addTagToFile file:%s tag:%s", fileId, tagName);
const tag = yield this.createTag(tagName);
if (!tag.canAssign) {
throw new error_1.default(`Error: No permission to assign tag "${tagName}" to file. Tag is not assignable`, "ERR_TAG_NOT_ASSIGNABLE");
}
const addTagBody = {
canAssign: tag.canAssign,
id: tag.id,
name: tag.name,
userAssignable: tag.assignable,
userVisible: tag.visible,
};
const requestInit = {
body: JSON.stringify(addTagBody, null, 4),
headers: new node_fetch_1.Headers({ "Content-Type": "application/json" }),
method: "PUT",
};
yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/systemtags-relations/files/${fileId}/${tag.id}`, requestInit, [201, 409], { description: "File add tag" }); // created or conflict
});
}
// ***************************************************************************************
// activity
// ***************************************************************************************
/*
@todo to be refactored to eventing
public async getActivities(): Promise<string[]> {
const result: string[] = [];
const requestInit: RequestInit = {
headers: new Headers({ "ocs-apirequest": "true" }),
method: "GET",
};
const response: Response = await this.getHttpResponse(
this.nextcloudOrigin + "/ocs/v2.php/apps/activity/api/v2/activity/files?format=json&previews=false&since=97533",
requestInit,
[200],
{ description: "Activities get" });
const responseObject: any = await response.json();
// @todo
for (const res of responseObject.ocs.data) {
debug(JSON.stringify({
acivityId: res.activity_id,
objects: res.objects,
type: res.type,
}, null, 4));
}
// debug("getActivities: responseObject %s", JSON.stringify(responseObject, null, 4));
return result;
}
*/
// ***************************************************************************************
// comments
// ***************************************************************************************
/**
* adds a comment to a file
* @param fileId the id of the file
* @param comment the comment to be added to the file
*/
addCommentToFile(fileId, comment) {
return __awaiter(this, void 0, void 0, function* () {
debug("addCommentToFile file:%s comment:%s", fileId, comment);
const addCommentBody = {
actorType: "users",
message: comment,
objectType: "files",
verb: "comment",
};
const requestInit = {
body: JSON.stringify(addCommentBody, null, 4),
headers: new node_fetch_1.Headers({ "Content-Type": "application/json" }),
method: "POST",
};
yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/comments/files/${fileId}`, requestInit, [201], { description: "File add comment" }); // created
});
}
/**
* returns comments of a file / folder
* @param fileId the id of the file / folder
* @param top number of comments to return
* @param skip the offset
* @returns array of comment strings
* @throws Exception
*/
getFileComments(fileId, top, skip) {
return __awaiter(this, void 0, void 0, function* () {
debug("getFileComments fileId:%s", fileId);
if (!top) {
top = 30;
}
if (!skip) {
skip = 0;
}
const requestInit = {
body: `<?xml version="1.0" encoding="utf-8" ?>
<oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">
<oc:limit>${top}</oc:limit>
<oc:offset>${skip}</oc:offset>
</oc:filter-comments>`,
method: "REPORT",
};
const response = yield this.getHttpResponse(`${this.nextcloudOrigin}/remote.php/dav/comments/files/${fileId}`, requestInit, [207], { description: "File get comments" });
const properties = yield this.getPropertiesFromWebDAVMultistatusResponse(response, "");
const comments = [];
for (const prop of properties) {
comments.push(prop.message);
}
return comments;
});
}
/**
* returns system information about the nextcloud server and the nextcloud client
*/
getSystemInfo() {
return __awaiter(this, void 0, void 0, function* () {
const requestInit = {
headers: this.getOcsHeaders(),
method: "GET",
};
const response = yield this.getHttpResponse(this.nextcloudOrigin + "/ocs/v2.php/apps/serverinfo/api/v1/info", requestInit, [200], { description: "SystemInfo get" });
const rawResult = yield response.json();
// validate the raw result
let system = {};
let storage = {};
let shares = {};
let server = {};
let activeUsers = {};
if (rawResult && rawResult.ocs && rawResult.ocs.data) {
if (rawResult.ocs.data.nextcloud) {
if (rawResult.ocs.data.nextcloud.system) {
system = rawResult.ocs.data.nextcloud.system;
}
else {
throw new error_1.default("Fatal Error: nextcloud data.nextcloud.system missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
if (rawResult.ocs.data.nextcloud.storage) {
storage = rawResult.ocs.data.nextcloud.storage;
}
else {
throw new error_1.default("Fatal Error: nextcloud data.nextcloud.storage missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
if (rawResult.ocs.data.nextcloud.shares) {
shares = rawResult.ocs.data.nextcloud.shares;
}
else {
throw new error_1.default("Fatal Error: nextcloud data.nextcloud.shares missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
}
else {
throw new error_1.default("Fatal Error: nextcloud data.nextcloud missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
if (rawResult.ocs.data.server) {
server = rawResult.ocs.data.server;
}
else {
throw new error_1.default("Fatal Error: nextcloud data.server missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
if (rawResult.ocs.data.activeUsers) {
activeUsers = rawResult.ocs.data.activeUsers;
}
else {
throw new error_1.default("Fatal Error: nextcloud data.activeUsers missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
}
else {
throw new error_1.default("Fatal Error: nextcloud system data missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
const result = {
activeUsers,
nextcloud: {
shares,
storage,
system,
},
nextcloudClient: {
version: require("../package.json").version,
},
server,
};
return result;
});
}
getSystemBasicData() {
return __awaiter(this, void 0, void 0, function* () {
const requestInit = {
headers: this.getOcsHeaders(),
method: "GET",
};
const response = yield this.getHttpResponse(this.nextcloudOrigin + "/ocs/v2.php/apps/serverinfo/api/v1/basicdata", requestInit, [200], { description: "System Basic Data get" });
const rawResult = yield response.json();
// console.log("Basic Data\n", JSON.stringify(rawResult));
let result;
if (rawResult &&
rawResult.ocs &&
rawResult.ocs.data &&
rawResult.ocs.data.servertime &&
rawResult.ocs.data.uptime &&
rawResult.ocs.data.timeservers) {
result = {
serverTimeString: rawResult.ocs.data.servertime.replace("\n", ""),
uptimeString: rawResult.ocs.data.uptime.replace("\n", ""),
timeServersString: rawResult.ocs.data.timeservers.trim(),
};
}
else {
throw new error_1.default("Fatal Error: nextcloud basic data missing", "ERR_SYSTEM_INFO_MISSING_DATA");
}
return result;
});
}