606 строки
21 KiB
TypeScript
606 строки
21 KiB
TypeScript
import {
|
|
HttpRequestBody,
|
|
HttpResponse,
|
|
isNode,
|
|
TransferProgressEvent
|
|
} from "@azure/ms-rest-js";
|
|
import { Aborter } from "./Aborter";
|
|
import { DirectoryURL } from "./DirectoryURL";
|
|
import { FileDownloadResponse } from "./FileDownloadResponse";
|
|
import * as Models from "./generated/lib/models";
|
|
import { File } from "./generated/lib/operations";
|
|
import { IRange, rangeToString } from "./IRange";
|
|
import { IFileHTTPHeaders, IMetadata } from "./models";
|
|
import { Pipeline } from "./Pipeline";
|
|
import { StorageURL } from "./StorageURL";
|
|
import {
|
|
DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS,
|
|
FILE_MAX_SIZE_BYTES,
|
|
FILE_RANGE_MAX_SIZE_BYTES
|
|
} from "./utils/constants";
|
|
import { appendToURLPath } from "./utils/utils.common";
|
|
|
|
export interface IFileCreateOptions {
|
|
/**
|
|
* File HTTP headers like Content-Type.
|
|
*
|
|
* @type {IFileHTTPHeaders}
|
|
* @memberof IFileCreateOptions
|
|
*/
|
|
fileHTTPHeaders?: IFileHTTPHeaders;
|
|
|
|
/**
|
|
* A name-value pair
|
|
* to associate with a file storage object.
|
|
*
|
|
* @type {IMetadata}
|
|
* @memberof IFileCreateOptions
|
|
*/
|
|
metadata?: IMetadata;
|
|
}
|
|
|
|
export interface IFileDownloadOptions {
|
|
/**
|
|
* Optional. ONLY AVAILABLE IN NODE.JS.
|
|
*
|
|
* How many retries will perform when original body download stream unexpected ends.
|
|
* Above kind of ends will not trigger retry policy defined in a pipeline,
|
|
* because they doesn't emit network errors.
|
|
*
|
|
* With this option, every additional retry means an additional FileURL.download() request will be made
|
|
* from the broken point, until the requested range has been successfully downloaded or maxRetryRequests is reached.
|
|
*
|
|
* Default value is 5, please set a larger value when loading large files in poor network.
|
|
*
|
|
* @type {number}
|
|
* @memberof IFileDownloadOptions
|
|
*/
|
|
maxRetryRequests?: number;
|
|
|
|
/**
|
|
* When this header is set to true and
|
|
* specified together with the Range header, the service returns the MD5 hash
|
|
* for the range, as long as the range is less than or equal to 4 MB in size.
|
|
*
|
|
* @type {boolean}
|
|
* @memberof IFileDownloadOptions
|
|
*/
|
|
rangeGetContentMD5?: boolean;
|
|
|
|
/**
|
|
* Download progress updating event handler.
|
|
*
|
|
* @memberof IFileDownloadOptions
|
|
*/
|
|
progress?: (progress: TransferProgressEvent) => void;
|
|
}
|
|
|
|
export interface IFileUploadRangeOptions {
|
|
/**
|
|
* An MD5 hash of the content. This hash is
|
|
* used to verify the integrity of the data during transport. When the
|
|
* Content-MD5 header is specified, the File service compares the hash of the
|
|
* content that has arrived with the header value that was sent. If the two
|
|
* hashes do not match, the operation will fail with error code 400 (Bad
|
|
* Request).
|
|
*
|
|
* @type {Uint8Array}
|
|
* @memberof IFileUploadRangeOptions
|
|
*/
|
|
contentMD5?: Uint8Array;
|
|
|
|
/**
|
|
* Progress updating event handler.
|
|
*
|
|
* @memberof IFileUploadRangeOptions
|
|
*/
|
|
progress?: (progress: TransferProgressEvent) => void;
|
|
}
|
|
|
|
export interface IFileGetRangeListOptions {
|
|
/**
|
|
* Optional. Specifies the range of bytes over which to list ranges, inclusively.
|
|
*
|
|
* @type {IRange}
|
|
* @memberof IFileGetRangeListOptions
|
|
*/
|
|
range?: IRange;
|
|
}
|
|
|
|
/**
|
|
* Contains response data for the getRangeList operation.
|
|
*/
|
|
export type FileGetRangeListResponse = Models.FileGetRangeListHeaders & {
|
|
/**
|
|
* Range list for an Azure file.
|
|
*
|
|
* @type {Models.Range[]}
|
|
*/
|
|
rangeList: Models.Range[];
|
|
|
|
/**
|
|
* The underlying HTTP response.
|
|
*/
|
|
_response: HttpResponse & {
|
|
/**
|
|
* The parsed HTTP response headers.
|
|
*/
|
|
parsedHeaders: Models.FileGetRangeListHeaders;
|
|
/**
|
|
* The response body as text (string format)
|
|
*/
|
|
bodyAsText: string;
|
|
/**
|
|
* The response body as parsed JSON or XML
|
|
*/
|
|
parsedBody: Models.Range[];
|
|
};
|
|
};
|
|
|
|
export interface IFileStartCopyOptions {
|
|
/**
|
|
* A name-value pair
|
|
* to associate with a file storage object.
|
|
*
|
|
* @type {IMetadata}
|
|
* @memberof IFileCreateOptions
|
|
*/
|
|
metadata?: IMetadata;
|
|
}
|
|
|
|
/**
|
|
* A FileURL represents a URL to an Azure Storage file.
|
|
*
|
|
* @export
|
|
* @class FileURL
|
|
* @extends {StorageURL}
|
|
*/
|
|
export class FileURL extends StorageURL {
|
|
/**
|
|
* Creates a FileURL object from a DirectoryURL object.
|
|
*
|
|
* @static
|
|
* @param {DirectoryURL} directoryURL A DirectoryURL object
|
|
* @param {string} fileName A file name
|
|
* @returns
|
|
* @memberof FileURL
|
|
*/
|
|
public static fromDirectoryURL(directoryURL: DirectoryURL, fileName: string) {
|
|
return new FileURL(
|
|
appendToURLPath(directoryURL.url, encodeURIComponent(fileName)),
|
|
directoryURL.pipeline
|
|
);
|
|
}
|
|
|
|
/**
|
|
* context provided by protocol layer.
|
|
*
|
|
* @private
|
|
* @type {File}
|
|
* @memberof FileURL
|
|
*/
|
|
private context: File;
|
|
|
|
/**
|
|
* Creates an instance of FileURL.
|
|
*
|
|
* @param {string} url A URL string pointing to Azure Storage file, such as
|
|
* "https://myaccount.file.core.windows.net/myshare/mydirectory/file". You can
|
|
* append a SAS if using AnonymousCredential, such as
|
|
* "https://myaccount.file.core.windows.net/myshare/mydirectory/file?sasString".
|
|
* This method accepts an encoded URL or non-encoded URL pointing to a file.
|
|
* Encoded URL string will NOT be escaped twice, only special characters in URL path will be escaped.
|
|
* However, if a file or directory name includes %, file or directory name must be encoded in the URL.
|
|
* Such as a file named "myfile%", the URL should be "https://myaccount.file.core.windows.net/myshare/mydirectory/myfile%25".
|
|
* @param {Pipeline} pipeline Call StorageURL.newPipeline() to create a default
|
|
* pipeline, or provide a customized pipeline.
|
|
* @memberof FileURL
|
|
*/
|
|
constructor(url: string, pipeline: Pipeline) {
|
|
super(url, pipeline);
|
|
this.context = new File(this.storageClientContext);
|
|
}
|
|
|
|
/**
|
|
* Creates a new FileURL object identical to the source but with the
|
|
* specified request policy pipeline.
|
|
*
|
|
* @param {Pipeline} pipeline
|
|
* @returns {FileURL}
|
|
* @memberof FileURL
|
|
*/
|
|
public withPipeline(pipeline: Pipeline): FileURL {
|
|
return new FileURL(this.url, pipeline);
|
|
}
|
|
|
|
/**
|
|
* Creates a new file or replaces a file. Note it only initializes the file with no content.
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/create-file
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {number} size Specifies the maximum size in bytes for the file, up to 1 TB.
|
|
* @param {IFileCreateOptions} [options]
|
|
* @returns {Promise<Models.FileCreateResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async create(
|
|
aborter: Aborter,
|
|
size: number,
|
|
options: IFileCreateOptions = {}
|
|
): Promise<Models.FileCreateResponse> {
|
|
if (size < 0 || size > FILE_MAX_SIZE_BYTES) {
|
|
throw new RangeError(`File size must >= 0 and < ${FILE_MAX_SIZE_BYTES}.`);
|
|
}
|
|
|
|
options.fileHTTPHeaders = options.fileHTTPHeaders || {};
|
|
return this.context.create(size, {
|
|
abortSignal: aborter,
|
|
...options.fileHTTPHeaders,
|
|
metadata: options.metadata
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reads or downloads a file from the system, including its metadata and properties.
|
|
*
|
|
* * In Node.js, data returns in a Readable stream `readableStreamBody`
|
|
* * In browsers, data returns in a promise `blobBody`
|
|
*
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/get-file
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {number} offset From which position of the file to download, >= 0
|
|
* @param {number} [count] How much data to be downloaded, > 0. Will download to the end when undefined
|
|
* @param {IFileDownloadOptions} [options]
|
|
* @returns {Promise<Models.FileDownloadResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async download(
|
|
aborter: Aborter,
|
|
offset: number,
|
|
count?: number,
|
|
options: IFileDownloadOptions = {}
|
|
): Promise<Models.FileDownloadResponse> {
|
|
if (options.rangeGetContentMD5 && offset === 0 && count === undefined) {
|
|
throw new RangeError(
|
|
`rangeGetContentMD5 only works with partial data downloading`
|
|
);
|
|
}
|
|
|
|
const downloadFullFile = offset === 0 && !count;
|
|
const res = await this.context.download({
|
|
abortSignal: aborter,
|
|
onDownloadProgress: !isNode ? options.progress : undefined,
|
|
range: downloadFullFile ? undefined : rangeToString({ offset, count }),
|
|
rangeGetContentMD5: options.rangeGetContentMD5
|
|
});
|
|
|
|
// Return browser response immediately
|
|
if (!isNode) {
|
|
return res;
|
|
}
|
|
|
|
// We support retrying when download stream unexpected ends in Node.js runtime
|
|
// Following code shouldn't be bundled into browser build, however some
|
|
// bundlers may try to bundle following code and "FileReadResponse.ts".
|
|
// In this case, "FileDownloadResponse.browser.ts" will be used as a shim of "FileDownloadResponse.ts"
|
|
// The config is in package.json "browser" field
|
|
if (
|
|
options.maxRetryRequests === undefined ||
|
|
options.maxRetryRequests < 0
|
|
) {
|
|
// TODO: Default value or make it a required parameter?
|
|
options.maxRetryRequests = DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS;
|
|
}
|
|
|
|
if (res.contentLength === undefined) {
|
|
throw new RangeError(
|
|
`File download response doesn't contain valid content length header`
|
|
);
|
|
}
|
|
|
|
return new FileDownloadResponse(
|
|
aborter,
|
|
res,
|
|
async (start: number): Promise<NodeJS.ReadableStream> => {
|
|
const updatedOptions: Models.FileDownloadOptionalParams = {
|
|
range: rangeToString({
|
|
count: offset + res.contentLength! - start,
|
|
offset: start
|
|
})
|
|
};
|
|
|
|
// Debug purpose only
|
|
// console.log(
|
|
// `Read from internal stream, range: ${
|
|
// updatedOptions.range
|
|
// }, options: ${JSON.stringify(updatedOptions)}`
|
|
// );
|
|
|
|
return (await this.context.download({
|
|
abortSignal: aborter,
|
|
...updatedOptions
|
|
})).readableStreamBody!;
|
|
},
|
|
offset,
|
|
res.contentLength!,
|
|
{
|
|
maxRetryRequests: options.maxRetryRequests,
|
|
progress: options.progress
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns all user-defined metadata, standard HTTP properties, and system properties
|
|
* for the file. It does not return the content of the file.
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/get-file-properties
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @returns {Promise<Models.FileGetPropertiesResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async getProperties(
|
|
aborter: Aborter
|
|
): Promise<Models.FileGetPropertiesResponse> {
|
|
return this.context.getProperties({
|
|
abortSignal: aborter
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes the file from the storage account.
|
|
* When a file is successfully deleted, it is immediately removed from the storage
|
|
* account's index and is no longer accessible to clients. The file's data is later
|
|
* removed from the service during garbage collection.
|
|
*
|
|
* Delete File will fail with status code 409 (Conflict) and error code SharingViolation
|
|
* if the file is open on an SMB client.
|
|
*
|
|
* Delete File is not supported on a share snapshot, which is a read-only copy of
|
|
* a share. An attempt to perform this operation on a share snapshot will fail with 400 (InvalidQueryParameterValue)
|
|
*
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/delete-file2
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @returns {Promise<Models.FileDeleteResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async delete(aborter: Aborter): Promise<Models.FileDeleteResponse> {
|
|
return this.context.deleteMethod({
|
|
abortSignal: aborter
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets HTTP headers on the file.
|
|
*
|
|
* If no option provided, or no value provided for the file HTTP headers in the options,
|
|
* these file HTTP headers without a value will be cleared.
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/set-file-properties
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {fileHTTPHeaders} [IFileHTTPHeaders] File HTTP headers like Content-Type.
|
|
* Provide undefined will remove existing HTTP headers.
|
|
* @returns {Promise<Models.FileSetHTTPHeadersResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async setHTTPHeaders(
|
|
aborter: Aborter,
|
|
fileHTTPHeaders: IFileHTTPHeaders = {}
|
|
): Promise<Models.FileSetHTTPHeadersResponse> {
|
|
return this.context.setHTTPHeaders({
|
|
abortSignal: aborter,
|
|
...fileHTTPHeaders
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resize file.
|
|
*
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/set-file-properties
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {number} length Resizes a file to the specified size in bytes.
|
|
* If the specified byte value is less than the current size of the file,
|
|
* then all ranges above the specified byte value are cleared.
|
|
* @returns {Promise<Models.FileSetHTTPHeadersResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async resize(
|
|
aborter: Aborter,
|
|
length: number
|
|
): Promise<Models.FileSetHTTPHeadersResponse> {
|
|
if (length < 0) {
|
|
throw new RangeError(`Size cannot less than 0 when resizing file.`);
|
|
}
|
|
return this.context.setHTTPHeaders({
|
|
abortSignal: aborter,
|
|
fileContentLength: length
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates user-defined metadata for the specified file.
|
|
*
|
|
* If no metadata defined in the option parameter, the file
|
|
* metadata will be removed.
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/set-file-metadata
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {IMetadata} [metadata] If no metadata provided, all existing directory metadata will be removed
|
|
* @returns {Promise<Models.FileSetMetadataResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async setMetadata(
|
|
aborter: Aborter,
|
|
metadata: IMetadata = {}
|
|
): Promise<Models.FileSetMetadataResponse> {
|
|
return this.context.setMetadata({
|
|
abortSignal: aborter,
|
|
metadata
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Upload a range of bytes to a file. Both the start and count of the
|
|
* range must be specified. The range can be up to 4 MB in size.
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {HttpRequestBody} body Blob, string, ArrayBuffer, ArrayBufferView or a function
|
|
* which returns a new Readable stream whose offset is from data source beginning.
|
|
* @param {number} offset Offset position of the destination Azure File to upload.
|
|
* @param {number} contentLength Length of body in bytes. Use Buffer.byteLength() to calculate body length for a
|
|
* string including non non-Base64/Hex-encoded characters.
|
|
* @param {IFileUploadRangeOptions} [options]
|
|
* @returns {Promise<Models.FileUploadRangeResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async uploadRange(
|
|
aborter: Aborter,
|
|
body: HttpRequestBody,
|
|
offset: number,
|
|
contentLength: number,
|
|
options: IFileUploadRangeOptions = {}
|
|
): Promise<Models.FileUploadRangeResponse> {
|
|
if (offset < 0 || contentLength <= 0) {
|
|
throw new RangeError(`offset must >= 0 and contentLength must be > 0`);
|
|
}
|
|
|
|
if (contentLength > FILE_RANGE_MAX_SIZE_BYTES) {
|
|
throw new RangeError(
|
|
`offset must be < ${FILE_RANGE_MAX_SIZE_BYTES} bytes`
|
|
);
|
|
}
|
|
|
|
return this.context.uploadRange(
|
|
rangeToString({ count: contentLength, offset }),
|
|
"update",
|
|
contentLength,
|
|
{
|
|
abortSignal: aborter,
|
|
contentMD5: options.contentMD5,
|
|
onUploadProgress: options.progress,
|
|
optionalbody: body
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Clears the specified range and
|
|
* releases the space used in storage for that range.
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {number} offset
|
|
* @param {number} contentLength
|
|
* @returns {Promise<Models.FileUploadRangeResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async clearRange(
|
|
aborter: Aborter,
|
|
offset: number,
|
|
contentLength: number
|
|
): Promise<Models.FileUploadRangeResponse> {
|
|
if (offset < 0 || contentLength <= 0) {
|
|
throw new RangeError(`offset must >= 0 and contentLength must be > 0`);
|
|
}
|
|
|
|
return this.context.uploadRange(
|
|
rangeToString({ count: contentLength, offset }),
|
|
"clear",
|
|
0,
|
|
{
|
|
abortSignal: aborter
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of valid ranges for a file.
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {IFileGetRangeListOptions} [options]
|
|
* @returns {Promise<FileGetRangeListResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async getRangeList(
|
|
aborter: Aborter,
|
|
options: IFileGetRangeListOptions = {}
|
|
): Promise<FileGetRangeListResponse> {
|
|
const originalResponse = await this.context.getRangeList({
|
|
abortSignal: aborter,
|
|
range: options.range ? rangeToString(options.range) : undefined
|
|
});
|
|
return {
|
|
_response: originalResponse._response,
|
|
date: originalResponse.date,
|
|
eTag: originalResponse.eTag,
|
|
errorCode: originalResponse.errorCode,
|
|
fileContentLength: originalResponse.fileContentLength,
|
|
lastModified: originalResponse.lastModified,
|
|
rangeList: originalResponse.filter(() => {
|
|
return true;
|
|
}),
|
|
requestId: originalResponse.requestId,
|
|
version: originalResponse.version
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Copies a blob or file to a destination file within the storage account.
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {string} copySource Specifies the URL of the source file or blob, up to 2 KB in length.
|
|
* To copy a file to another file within the same storage account, you may use Shared Key to
|
|
* authenticate the source file. If you are copying a file from another storage account, or if you
|
|
* are copying a blob from the same storage account or another storage account, then you must
|
|
* authenticate the source file or blob using a shared access signature. If the source is a public
|
|
* blob, no authentication is required to perform the copy operation. A file in a share snapshot
|
|
* can also be specified as a copy source.
|
|
* @param {IFileStartCopyOptions} [options]
|
|
* @returns {Promise<Models.FileStartCopyResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async startCopyFromURL(
|
|
aborter: Aborter,
|
|
copySource: string,
|
|
options: IFileStartCopyOptions = {}
|
|
): Promise<Models.FileStartCopyResponse> {
|
|
return this.context.startCopy(copySource, {
|
|
abortSignal: aborter,
|
|
metadata: options.metadata
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Aborts a pending Copy File operation, and leaves a destination file with zero length and full
|
|
* metadata.
|
|
* @see https://docs.microsoft.com/en-us/rest/api/storageservices/abort-copy-file
|
|
*
|
|
* @param {Aborter} aborter Create a new Aborter instance with Aborter.none or Aborter.timeout(),
|
|
* goto documents of Aborter for more examples about request cancellation
|
|
* @param {string} copyId
|
|
* @returns {Promise<Models.FileAbortCopyResponse>}
|
|
* @memberof FileURL
|
|
*/
|
|
public async abortCopyFromURL(
|
|
aborter: Aborter,
|
|
copyId: string
|
|
): Promise<Models.FileAbortCopyResponse> {
|
|
return this.context.abortCopy(copyId, {
|
|
abortSignal: aborter
|
|
});
|
|
}
|
|
}
|