Drv is a SuperFetch plugin to access the Google Drive API. SuperFetch is a proxy for UrlFetchApp with additional features – see SuperFetch – a proxy enhancement to Apps Script UrlFetch for how it works and what it does.

This is another in my series on SuperFetch plugins.

Page Content hide
1 Motivation

Motivation

There are of course already 2 Drive API services (Drive and DriveApp) for Apps Script. As well as built in caching and list handling, SuperFetch plugin takes a slightly different approach to these Drive APIS, which are about navigating Drive via a series of file and folder ids.

Drv supports ids, but it also provides a more standard path style of file access (eg user/files/documents/a.doc) which I personally prefer to messing around with all those ids. Not all methods are supported yet of course, but I’ll add more over time.

Behind the scenes, Drv use the Drive REST API. It doesn’t use the built in Apps Script services at all.

The main additional features of Drv are

  • Access files and folders by their path and name as well as their id
  • Create intermediate folders on the fly
  • Handle big lists of files automatically without bothering about paging
  • Built in caching
  • Closures for relative paths
  • Wildcards
  • Version pruning
  • Resumable uploads for big files
  • Automatic detection of and conversion to and from JSON
  • Export and conversions automatically supported

I’ll cover some of these in this article and some more advanced topics in future articles

Script Preparation

You’ll need the bmSuperFetch library and if you want to run some tests, the bmUnitTest library – details at the end of the article.

I use a regular cloud project for all of these plugins rather than the Apps Script managed one (you can change it in Project settings), and you’ll need at least these scopes enabled in your manifest file (appsscript.json), as well as the Drive API enabled

"oauthScopes": [
  "https://www.googleapis.com/auth/script.external_request",
  "https://www.googleapis.com/auth/drive"
]
scopes

Instantiation

First you need a SuperFetch instance. If you don’t need caching, then just omit the cacheService property.


const { Plugins, SuperFetch } = bmSuperFetch

// use this basic drv setup
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken,
cacheService: CacheService.getUserCache()
})
superfetch instance

Drv instance

This will be the handle for accessing Drv


const drv = new Plugins.Drv({
superFetch
})
drv instance

There are various other standard SuperFetch Plugin parameters that I’ll deal with later, but the ones shown are of special interest to this article.

noCache property (optional)

You can turn off caching by setting this to true, or by omitting the CacheService poperty in the SuperFetch instantiation.

base property (optional)

You can ignore the base property if you want to be able to write to the top level of the Drive. But setting a top folder means that you can isolate to some subfolder.  All all paths accessed by this instance will be relative to this base. Eg if you set base to test/users, all operations will be relative to the folder test/users

Usage

Uploading data

This will upload the payload data and return the metadata of a newly created file ‘testdrv/json/stuff.json’.

  
// this will throw an error automatically if detected
const stuff = drv.files.upload({
path: 'testdrv/json',
name: 'stuff.json',
payload: {
foo: 'bar'
}
}).data

/*
{ kind: 'drive#file',
id: '1OOIn_UniYDvEBwqXHhKmyY8V22RoDEG-',
name: 'stuff.json',
mimeType: 'application/json' }
*/
upload

Getting the file metadata

This returns the file metadata

  // get the metadata by path
console.log( drv.files.get({path: 'testdrv/json', name: 'stuff.json'}).data)

// get the metadata by id
console.log( drv.files.get({id: stuff.data.id}).data)

/* same results
{ kind: 'drive#file',
id: '1hzFJfGxOEDR-oHLrnvC9gEjHduInkorr',
name: 'stuff.json',
mimeType: 'application/json' }
*/
getting metadata

Downloading data

This will return the content – if it’s pareseable (as in this case), the result will be in the data property. Later on we’ll look at other kinds of data

  // get the data by path
console.log( drv.files.download({path: 'testdrv/json', name: 'stuff.json'}).data)

// get the data by id
console.log( drv.files.download({id: stuff.data.id}).data)

/*
results are the same for each
{ foo: 'bar' }
*/
download data

Closures

As with all other SuperFetch plugins, Drv makes use of closures.

Path closure

Let’s rewrite all of the above using the path closure.

  // get a closure for the path
const path = drv.files.path({ path: 'testdrv/json', name: 'stuff.json' })

path.upload({
payload: {
foo: 'bar'
}
})
// get the metadata by path
console.log(path.get().data)

// get the data by path
console.log(path.download().data)
path closure

Combined path closure

You can  specify the path and name in either the path or the access method, and in if you specify the path property in both of them, they are combined. Let’s write that again as a combined path.

  const testdrv = drv.files.path({path: 'testdrv'})
testdrv.upload({
path: 'json',
name: 'stuff.json',
payload: {
foo: 'bar'
}
})

// get the metadata by path
console.log(testdrv.get({path: 'json', name: 'stuff.json'}).data)

// get the data by path
console.log(testdrv.download({path: 'json', name: 'stuff.json'}).data)
combined path closure

Ref

You can create a new instance of drv base on an existing instance. Typically you might want to do this either to set a new base (top folder), or to change some of the characteristics of the instance (eg switch off caching).

All of these will produce the same request

  const ref = drv.ref('testdrv')
console.log(drv.files.path({path: 'testdrv/json', name:'stuff.json'}).get().data)
console.log(ref.files.get({ path: 'json', name: 'stuff.json' }).data)
console.log(ref.files.path({path: 'json', name:'stuff.json'}).get().data)
console.log(ref.files.path({path: 'json'}).get({name:'stuff.json'}).data)
ref

Here’s how to use ref to duplicate an instance but with caching off. Any drv constructor options can be passed as the second argument of .ref()

  const ref = drv.ref('', {
noCache: true
})
ref options

Why closures ?

These different ways of specifying the same thing are a very useful way of limiting operations to an area of drive below a particular folder, and reusing code with minimal changes in different modes.

const testPath = drv.files.path({path:"test"})
const prodPath = drv.files.path({path:"prod"})

const doThings = (path) => {

path.list()
path.get({name:'somefile'})
....

}

// for testing
doThings (testPath)

// for prod
doThings (prodPath)
using closures

Listing contents

You can get a list of files like this. All of these are equivalent. In the case of list, the name property refers to the  final folder name – so for example the contents of users/profiles/documents would be specified as {path:’users/profiles’, name:’documents’}

console.log(drv.files.list({path:'testdrv/json'}).data)
console.log(drv.files.path({path:'testdrv', name:'json'}).list().data)
console.log(drv.files.path({path:'testdrv'}).list({name:'json'}).data)
/*
{ items:
[ { kind: 'drive#file',
id: '17Xp-Ae2hsRbvoWs15kF27rpIjuVvJAx6',
name: 'stuff.json',
mimeType: 'application/json' }, ... ]

}
*/
list

Queries in listings

.list() can also take any of the queries described in Search Query Terms for Drive

  console.log(drv.files.list({ 
path: 'testdrv/combined/sub',
query: "fullText contains 'some text' and viewedByMeTime > '2022-07-26'"
}).data)
queries in list

Queries can be combined with any of the closures mentioned earlier.

Paging closure

The drive API has a maximum pageSize of 1000, and after that you’d need to worry about handling paging with the basic APIS. Drv takes care of that and will make whatever calls are necessary to consolidate all the results without you having to do anything. Normally in Server side Apps Script, you just want all the the files at once.

Limit list max number of results

Behind the scenes, it’ll optimize the pageSize and make as many calls as necessary to achieve your requested max. The default max is Infinity.

  const limit = drv.files.path({path: 'testdrv', name: 'json'}).page({max: 5}).list()
console.log(limit.data.items.length) //5

// or you can make any combination of closures for example
const limitPath = drv.files.path({path: 'testdrv'})
const limitPage = limitPath.page({max:10})
const tenItems = limitPage.list({name: 'json'})
console.log(tenItems.data.items.length) //10
limit number of items returned

Do your own paging

There may be a reason that you want to do paging or to limit the number of results per request. Drv Supports this through its paging closure. You specify the max number of items you want back, and Drv returns those, plus a pageToken if there are still more. Here’s how to do your own paging.

  const noCache = drv.ref('', {noCache:true})
const paged = noCache.files.page({ max: 3 }).list()
console.log(paged.data.items.length, paged.pageToken)
// get the next 2
const nextPaged = noCache.files.page({max:2, pageToken: paged.pageToken }).list()
console.log(nextPaged.data.items.length)
your own paging

Caching and paging

You’ll notice I turned caching off to do my own paging in the previous example. This is because pageTokens could be invalid if taken from a previously cached result, so you must use a drv instance with noCache: true. The simplest way to do this is just to get a ref, as in the example above.

Drv will detect if you are trying do your own paging with an instance with caching enabled and give you this error

  const paged =  drv.files.page({ max: 3 }).list()
console.log(paged.data.items.length, paged.pageToken)
// zmVu3DuH3K_R1X58soxCzqLEyDs= was cached
// 3 null

// DONT do paging with caching turned on
const nextPaged = drv.files.page({max:2, pageToken: paged.pageToken }).list()
console.log(nextPaged.data.items.length)

// Error
// you attempted to provide a pageToken, but caching is on - try again with eg. drv.ref("",{noCache: true})
Paging with caching is invalid

SuperFetch reponse

The result of each of these operations is a SuperFetch response that looks like this.

/**
* This is the response from the apply proxied function
* @typedef PackResponse
* @property {boolean} cached whether item was retrieved from cache
* @property {object||null} data parsed data
* @property {number} age age in ms of the cached data
* @property {Blob|| null} blob the recreated blob if there was one
* @property {boolean} parsed whether the data was parsed
* @property {HttpResponse} response fetch response
* @property {function} throw a function to throw on error
* @property {Error || string || null} the error if there was one
* @property {number} responseCode the http response code
*/
result

You’ll mainly be interested in these properties

    // the parsed data - the contents of the requested path
const data = result.data

// whether there was an error
if (result.error) {
// handle error
console.log(result.error)
}

// whether it came from cache
const wasCached = result.cached

// how many ms since it was written to cache
const cacheAge = result.age

// if you want it to throw an error if there was one add .throw() after result
// if no error you'll get the result required
// for example
const data = result.throw().data
common result properties

For more info on this and other response properties see  SuperFetch – a proxy enhancement to Apps Script UrlFetch

Error handling

All Superfetch plugins use the same method of signalling or optionally, reacting to an error.

You can detect an error by checking the result.error property or you can force an automatic throw if an error is detected. Here’s examples of each of these.

// handle the error yourself
const result = drv.files.list()
if (result.error) {
//... handle the error
throw result.error
} else {
// results are in result.data
}

// throw automatically on error
const result = drv.files.list().throw()

// results are in result.data
error handling

Constructor parameters

I’ve mentioned the usual Drv constructor parameters earlier, but here’s a full list of those expected by the constructor.

/**
* @typedef DrvApiOptions
* @property {_SuperFetch} superFetch a superfetch instance
* @property {boolean} noCache whether to cache
* @property {boolean} includeItemsFromAllDrives or just those owned by me
* @property {string} base the base path on the bucket (all paths will be relative to this)
* @property {string} orderBy default order of list results
* @property {boolean} showUrl whether to showUrls when fetching
* @property {object[]} extraParams always add these params
* @property {number} maxWait how long to wait max for rate limit probs
* @property {number} max max number to return
*/
Drv class constructor

Ref parameters

The ref method creates a new Drv instance, but with a new base. The DrvApiOptions argument is the same object as for the Drv constructor, but the base property is ignored.

const ref = drv.ref (newBase, drvApiOptions)
ref parameters

Unit testing

I’ll use Simple but powerful Apps Script Unit Test library to demonstrate calls and responses. It should be straightforward to see how this works and the responsese to expect from calls. These tests demonstrate each of the topics mentioned in this article, as well as some I’ll be covering in a later article, and could serve as a useful crib sheet for the plugin – it’s a long read so you may want to just come back and refer to this.

const testDrv = ({
force = false,
unit } = {}) => {

// control which tests to skip
const skipTest = {
ref: true && !force,
pathFiles: true && !force,
download: true && !force,
convert: true && !force,
list: true && !force,
caching: true && !force,
upload: true && !force,
pathFolders: false && !force,
export: true && !force,
json: true && !force,
getFolders: true && !force,
page: true && !force
}
// get a testing instance
unit = unit || new bmUnitTester.Unit({
showErrorsOnly: true,
maxLog: 200,
showValues: true
})

// import required modules
const { Plugins, SuperFetch } = bmSuperFetch

// use this basic drv setup
const superFetch = new SuperFetch({
fetcherApp: UrlFetchApp,
tokenService: ScriptApp.getOAuthToken,
cacheService: CacheService.getUserCache()
})

//----paging
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true, showUrl: true })
const path = drv.files.path({ path: 'tcf' })
const page = path.page({ max: Infinity })

unit.is('object', typeof page, {
description: 'page is object'
})
unit.is(null, page.list().error, { description: 'drv list' })
unit.is(9, page.list().throw().data.items.length, {
description: 'drv list >=9 folders',
compare: (expect, actual) => actual >= expect
})
unit.is(1, page.list({ query: `name contains 'bruce'` }).throw().data.items.length, {
description: 'only 1 file matching'
})
unit.is(path.page({ max: Infinity }).list().throw().data, path.list().throw().data, {
description: 'Infinite page size matches no constraint'
})
unit.is(path.page({ max: 2 }).list().throw().data.items, path.list().throw().data.items.slice(0, 2), {
description: 'small max took first 2'
})
unit.is(path.page().list().data, path.page({ max: Infinity }).list().data, {
description: 'default max inifinty'
})
unit.is(path.list().data, path.page().list().data, {
description: 'default max inifinty'
})
const { actual: paged } = unit.is(3, path.page({ max: 3 }).list().throw(), {
description: 'get 3 items',
compare: (e, a) => a.data.items.length = e
})
unit.is('string', typeof paged.pageToken, {
description: 'page token is a string'
})
unit.is(path.list().data.items.slice(3), path.page({ pageToken: paged.pageToken }).list().data.items, {
description: 'page token works'
})

}, {
skip: skipTest.page,
description: 'DrvApi list page tests'
})
//---json
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true, showUrl: true })
const asset = { name: 'testdrv' }
const { actual: folder } = unit.not(null, drv.folders.get(asset))
unit.is(asset.name, folder.data.name, {
description: 'folder name matches'
})
const fpath = drv.folders.path({ path: 'testdrv/combined', name: 'sub' })
const { actual: sub } = unit.not(null, fpath.get(), {
description: 'got sub folders with path'
})
unit.is('sub', sub.data.name, {
description: 'folder name matches with path'
})
unit.is(sub.data, drv.folders.get({ id: sub.data.id }).data, {
description: 'id matches path results'
})

}, {
description: 'folder gets',
skip: skipTest.getFolders
})
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true, showUrl: true })
const asset = { name: 'drvassset', path: 'testdrv' }
const fixture = {
a: [1, 2, 3],
b: {
c: 'cccc'
}
}
const textFixture = 'some text'

const { actual: drvasset } = unit.not(null, drv.files.upload({ ...asset, payload: fixture }), {
description: 'upload a json file'
})
unit.is('application/json', drvasset.data.mimeType, {
description: 'json mimetype is correct'
})
unit.is(fixture, drv.files.download(asset).throw().data, {
description: 'json converted back okay'
})
unit.is(fixture, drv.files.download({ id: drvasset.data.id }).throw().data, {
description: 'json converted back okay by id'
})

const assetPath = drv.files.path(asset)

const { actual: drvassetpath } = unit.not(null, assetPath.upload({ payload: fixture }), {
description: 'upload a json file using path'
})
unit.is('application/json', drvassetpath.data.mimeType, {
description: 'json mimetype is correct using path'
})
unit.is(fixture, assetPath.download().throw().data, {
description: 'json converted back okay using path'
})
unit.is(fixture, drv.files.download({ id: drvassetpath.data.id }).throw().data, {
description: 'json converted back okay by id using path'
})

const partialPath = drv.files.path({ path: asset.path })

const { actual: drvpartialpath } = unit.not(null, partialPath.upload({ payload: fixture, name: asset.name }), {
description: 'upload a json file using partial path'
})
unit.is('application/json', drvpartialpath.data.mimeType, {
description: 'json mimetype is correct using partial path'
})
unit.is(fixture, partialPath.download({ name: asset.name }).throw().data, {
description: 'json converted back okay using partial path'
})
unit.is(fixture, drv.files.download({ id: drvpartialpath.data.id }).throw().data, {
description: 'json converted back okay by id using partial path'
})

// plain text
const textPath = drv.files.path({ ...asset, name: 'text.txt' })
const { actual: drvText } = unit.not(null, textPath.upload({ payload: textFixture }), {
description: 'upload a text file'
})
unit.is('text/plain', drvText.data.mimeType, {
description: 'text mimetype is correct'
})
unit.is(textFixture, textPath.download().throw().asString(), {
description: 'text converted back okay '
})
unit.is(textFixture, drv.files.download({ id: drvText.data.id }).throw().asString(), {
description: 'text converted back okay by id'
})

// plain text conmbined
const comPath = drv.files.path({ ...asset, name: 'text.txt' })
const extra = 'combined/sub'
const { actual: comText } = unit.not(null, comPath.upload({ path: extra, payload: textFixture, createIfMissing: true }), {
description: 'upload a text file with complex path'
})
unit.is('text/plain', comText.data.mimeType, {
description: 'text mimetype is correct with complex path'
})
unit.is(textFixture, comPath.download({ path: extra }).throw().asString(), {
description: 'text converted back okay with complex path'
})
unit.is(textFixture, drv.files.download({ name: 'text.txt', path: asset.path '/' extra }).throw().asString(), {
description: 'found text normal from complex path'
})


}, {
description: 'json and text uploads and downloads',
skip: skipTest.json
})

//-----------
// test export
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true, showUrl: true })
const upDrv = drv.ref().ref("uploads")

const fixtures = {
id: '1DlKpVVYCrCPNfRbGsz6N_K3oPTgdC9gQIKi0aNb42uI', // a sheet
sheet: {
path: 'Published Sheets',
name: 'sharedlibraries'
}// its path
}
// closure shortcuts
const path = drv.files.path(fixtures.sheet)
const did = drv.files.path({ id: fixtures.id })

const { actual: idExport } = unit.not(null, did.export({ contentType: MimeType.PDF }).throw(), {
description: 'export sheet to pdf by id'
})

const { actual: pathExport } = unit.not(null, path.export({ contentType: MimeType.PDF }).throw(), {
description: 'export sheet to pdf by path'
})

unit.is(MimeType.PDF, pathExport.blob.getContentType(), {
description: 'blob is correct pdf mimetype'
})

const { actual: blb } = unit.not(null, upDrv.files.path({
name: pathExport.blob.getName()
}).upload({
payload: pathExport.blob
}).throw(), {
description: `wrote exported blob`
})

unit.is(
pathExport.blob.getBytes(),
upDrv.files.download({ id: blb.data.id }).throw().blob.getBytes(), {
description: "downloaded export blob equals original"
})

const { actual: conversion } = unit.not(
null,
path.convert({ contentType: MimeType.PDF, toPath: 'lower', toName: 'x.pdf' }).throw(), {
description: 'direct export'
}
)
unit.is(
pathExport.blob.getBytes(),
upDrv.files.download({ id: conversion.data.id }).throw().blob.getBytes(), {
description: "direct conversion blob equals original"
})
}, {
description: 'test exporting',
skip: skipTest.export
})

//-----------
// testing folders
unit.section(() => {
// create a drive instance
const drv = new Plugins.Drv({ superFetch, noCache: false })
unit.is(null, drv.folders.path({ path: 'tcf', name: 'bruce' }).get().error, {
description: 'drv folder no error'
})
unit.not(null, drv.folders.path({ path: 'tcf' }).get({ name: 'bruce' }).throw().data.id, {
description: 'folderId not blank'
})
unit.not(null, drv.folders.path({ path: 'tcf' }).get({ name: 'bruce' }).throw().data.id, {
description: 'multi path'
})
}, {
skip: skipTest.pathFolders,
description: 'DrvApi folders path tests'
})

//-----------
// testing file paths
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true })
unit.not(null, drv.files.path({ path: 'tcf/bruce', name: 'Mail-bm.csv' }).get().throw().data.id, {
description: 'get file from path'
})
}, {
skip: skipTest.pathFiles,
description: 'DrvApi pathfiles tests'
})

//-----------
// testing file listing
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true })
const path = drv.files.path({ path: 'tcf' })

unit.is('object', typeof path, {
description: 'path domain is object'
})
unit.is(null, path.list().error, { description: 'drv list' })
unit.is(9, path.list().throw().data.items.length, {
description: 'drv list >=9 folders',
compare: (expect, actual) => actual >= expect
})
unit.is(1, path.list({ query: `name contains 'bruce'` }).throw().data.items.length, {
description: 'only 1 file matching'
})
unit.is(true, drv.isNotFound(drv.files.path({ path: 'tcf/nonsense/rubbish' }).list().error), {
description: 'should be a missing folder'
})
}, {
skip: skipTest.list,
description: 'DrvApi list tests'
})

//-----------
// testing download and getting files by id
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true })
const filePath = drv.files.path({ path: 'tcf/bruce', name: 'Mail-bm.csv' })
const r = filePath.download().throw()
unit.is(false, r.parsed, { description: 'csv shouldnt be parsed' })
unit.not(null, r.blob, { description: 'csv should be a blob' })
unit.is('text/csv', r.blob.getContentType(), { description: 'check should be csv' })
const { data: file } = filePath.get().throw()
unit.is(file.mimeType, r.blob.getContentType(), {
description: 'meta data matches blob'
})
// repeat using id
const byId = drv.files.path({ id: file.id })
unit.is(file.id, byId.get().throw().data.id, {
description: 'id matches path metadata'
})
const { blob } = byId.download().throw()
unit.is(file.mimeType, blob.getContentType(), {
description: 'check should still be csv'
})
unit.is(r.blob.getBytes(), blob.getBytes(), {
description: 'should be same blob'
})
}, {
skip: skipTest.download,
description: 'Drvapi download and get by id checks'
})

// testing ref
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true })
const img = drv.files.path({ path: 'images' }).download({ name: 'a.png' }).throw()
// make a ref relative path
const ref = drv.ref('images', { showUrl: true })
const refPath = ref.files.path({ name: 'a.png' })

unit.is(
img.blob.getBytes(),
drv.ref().files.path({ path: 'images', name: 'a.png' }).download().throw().blob.getBytes(), {
description: 'blank ref should be no action'
}
)
unit.is(img.blob.getBytes(), refPath.download().throw().blob.getBytes(), {
description: 'file ref should work'
})
}, {
skip: skipTest.ref,
description: 'drv ref tests'
})

// caching behaving as expected
unit.section(() => {
const drv = new Plugins.Drv({ superFetch, noCache: true })
const img = drv.files.path({ path: 'images', name: 'a.png' }).download().throw()
unit.is(false, img.cached, {
description: 'shouldnt be cached'
})

const drvCached = drv.ref('images', {
noCache: false
})
const cachedPath = drvCached.files.path({
name: 'a.png'
})
// seed cache
const seed = cachedPath.download().throw()
// this one should come from cache
const download = cachedPath.download().throw()
unit.is(img.blob.getBytes(), download.blob.getBytes(), {
description: 'double check cache matches'
})
unit.is(true, download.cached, {
description: 'download should be cached'
})
unit.is(img.blob.getBytes(), download.blob.getBytes(), {
description: 'download cache matches'
})
unit.is(
img.response.getHeaders()['Content-Type'],
download.response.getHeaders()['Content-Type'], {
description: 'fake content type response from cache'
})
unit.is(false, drv.files.path({ path: 'images', name: 'a.png' }).download().throw().cached, {
description: 'using nocache should clear cache'
})
unit.is(false, cachedPath.download().throw().cached, {
description: 'cache should be cleared from previous call'
})
unit.is(true, cachedPath.download().throw().cached, {
description: 'cache should now be active'
})
unit.is(img.blob.getBytes(), cachedPath.download().blob.getBytes(), {
description: 'double check cache matches'
})

}, {
skip: skipTest.caching,
description: 'drv caching tests'
})

// uploading
unit.section(() => {
const drv = new Plugins.Drv({ superFetch })
// get something to upload
const img = drv.files.path({ path: 'images', name: 'a.png' }).download().throw()

// reupload it
const uploadPath = drv.files.path({ path: 'uploads', name: 'a.png' })
const r = uploadPath.upload({ payload: img.blob }).throw()
unit.is(
img.blob.getBytes(),
drv.files.path({ id: r.data.id }).download().throw().blob.getBytes(), {
description: 'check it got written properly'
})

unit.is(
img.blob.getBytes(),
drv.files.path({ path: 'uploads', name: 'a.png' }).download().throw().blob.getBytes(), {
description: 'check it got written properly to the right place'
})

unit.is(
img.blob.getContentType(),
uploadPath.get().data.mimeType, {
description: 'check it got the right mimetype'
})
}, {
skip: skipTest.upload,
description: 'drv upload content tests'
})



unit.report()



}
tests

Next

I’ve only covered some of the drv topics in this article – there are plenty more, so watch out for the next 2 or 3 on this plugin.

Still to come –

  • folder processing
  • metadata
  • wildcards
  • handling big files
  • exporting
  • converting
  • pruning
  • handling rate limit problems

Links

bmSuperFetch: 1B2scq2fYEcfoGyt9aXxUdoUPuLy-qbUC2_8lboUEdnNlzpGGWldoVYg2

IDE

GitHub

bmUnitTester: 1zOlHMOpO89vqLPe5XpC-wzA9r5yaBkWt_qFjKqFNsIZtNJ-iUjBYDt-x

IDE

GitHub

Related

file conversion

Convert any file with Apps Script

The Drive API offers a whole range of conversions between mimeTypes, but it's a little fiddly to figure out exactly ...
Superfetch plugin

Caching, property stores and pre-caching

I've written many times about various Apps Script caching techniques such as how to deal with size limits and use ...
document AI add-on

State management across CardService, HtmlService and Server side Add-ons

Motivation I've been working on a CardService Add-on lately which also uses HtmlService, and also runs quite a few things ...
Secret Manager

SuperFetch Plugin: Cloud Manager Secrets and Apps Script

Smg is a SuperFetch plugin to access the Google Cloud Secrets API. SuperFetch is a proxy for UrlFetchApp with additional ...
Superfetch plugin

SuperFetch caching: How does it work?

SuperFetch is a proxy for UrlFetchApp with additional features such as built-in caching – see SuperFetch – a proxy enhancement ...
superfetch tank drive

SuperFetch plugins: Tank events and appending

Tank and Drv are SuperFetch plugins to emulate streaming and use the Drive REST API with Apps Script. SuperFetch is ...
superfetch tank drive

SuperFetch Plugins: Apps Script streaming with Tank and Drive

Tank and Drv are SuperFetch plugins to emulate streaming and use the Drive REST API with Apps Script. SuperFetch is ...
superfetch tank apps script streaming

SuperFetch Tank Plugin: Streaming for Apps Script

Tank is a SuperFetch plugin to emulate streaming with Apps Script. SuperFetch is a proxy for UrlFetchApp with additional features ...
superfetch drive plugin logo

SuperFetch plugin – Google Drive client for Apps Script – Part 1

Drv is a SuperFetch plugin to access the Google Drive API. SuperFetch is a proxy for UrlFetchApp with additional features ...
Superfetch plugin twitter

SuperFetch – Twitter plugin for Apps Script – Get Follows, Mutes and blocks

Twt is a SuperFetch plugin to easily access to the Twitter v2 API. SuperFetch is a proxy for UrlFetchApp with ...
Superfetch plugin twitter

SuperFetch plugin – Twitter client for Apps Script – Counts

Twt is a SuperFetch plugin to easily access to the Twitter v2 API. SuperFetch is a proxy for UrlFetchApp with ...
goa twitter oauth2 apps script

OAuth2 and Twitter API – App only flow for Apps Script

I covered how to handle the somewhat more complex OAUTH2 authorization flow for the Twitter v2 API (OAuth 2.0 Authorization ...
Superfetch plugin twitter

SuperFetch plugin – Twitter client for Apps Script – Search and Get

Twt is a SuperFetch plugin to easily access to the Twitter v2 API. SuperFetch is a proxy for UrlFetchApp with ...
Goa Oauth2 for Apps Script

Apps Script Oauth2 library Goa: tips, tricks and hacks

Motivation Goa is a library to support OAuth2 for Apps Script connecting to a variety of services, using a variety ...
goa twitter oauth2 apps script

Apps Script Oauth2 – a Goa Library refresher

It's been a few years since I first created the Goa library. Initially it was mainly to provide OAuth2 authorization ...
SuperFetch

SuperFetch plugin – Firebase client for Apps Script

Frb is a SuperFetch plugin to easily access a Firebase Real time database. SuperFetch is a proxy for UrlFetchApp with ...
SuperFetch

SuperFetch plugin – iam – how to authenticate to Cloud Run from Apps Script

SuperFetch is a proxy for UrlFetchApp with additional features - see SuperFetch - a proxy enhancement to Apps Script UrlFetch ...
SuperFetch

SuperFetch – a proxy enhancement to Apps Script UrlFetch

I've written a few articles about JavaScript proxying on here, and I'm a big fan. I also use a lot ...
SuperFetch

Apps script caching with compression and enhanced size limitations

Motivation Caching is a great way to improve performance, avoid rate limit problems and even save money if you are ...

Simple but powerful Apps Script Unit Test library

Why unit testing? There are many test packages for Node (my favorite is ava) and there are also a few ...