@pi-r/azure 0.11.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/index.js CHANGED
@@ -1,25 +1,12 @@
1
1
  "use strict";
2
- exports.CLOUD_DOWNLOAD_CHUNK = exports.CLOUD_UPLOAD_CHUNK = exports.CLOUD_UPLOAD_STREAM = void 0;
3
- exports.validateStorage = validateStorage;
4
- exports.validateDatabase = validateDatabase;
5
- exports.createStorageClient = createStorageClient;
6
- exports.createDatabaseClient = createDatabaseClient;
7
- exports.createBucket = createBucket;
8
- exports.createBucketV2 = createBucketV2;
9
- exports.setBucketWebsite = setBucketWebsite;
10
- exports.deleteObjects = deleteObjects;
11
- exports.deleteObjectsV2 = deleteObjectsV2;
12
- exports.deleteObjectsV3 = deleteObjectsV3;
13
- exports.copyObject = copyObject;
14
- exports.executeQuery = executeQuery;
15
- exports.executeBatchQuery = executeBatchQuery;
16
- const path = require("node:path");
17
- const storage = require("@azure/storage-blob");
18
- const cosmos = require("@azure/cosmos");
19
- const identity = require("@azure/identity");
20
- const Cloud = require("@e-mc/cloud");
21
- const types_1 = require("@e-mc/types");
22
- const util_1 = require("@e-mc/cloud/util");
2
+
3
+ const path = require('node:path');
4
+ const { AnonymousCredential, BlobServiceClient, StorageSharedKeyCredential } = require('@azure/storage-blob');
5
+ const { CosmosClient } = require('@azure/cosmos');
6
+ const { DefaultAzureCredential, EnvironmentCredential, UsernamePasswordCredential } = require('@azure/identity');
7
+ const Cloud = require('@e-mc/cloud');
8
+ const { errorMessage, isErrorCode, isString } = require('@e-mc/types');
9
+ const { formatError } = require('@e-mc/cloud/util');
23
10
  function isEnvironmentCredential() {
24
11
  const env = process.env;
25
12
  return !!(env.AZURE_TENANT_ID && env.AZURE_CLIENT_ID && (env.AZURE_CLIENT_SECRET || env.AZURE_CLIENT_CERTIFICATE_PATH));
@@ -40,7 +27,7 @@ function createStorageClient(credential) {
40
27
  if (!accountName) {
41
28
  credential.accountName = /AccountName=([^;]+);/.exec(connectionString)?.[1];
42
29
  }
43
- return storage.BlobServiceClient.fromConnectionString(connectionString, credential.clientOptions);
30
+ return BlobServiceClient.fromConnectionString(connectionString, credential.clientOptions);
44
31
  }
45
32
  if (accountSas && !accountSas.startsWith('?')) {
46
33
  accountSas = '?' + accountSas;
@@ -55,26 +42,26 @@ function createStorageClient(credential) {
55
42
  else {
56
43
  credential.accountName ||= /^https:\/\/([a-z\d]+)\./.exec(sharedAccessSignature)?.[1];
57
44
  }
58
- return new storage.BlobServiceClient(sharedAccessSignature);
45
+ return new BlobServiceClient(sharedAccessSignature);
59
46
  }
60
47
  let serviceCredential;
61
48
  if (accountName && accountSas) {
62
- serviceCredential = new storage.AnonymousCredential();
49
+ serviceCredential = new AnonymousCredential();
63
50
  }
64
51
  else if (accountName && accountKey) {
65
- serviceCredential = new storage.StorageSharedKeyCredential(accountName, accountKey);
52
+ serviceCredential = new StorageSharedKeyCredential(accountName, accountKey);
66
53
  }
67
54
  else if (isEnvironmentCredential()) {
68
- serviceCredential = new identity.EnvironmentCredential();
55
+ serviceCredential = new EnvironmentCredential();
69
56
  }
70
57
  else {
71
- serviceCredential = new identity.DefaultAzureCredential(credential.defaultAzureOptions);
58
+ serviceCredential = new DefaultAzureCredential(credential.defaultAzureOptions);
72
59
  }
73
- return new storage.BlobServiceClient(url, serviceCredential, credential.clientOptions);
60
+ return new BlobServiceClient(url, serviceCredential, credential.clientOptions);
74
61
  }
75
62
  function createDatabaseClient(credential) {
76
63
  if (credential.username && credential.password && credential.tenantId && credential.clientId) {
77
- credential.aadCredentials = new identity.UsernamePasswordCredential(credential.username, credential.password, credential.tenantId, credential.clientId);
64
+ credential.aadCredentials = new UsernamePasswordCredential(credential.username, credential.password, credential.tenantId, credential.clientId);
78
65
  credential = { ...credential };
79
66
  delete credential.username;
80
67
  delete credential.password;
@@ -82,9 +69,9 @@ function createDatabaseClient(credential) {
82
69
  delete credential.clientId;
83
70
  }
84
71
  else if (isEnvironmentCredential()) {
85
- credential.aadCredentials = new identity.EnvironmentCredential();
72
+ credential.aadCredentials = new EnvironmentCredential();
86
73
  }
87
- return new cosmos.CosmosClient(credential);
74
+ return new CosmosClient(credential);
88
75
  }
89
76
  async function createBucket(credential, bucket, publicRead, service = "azure") {
90
77
  return createBucketV2.call(this, credential, bucket, publicRead ? 'blob' : 'container', undefined, service);
@@ -119,7 +106,7 @@ async function createBucketV2(credential, bucket, access, options, service = "az
119
106
  .then(response => {
120
107
  const { requestId, errorCode } = response;
121
108
  if (errorCode) {
122
- this.formatMessage(64, service, ['Container created with errors', bucket], (0, types_1.errorMessage)("Error code", errorCode), Cloud.optionsLogMessage('WARN'));
109
+ this.formatMessage(64, service, ['Container created with errors', bucket], errorMessage("Error code", errorCode), Cloud.optionsLogMessage('WARN'));
123
110
  }
124
111
  else {
125
112
  this.formatMessage(64, service, ['Container created', bucket], requestId, Cloud.optionsLogMessage('COMMAND'));
@@ -127,7 +114,7 @@ async function createBucketV2(credential, bucket, access, options, service = "az
127
114
  return true;
128
115
  })
129
116
  .catch(async (err) => {
130
- if ((0, types_1.isErrorCode)(err, 'ContainerAlreadyExists')) {
117
+ if (isErrorCode(err, 'ContainerAlreadyExists')) {
131
118
  if (access) {
132
119
  await setAccessPolicy();
133
120
  }
@@ -140,20 +127,20 @@ async function createBucketV2(credential, bucket, access, options, service = "az
140
127
  async function setBucketWebsite(credential, bucket, options, service = "azure") {
141
128
  const staticWebsite = { enabled: true };
142
129
  const { indexPage, indexPath, errorPath } = options;
143
- if ((0, types_1.isString)(indexPage)) {
130
+ if (isString(indexPage)) {
144
131
  staticWebsite.indexDocument = path.basename(indexPage);
145
132
  }
146
- if ((0, types_1.isString)(indexPath)) {
133
+ if (isString(indexPath)) {
147
134
  staticWebsite.defaultIndexDocumentPath = Cloud.joinPath(bucket, indexPath);
148
135
  }
149
- if ((0, types_1.isString)(errorPath)) {
136
+ if (isString(errorPath)) {
150
137
  staticWebsite.errorDocument404Path = Cloud.joinPath(bucket, errorPath);
151
138
  }
152
139
  return createStorageClient(credential).setProperties({ staticWebsite })
153
140
  .then(response => {
154
141
  const { requestId, errorCode } = response;
155
142
  if (errorCode) {
156
- this.formatMessage(64, service, ["Bucket website configured" + ' with errors', bucket], (0, types_1.errorMessage)("Error code", errorCode), Cloud.optionsLogMessage('WARN'));
143
+ this.formatMessage(64, service, ["Bucket website configured" + ' with errors', bucket], errorMessage("Error code", errorCode), Cloud.optionsLogMessage('WARN'));
157
144
  }
158
145
  else {
159
146
  this.formatMessage(64, service, ["Bucket website configured", bucket], requestId, Cloud.optionsLogMessage('COMMAND'));
@@ -165,8 +152,8 @@ async function setBucketWebsite(credential, bucket, options, service = "azure")
165
152
  return false;
166
153
  });
167
154
  }
168
- async function deleteObjects(credential, bucket, service, sdk, recursive = true) {
169
- return deleteObjectsV2.call(this, credential, bucket, recursive, service);
155
+ async function deleteObjects(credential, bucket, options, service) {
156
+ return deleteObjectsV3.call(this, credential, bucket, options, service);
170
157
  }
171
158
  async function deleteObjectsV2(credential, bucket, recursive = true, service) {
172
159
  return deleteObjectsV3.call(this, credential, bucket, { recursive }, service);
@@ -210,7 +197,7 @@ async function copyObject(credential, bucketSource, keySource, bucket, key, opti
210
197
  return poller.pollUntilDone()
211
198
  .then(response => {
212
199
  if (response.errorCode) {
213
- this.formatMessage(64, service, ["Copy success" + ' with errors', bucket], (0, types_1.errorMessage)("Error code", response.errorCode, bucketSource), Cloud.optionsLogMessage('WARN'));
200
+ this.formatMessage(64, service, ["Copy success" + ' with errors', bucket], errorMessage("Error code", response.errorCode, bucketSource), Cloud.optionsLogMessage('WARN'));
214
201
  }
215
202
  else {
216
203
  this.formatMessage(64, service, ["Copy success", bucket], bucketSource + '/' + keySource, Cloud.optionsLogMessage('COMMAND'));
@@ -243,7 +230,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
243
230
  const { service, name, table, id, query, storedProcedureId, partitionKey = null, params, limit = 0, ignoreCache } = item;
244
231
  if (!name || !table) {
245
232
  closeClient();
246
- throw (0, util_1.formatError)(item, name ? "Missing database table" : "Missing database name");
233
+ throw formatError(item, name ? "Missing database table" : "Missing database name");
247
234
  }
248
235
  cacheValue.exclusiveOf = Array.isArray(ignoreCache) ? ignoreCache : undefined;
249
236
  let rows, queryString = caching && ignoreCache !== true ? name + '_' + table + '_' : '';
@@ -306,7 +293,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
306
293
  }
307
294
  else {
308
295
  closeClient();
309
- throw (0, util_1.formatError)(item, "Missing database query");
296
+ throw formatError(item, "Missing database query");
310
297
  }
311
298
  }
312
299
  result[i] = this.setQueryResult(service, credential, queryString, rows, cacheValue);
@@ -314,6 +301,23 @@ async function executeBatchQuery(credential, batch, sessionKey) {
314
301
  closeClient();
315
302
  return result;
316
303
  }
317
- exports.CLOUD_UPLOAD_STREAM = true;
318
- exports.CLOUD_UPLOAD_CHUNK = true;
319
- exports.CLOUD_DOWNLOAD_CHUNK = true;
304
+ const CLOUD_UPLOAD_STREAM = true;
305
+ const CLOUD_UPLOAD_CHUNK = true;
306
+ const CLOUD_DOWNLOAD_CHUNK = true;
307
+
308
+ exports.CLOUD_DOWNLOAD_CHUNK = CLOUD_DOWNLOAD_CHUNK;
309
+ exports.CLOUD_UPLOAD_CHUNK = CLOUD_UPLOAD_CHUNK;
310
+ exports.CLOUD_UPLOAD_STREAM = CLOUD_UPLOAD_STREAM;
311
+ exports.copyObject = copyObject;
312
+ exports.createBucket = createBucket;
313
+ exports.createBucketV2 = createBucketV2;
314
+ exports.createDatabaseClient = createDatabaseClient;
315
+ exports.createStorageClient = createStorageClient;
316
+ exports.deleteObjects = deleteObjects;
317
+ exports.deleteObjectsV2 = deleteObjectsV2;
318
+ exports.deleteObjectsV3 = deleteObjectsV3;
319
+ exports.executeBatchQuery = executeBatchQuery;
320
+ exports.executeQuery = executeQuery;
321
+ exports.setBucketWebsite = setBucketWebsite;
322
+ exports.validateDatabase = validateDatabase;
323
+ exports.validateStorage = validateStorage;
package/download/index.js CHANGED
@@ -1,23 +1,24 @@
1
1
  "use strict";
2
- const path = require("node:path");
3
- const Cloud = require("@e-mc/cloud");
4
- const types_1 = require("@e-mc/types");
5
- const util_1 = require("@e-mc/cloud/util");
6
- const client_1 = require("@pi-r/azure");
2
+
3
+ const path = require('node:path');
4
+ const Cloud = require('@e-mc/cloud');
5
+ const { alignSize, errorValue, getTempDir, isErrorCode, isPlainObject } = require('@e-mc/types');
6
+ const { createErrorHandler, intoArray } = require('@e-mc/cloud/util');
7
+ const { copyObject, createStorageClient } = require('@pi-r/azure');
7
8
  function download(credential, service = "azure") {
8
- const blobServiceClient = (0, client_1.createStorageClient)(credential);
9
+ const blobServiceClient = createStorageClient(credential);
9
10
  return (data, callback) => {
10
11
  const { bucket, download: target } = data;
11
12
  const key = target.keyname || target.filename;
12
13
  if (!bucket || !key) {
13
- callback((0, types_1.errorValue)('Missing property', !bucket ? 'Container' : 'Name'));
14
+ callback(errorValue('Missing property', !bucket ? 'Container' : 'Name'));
14
15
  return;
15
16
  }
16
17
  const { flags = 0 } = target;
17
18
  const containerClient = blobServiceClient.getContainerClient(bucket);
18
19
  const blobClient = containerClient.getBlockBlobClient(key);
19
- const blockSize = (flags & 4) && (0, types_1.alignSize)(target.chunkSize, 1024) || 0;
20
- const tempDir = blockSize >= 2097152000 ? (0, types_1.getTempDir)(true, service) : null;
20
+ const blockSize = (flags & 4) && alignSize(target.chunkSize, 1024) || 0;
21
+ const tempDir = blockSize >= 2097152000 ? getTempDir(true, service) : null;
21
22
  let diskClient, destination;
22
23
  if (tempDir) {
23
24
  destination = path.join(tempDir, path.basename(target.filename || key));
@@ -29,26 +30,26 @@ function download(credential, service = "azure") {
29
30
  diskClient
30
31
  .then(async (value) => {
31
32
  callback(null, destination || value);
32
- const copyTo = (0, util_1.intoArray)(target.copyObject);
33
+ const copyTo = intoArray(target.copyObject);
33
34
  if (copyTo) {
34
35
  const tasks = [];
35
36
  for (const { bucket: bucketName, pathname, filename, options } of copyTo) {
36
37
  const keyObject = filename ? Cloud.joinPath(pathname, filename, true) : key;
37
- tasks.push(client_1.copyObject
38
+ tasks.push(copyObject
38
39
  .call(this, credential, bucket, key, bucketName, keyObject, options)
39
- .catch((0, util_1.createErrorHandler)(this, service, bucket)));
40
+ .catch(createErrorHandler(this, service, bucket)));
40
41
  }
41
42
  await Promise.all(tasks);
42
43
  }
43
44
  const deleteObject = target.deleteObject;
44
45
  if (deleteObject) {
45
46
  const location = Cloud.joinPath(bucket, key);
46
- containerClient.delete((0, types_1.isPlainObject)(deleteObject) ? { ...deleteObject, abortSignal: this.signal } : { abortSignal: this.signal })
47
+ containerClient.delete(isPlainObject(deleteObject) ? { ...deleteObject, abortSignal: this.signal } : { abortSignal: this.signal })
47
48
  .then(() => {
48
49
  this.formatMessage(64, service, "Delete success", location, Cloud.optionsLogMessage('DELETE'));
49
50
  })
50
51
  .catch((err) => {
51
- if (!(0, types_1.isErrorCode)(err, 'BlobNotFound')) {
52
+ if (!isErrorCode(err, 'BlobNotFound')) {
52
53
  this.formatFail(64, service, ["Delete failed", location], err, Cloud.optionsLogMessage('FAIL', { fatal: !!target.active }));
53
54
  }
54
55
  });
@@ -57,4 +58,5 @@ function download(credential, service = "azure") {
57
58
  .catch(callback);
58
59
  };
59
60
  }
61
+
60
62
  module.exports = download;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/azure",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "Azure cloud functions for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "publishConfig": {
@@ -19,9 +19,9 @@
19
19
  "license": "MIT",
20
20
  "homepage": "https://github.com/anpham6/pi-r#readme",
21
21
  "dependencies": {
22
- "@e-mc/cloud": "^0.13.10",
23
- "@e-mc/module": "^0.13.10",
24
- "@e-mc/types": "^0.13.10",
22
+ "@e-mc/cloud": "^0.14.0",
23
+ "@e-mc/module": "^0.14.0",
24
+ "@e-mc/types": "^0.14.0",
25
25
  "@azure/cosmos": "^4.9.3",
26
26
  "@azure/identity": "*",
27
27
  "@azure/storage-blob": "^12.31.0"
package/upload/index.js CHANGED
@@ -1,19 +1,20 @@
1
1
  "use strict";
2
- const path = require("node:path");
3
- const fs = require("node:fs");
4
- const stream = require("node:stream");
5
- const Cloud = require("@e-mc/cloud");
6
- const types_1 = require("@e-mc/types");
7
- const util_1 = require("@e-mc/cloud/util");
8
- const client_1 = require("@pi-r/azure");
2
+
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
5
+ const stream = require('node:stream');
6
+ const Cloud = require('@e-mc/cloud');
7
+ const { alignSize, coerceObject, createAbortError, isObject } = require('@e-mc/types');
8
+ const { createErrorHandler, createKeyAndBody, generateFilename, intoArray } = require('@e-mc/cloud/util');
9
+ const { copyObject, createBucketV2, createStorageClient } = require('@pi-r/azure');
9
10
  const BUCKET_SESSION = new Set();
10
11
  const BUCKET_RESPONSE = {};
11
12
  const getBucketKey = (credential, bucket, acl = '') => Cloud.asString(credential, true) + bucket + '_' + acl;
12
13
  function upload(credential, service = "azure") {
13
- const blobServiceClient = (0, client_1.createStorageClient)(credential);
14
+ const blobServiceClient = createStorageClient(credential);
14
15
  return async (data, callback) => {
15
16
  const { bucket, localUri } = data;
16
- const { pathname, flags = 0, fileGroup, contentType, metadata, tags, endpoint, admin = {}, overwrite, options } = data.upload;
17
+ const { pathname, flags = 0, fileGroup, descendantsGroup, contentType, metadata, tags, endpoint, admin = {}, overwrite, options } = data.upload;
17
18
  const containerClient = blobServiceClient.getContainerClient(bucket);
18
19
  let filename = data.upload.filename || path.basename(localUri), bucketKey;
19
20
  const complete = (err, url) => {
@@ -23,23 +24,23 @@ function upload(credential, service = "azure") {
23
24
  }
24
25
  callback(err, url);
25
26
  };
26
- const addLog = (0, util_1.createErrorHandler)(this, bucket, service);
27
+ const addLog = createErrorHandler(this, bucket, service);
27
28
  const configBucket = admin.configBucket;
28
29
  if (!BUCKET_SESSION.has(bucket)) {
29
30
  const bucketAcl = admin.publicRead ? 'blob' : admin.acl;
30
- const response = BUCKET_RESPONSE[bucketKey = getBucketKey(credential, bucket, bucketAcl)] ||= client_1.createBucketV2.call(this, credential, bucket, bucketAcl, configBucket?.create);
31
+ const response = BUCKET_RESPONSE[bucketKey = getBucketKey(credential, bucket, bucketAcl)] ||= createBucketV2.call(this, credential, bucket, bucketAcl, configBucket?.create);
31
32
  if (!await response) {
32
33
  complete(null);
33
34
  return;
34
35
  }
35
36
  BUCKET_SESSION.add(bucket);
36
37
  }
37
- const retentionPolicy = (0, util_1.intoArray)(configBucket?.retentionPolicy);
38
+ const retentionPolicy = intoArray(configBucket?.retentionPolicy);
38
39
  if (retentionPolicy) {
39
40
  try {
40
41
  if (this.hasCoerce(service, 'options')) {
41
42
  for (const policy of retentionPolicy) {
42
- (0, types_1.coerceObject)(policy);
43
+ coerceObject(policy);
43
44
  }
44
45
  }
45
46
  }
@@ -55,7 +56,7 @@ function upload(credential, service = "azure") {
55
56
  if (!overwrite) {
56
57
  try {
57
58
  const current = filename;
58
- const next = (0, util_1.generateFilename)(filename);
59
+ const next = generateFilename(filename);
59
60
  let i = 0, exists = false;
60
61
  do {
61
62
  if (i > 0) {
@@ -81,7 +82,7 @@ function upload(credential, service = "azure") {
81
82
  return;
82
83
  }
83
84
  }
84
- const chunkSize = flags & 4 ? (0, types_1.alignSize)(data.upload.chunkSize, 1024) : 0;
85
+ const chunkSize = flags & 4 ? alignSize(data.upload.chunkSize, 1024) : 0;
85
86
  const maxSingleShotSize = Math.max(chunkSize, 8388608);
86
87
  const Key = [filename];
87
88
  const Body = [data.buffer];
@@ -110,7 +111,7 @@ function upload(credential, service = "azure") {
110
111
  Stream[0] = stream.Readable.from(Body[0], { highWaterMark: getChunkSize() });
111
112
  }
112
113
  if (fileGroup) {
113
- const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, chunkSize > 0 ? maxSingleShotSize : 0, addLog, flags);
114
+ const [key, body, type] = createKeyAndBody(filename, fileGroup, { errorCallback: addLog, descendantsGroup, flags, chunkSize: chunkSize > 0 ? maxSingleShotSize : 0 });
114
115
  Key.push(...key);
115
116
  ContentType.push(...type);
116
117
  for (let i = 0; i < body.length; ++i) {
@@ -130,7 +131,7 @@ function upload(credential, service = "azure") {
130
131
  const first = i === 0;
131
132
  if (this.aborted) {
132
133
  if (first) {
133
- complete((0, types_1.createAbortError)());
134
+ complete(createAbortError());
134
135
  }
135
136
  return;
136
137
  }
@@ -138,10 +139,10 @@ function upload(credential, service = "azure") {
138
139
  const params = { ...options, abortSignal: this.signal };
139
140
  const headers = params.blobHTTPHeaders ||= {};
140
141
  if (first) {
141
- if ((0, types_1.isObject)(metadata)) {
142
+ if (isObject(metadata)) {
142
143
  params.metadata = metadata;
143
144
  }
144
- if ((0, types_1.isObject)(tags)) {
145
+ if (isObject(tags)) {
145
146
  params.tags = tags;
146
147
  }
147
148
  headers.blobContentType ||= ContentType[i];
@@ -173,12 +174,12 @@ function upload(credential, service = "azure") {
173
174
  const url = endpoint ? Cloud.joinPath(endpoint, blobName) : Cloud.isFile(requestUrl = decodeURIComponent(requestUrl), 'http/s') ? requestUrl : Cloud.joinPath(`https://${credential.accountName}.blob.core.windows.net`, bucket, blobName);
174
175
  this.formatMessage(64, service, "Upload success", url, Cloud.optionsLogMessage('UPLOAD'));
175
176
  if (first) {
176
- const copyTo = (0, util_1.intoArray)(data.upload.copyObject);
177
+ const copyTo = intoArray(data.upload.copyObject);
177
178
  if (copyTo) {
178
179
  const tasks = [];
179
180
  for (const { bucket: bucketName, pathname: pathObject = pathname, filename: fileObject, options: copyOptions } of copyTo) {
180
181
  const keyObject = fileObject ? Cloud.joinPath(pathObject, fileObject, true) : blobName;
181
- tasks.push(client_1.copyObject
182
+ tasks.push(copyObject
182
183
  .call(this, credential, bucket, blobName, bucketName, keyObject, copyOptions)
183
184
  .catch(addLog));
184
185
  }
@@ -198,4 +199,5 @@ function upload(credential, service = "azure") {
198
199
  }
199
200
  };
200
201
  }
202
+
201
203
  module.exports = upload;