Google Apps Script snippets ᕦʕ •ᴥ•ʔᕤ

Using the Google Apps Script Cache Service for objects/files above 100KB

Using the Google Apps Script Cache Service for objects above 100KB

Snippet

This code is a fork of pilbot/9d0567ef1daf556449fb and glade-at-gigwell/4e080771d685fbf1908edbd98eb2d88c

index.js
/**
 * The fork of
 *   https://gist.github.com/pilbot/9d0567ef1daf556449fb,
 *   https://gist.github.com/glade-at-gigwell/4e080771d685fbf1908edbd98eb2d88c
 */

/**
 * Using the Google Apps Script Cache Service for objects above 100Kb
 */
class ChunkyCache {
  /**
   *
   * @param {GoogleAppsScript.Cache.Cache} cache
   */
  constructor(cache) {
    this.cache = cache || CacheService.getScriptCache();
    this.chunkSize = 100 * 1024;
  }
  /**
   * Gets the cached value for the given key, or null if none is found.
   * https://developers.google.com/apps-script/reference/cache/cache#getkey
   *
   * @param {string} key
   * @returns {any} A JSON.parse result
   */
  get(key) {
    const superKeyjson = this.cache.get(key);
    if (superKeyjson === null) return null;
    const superKey = JSON.parse(superKeyjson);
    const cache = this.cache.getAll(superKey.chunks);
    const chunks = superKey.chunks.map((key) => cache[key]);
    if (
      chunks.every(function (c) {
        return c !== undefined;
      })
    ) {
      return JSON.parse(chunks.join(''));
    }
  }
  /**
   * Adds a key/value pair to the cache.
   * https://developers.google.com/apps-script/reference/cache/cache#putkey,-value
   *
   * @param {string} key
   * @param {string} value
   * @param {number} expirationInSeconds
   */
  put(key, value, expirationInSeconds = 600) {
    const json = JSON.stringify(value);
    const chunks = [];
    const chunkValues = {};
    let index = 0;
    while (index < json.length) {
      const chunkKey = key + '_' + index;
      chunks.push(chunkKey);
      chunkValues[chunkKey] = json.substr(index, this.chunkSize);
      index += this.chunkSize;
    }
    const superKey = {
      chunkSize: this.chunkSize,
      chunks: chunks,
      length: json.length,
    };
    chunkValues[key] = JSON.stringify(superKey);
    this.cache.putAll(chunkValues, expirationInSeconds);
  }
  /**
   * Removes an entry from the cache using the given key.
   *
   * @returns {null}
   */
  remove(key) {
    const superKeyjson = this.cache.get(key);
    if (superKeyjson !== null) {
      const superKey = JSON.parse(superKeyjson);
      this.cache.removeAll([...superKey.chunks, key]);
    }
    return null;
  }
}

/**
 * Using the Google Apps Script Cache Service for blobs
 */
class BlobCache extends ChunkyCache {
  /**
   * Extends of ChunkyCache
   */
  constructor(cache) {
    super(cache);
    this.splitter = '1c16c2eb-a4a7-4cac-bf79-064cedfbb346';
    this.defaultName = '772fff0c-4207-4893-834c-aec73c498eeb';
    this.prefixSize = 250;
  }
  /**
   * Adds a key/blob pair to the cache.
   *
   * @param {string} key
   * @param {GoogleAppsScript.Base.Blob | GoogleAppsScript.Base.BlobSource} blob
   * @param {number} expirationInSeconds
   */
  putBlob(key, blob, expirationInSeconds = 600) {
    let name = blob.getName();
    if (name === null) name = this.defaultName;
    const contentType = blob.getContentType();
    const prefix = [name, this.splitter, contentType, this.splitter]
      .join('')
      .padEnd(this.prefixSize, ' ');
    const data = prefix + Utilities.base64Encode(blob.getBytes());
    this.put(key, data, expirationInSeconds);
  }
  /**
   * Gets the cached blob for the given key, or null if none is found.
   *
   * @param {*} key
   */
  getBlob(key) {
    const data = this.get(key);
    if (data !== null) {
      const prefix = data.slice(0, this.prefixSize).split(this.splitter);
      const blob = Utilities.newBlob('');
      blob.setBytes(Utilities.base64Decode(data.slice(this.prefixSize)));
      if (prefix[0] !== this.defaultName) blob.setName(prefix[0]);
      blob.setContentType(prefix[2]);
      return blob;
    }
    return null;
  }
}

Run it

run.js
/* eslint-disable no-console */
/* global ChunkyCache BlobCache */

/**
 * Runs the snippet. Caches a 200KB string.
 */
function run() {
  const value = DriveApp.getFileById('19WGODj4pQ-VgwI2unZfQCxX25D4xaB4Q')
    .getBlob()
    .getDataAsString();
  new ChunkyCache().put('key', value);

  const cacheValue = new ChunkyCache().get('key');
  new ChunkyCache().remove('key');
  console.log(value.length, cacheValue.length);
}

/**
 * Runs the snippet. Caches an image.
 * Then creates a copy of this from the cache
 */
function runCacheImage() {
  const data = DriveApp.getFileById(
    '14Sm76a_dJI4eKtSbfCfDq4gVjcUzREE7'
  ).getBlob();
  new BlobCache().putBlob('myfile1', data);

  DriveApp.createFile(new BlobCache().getBlob('myfile1'));
  new BlobCache().remove('myfile1');
}

/**
 * Runs the test. Caches a data from the Google Sheet.
 * Compares the original data and the cache data
 */
function runTest() {
  const sheet = SpreadsheetApp.openById(
    '19TlsK5ICOuzrv07OSw5n3KtYbTHn00p1iFY6QArBWTM'
  ).getSheetByName('aka FuzzyMatch');
  const data = sheet.getDataRange().getValues();
  const chunky = new ChunkyCache(CacheService.getUserCache());
  chunky.put('Data', data);
  const check = chunky.get('Data');
  console.log(
    data.length,
    check.length,
    JSON.stringify(data) === JSON.stringify(check) // It's not work for Date values
  );
}

Minimized version

For quick use, you can use a more transparent version

index.min.js
class ChunkyCache {
    constructor(cache) {
        this.cache = cache || CacheService.getScriptCache(), this.chunkSize = 102400;
    }
    get(superKeyjson) {
        superKeyjson = this.cache.get(superKeyjson);
        if (null === superKeyjson) return null;
        const superKey = JSON.parse(superKeyjson), cache = this.cache.getAll(superKey.chunks), chunks = superKey.chunks.map(key => cache[key]);
        return chunks.every(function(c) {
            return void 0 !== c;
        }) ? JSON.parse(chunks.join("")) : void 0;
    }
    put(key, superKey, expirationInSeconds = 600) {
        const json = JSON.stringify(superKey), chunks = [], chunkValues = {};
        let index = 0;
        for (;index < json.length; ) {
            var chunkKey = key + "_" + index;
            chunks.push(chunkKey), chunkValues[chunkKey] = json.substr(index, this.chunkSize), 
            index += this.chunkSize;
        }
        superKey = {
            chunkSize: this.chunkSize,
            chunks: chunks,
            length: json.length
        };
        chunkValues[key] = JSON.stringify(superKey), this.cache.putAll(chunkValues, expirationInSeconds);
    }
    remove(key) {
        var superKey = this.cache.get(key);
        return null !== superKey && (superKey = JSON.parse(superKey), this.cache.removeAll([ ...superKey.chunks, key ])), 
        null;
    }
}

class BlobCache extends ChunkyCache {
    constructor(cache) {
        super(cache), this.splitter = "1c16c2eb-a4a7-4cac-bf79-064cedfbb346", this.defaultName = "772fff0c-4207-4893-834c-aec73c498eeb", 
        this.prefixSize = 250;
    }
    putBlob(key, data, expirationInSeconds = 600) {
        let name = data.getName();
        null === name && (name = this.defaultName);
        const contentType = data.getContentType();
        data = [ name, this.splitter, contentType, this.splitter ].join("").padEnd(this.prefixSize, " ") + Utilities.base64Encode(data.getBytes());
        this.put(key, data, expirationInSeconds);
    }
    getBlob(prefix) {
        const data = this.get(prefix);
        if (null === data) return null;
        {
            prefix = data.slice(0, this.prefixSize).split(this.splitter);
            const blob = Utilities.newBlob("");
            return blob.setBytes(Utilities.base64Decode(data.slice(this.prefixSize))), prefix[0] !== this.defaultName && blob.setName(prefix[0]), 
            blob.setContentType(prefix[2]), blob;
        }
    }
}

#cache