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.

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

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 ...
Read More
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 ...
Read More
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 ...
Read More
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 ...
Read More
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 ...
Read More
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 ...
Read More
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 ...
Read More
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 ...
Read More
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 for ...
Read More
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 ...
Read More