Предыстория
Google изменяет политику хранения данных с 1 июня 2021 года. Вкратце: документы и фото теперь станут полновесными и будут учитываться в общей квоте 15Гб. К тому же, при неактивности аккаунта более двух лет, Google может удалить ваши данные.
Я часто работаю с Google документами, и при активном использовании дисковая квота закончится довольно быстро. Но есть и хорошая новость: документы, созданные до 1 июня 2021 года так и останутся невесомыми, поэтому вы не получите превышение квоты в одночасье.
У меня сразу возникла мысль сделать документов "в запас". Ниже я расскажу, как это можно осуществить, не тратя много времени и сил.
Пишем скрипт, создающий документы
Скрипт буду писать с помощью Google Apps Script.
Создаём новую таблицу Google, заходим в редактор скриптов (Инструменты - Редактор скриптов).
Создаём три файла
-
main.gs - основной код для создания файлов
-
menu.gs - код для создания пользовательского меню при открытии таблицы
-
index.html - шаблон страницы для отображения информации
Сначала в файле menu.gs создаём функцию onOpen(). Это простой триггер, который выполняется при открытии таблицы. Его задача - создать пользовательское меню, из которого можно запустить функцию.
function onOpen(e) { // Выполняется при открытии таблицы SpreadsheetApp.getUi() // Получаем интерфейс пользователя .createMenu('Меню')// Создаём меню .addItem('Создать документы', 'main') // Создаём команду меню .addToUi();// Добавляем меню в интерфейс}
В файле main.gs создадим функцию main(), которая и будет запускаться с кнопки в меню.
function main() { // Меню - Создать документы // Создаём HTML документ из шаблона let template = HtmlService.createTemplateFromFile(`index`); // Показываем модальное окно с HTML SpreadsheetApp.getUi() .showModelessDialog(template.evaluate(),`Создаю документы...`);}
Пора разобраться с index.html. это обычный HTML файл, который отобразится в модальном окне. там можно использовать стили, скрипты и т.п.
index.html
<!doctype html><html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="http://personeltest.ru/aways/maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <script> let timer = function(){ // Обслуживает таймер // Получаем текущее время let now = (new Date()).getTime(); // Задаём режим обновления таймера - 1 раз в секунду setInterval(function(){ // Получаем прошедшее время. now будет доступно из-за замыкания. let time = (new Date()).getTime() - now; // Вычисляем минуты let minutes = Math.floor(time/60000); // И секунды let seconds = Math.floor(time%60000/1000); // Обновляем данные в поле lifeTime updateData("lifeTime", `${minutes<10?"0"+minutes:minutes}:${seconds<10?"0"+seconds:seconds}`); },1000); }; updateData = function(id, value){ // Обновляет информацию в одном элементе по id let element = document.getElementById(id); if (element) { element.innerHTML = value; }; }; refreshData = function(){ // Обновляем все данные google.script.run.withSuccessHandler(function(data){ updateData("createTables", data.createTables); // Таблиц создано: updateData("createDocs", data.createDocs);// Документов создано: updateData("createForms", data.createForms);// Форм создано updateData("createSlides", data.createSlides);// Презентаций создано updateData("log", data.log);// Лог }).getData(); // Получаем данные }; setTimeout(setInterval(refreshData, 2000),1000); // Данные обновляем раз в 2 секунды timer();// Запуск таймера google.script.run.doMagic();// Запуск функции для создания документов </script> </head> <body> <div class=".container bg-dark text-white text-center"> <div class="row"> <div class="col"> Таблиц </div> <div class="col"> Документов </div> <div class="col"> Форм </div> <div class="col"> Презентаций </div> </div> <div class="row"> <div class="col" id="createTables"> 0 </div> <div class="col" id="createDocs"> 0 </div> <div class="col" id="createForms"> 0 </div> <div class="col" id="createSlides"> 0 </div> </div> </div> <div class=".container bg-dark text-white"> <div class="row"> <div class="col text-right" id="label_lifeTime"> Время работы: </div> <div class="col text-left" id="lifeTime"> 00:00 </div> </div> </div> <div bg-dark text-white id="label_log">Лог: </div> <ul class="list-group" id="log"> </ul> </body></html>
В ней подключаем стили, создаём скрипт, в котором три функции:
-
updateData(id, value) - ищет на странице элемент по id и обновляет содержимое
-
refreshData() - обновляет все данные на странице, кроме таймера
-
timer() - обслуживает таймер
Для создания документов в файле main.gs создаём универсальную функцию.
function create()
const FILES_TO_CREATE = 50;function create(filesToCreate = FILES_TO_CREATE, folderId, prefix="file_", app, key) { // Получаем словарь ключ-значение для текущего скрипта. let props = PropertiesService.getScriptProperties(); // Получаем значение для ключа data. Преобразуем в объект let data = JSON.parse(props.getProperty(`data`)); try{ // Получаем директорию по id let folder = DriveApp.getFolderById(folderId); for(var i=0; i<filesToCreate; i++){ // Создаём filesToCreate документов // Создаём документ, получаем его id let ssId = app.create(`${prefix}${(new Date()).getTime()}`).getId(); // Получаем файл по его id let ss = DriveApp.getFileById(ssId); // Копируем файл в папку folder.addFile(ss); // Удаляем файл из корневой папки DriveApp.getRootFolder().removeFile(ss); // Увеличиваем счётчик созданных файлов data[key]=1+data[key]; // Сохраняем новые данные props.setProperty(`data`, JSON.stringify(data)); }; }catch(err){ // В случае ошибки - пишем её в лог logToHtml(`Error: ${err}`, LOG_TYPES.danger); }; // Возвращаем из функции количество созданных файлов return +i;};
И конкретные реализации - для таблиц, документов, форм и презентаций.
Обёртки для функции create()
function createSheets(key) { // Получаем id папки для таблиц let folderId = PropertiesService.getScriptProperties().getProperty(`sheetsFolder`); // Создаём нужное количество таблиц let count = create(FILES_TO_CREATE, folderId, `sheet_`, SpreadsheetApp, key); // Сохраняем в лог logToHtml(`${count} sheets were created`, LOG_TYPES.success);}function createDocs(key) { let folderId = PropertiesService.getScriptProperties().getProperty(`docsFolder`); let count = create(FILES_TO_CREATE, folderId, `doc_`, DocumentApp, key); logToHtml(`${count} docs were created`, LOG_TYPES.success);}function createForms(key) { let folderId = PropertiesService.getScriptProperties().getProperty(`formsFolder`); let count = create(FILES_TO_CREATE, folderId, `form_`, FormApp, key); logToHtml(`${count} forms were created`, LOG_TYPES.success);}function createSlides(key) { let folderId = PropertiesService.getScriptProperties().getProperty(`slidesFolder`); let count = create(FILES_TO_CREATE, folderId, `slide_`, SlidesApp, key); logToHtml(`${count} slides were created`, LOG_TYPES.success);}
Далее делаем функцию для создания папок.
function createFolders(){ // Получаем словарь ключ-значение для текущего скрипта let props = PropertiesService.getScriptProperties(); // Задаём структуру папок let folders = [ {key:`rootFolder`, name:`Прозапас` }, {key:`sheetsFolder`, name:`Sheets`, parentFolder:`rootFolder`}, {key:`docsFolder`, name:`Docs`, parentFolder:`rootFolder`}, {key:`formsFolder`, name:`Forms`, parentFolder:`rootFolder`}, {key:`slidesFolder`, name:`Slides`, parentFolder:`rootFolder`}, ]; // Проходим по структуре и создаём папки folders.forEach(folder=>{ if (!props.getProperty(folder.key)){ // Если папка ещё не создана // Если есть параметр rootFolder, то используем его, иначе выбираем корневую папку let parentFolder = folder.parentFolder?DriveApp.getFolderById(props.getProperty(folder.parentFolder)):DriveApp.getRootFolder(); // Создаём папку let folderId = parentFolder.createFolder(folder.name).getId(); // Сохраняем информацию о ней props.setProperty(folder.key, folderId); }; });}
И функцию для создания всех типов документов:
Основная функция, которая запускает создание папок и документов
function doMagic(){ let props = PropertiesService.getScriptProperties(); // Структура данных let data = { createTables:0, createDocs:0, createForms:0, createSlides:0, startTime:new Date(), log:``, }; // Сохраняем данные в словарь скрипта props.setProperty(`data`, JSON.stringify(data)); try{ createFolders(); // Создаём папки createSheets(`createTables`);// Создаём таблицы createDocs(`createDocs`);// Создаём документы createForms(`createForms`);// Создаём формы createSlides(`createSlides`);// Создаём презентации // Сообщаем, что всё готово SpreadsheetApp.getUi().alert(`Готово!`); }catch(err){ // При ошибке сообщаем об этом SpreadsheetApp.getUi().alert(`Ошибка! ${err}`); };};
Остаётся создать функцию для получения данных - так модальное окно может получить актуальные данные для отображения.
function getData(){ // Получает данные из словаря // Получаем словарь ключ-значение let props = PropertiesService.getScriptProperties(); // Получаем нужные данные и преобразуем в объект let data = JSON.parse(props.getProperty(`data`)); return data; //Возвращаем данные};
И функция для записи строки в лог:
const LOG_TYPES = { // Типы сообщений. Взято из bootstrap primary: "primary", secondary: "secondary", success: "success", danger: "danger", warning: "warning", info: "info", };function logToHtml(log, type = LOG_TYPES.primary){ // Получаем данные let data = getData(); // Добавляем li тег (лог отображается в виде списка) с данными data.log+=`<li class="list-group-item text-${type}">${log}</li>\n`; // Сохраняем данные let props = PropertiesService.getScriptProperties(); props.setProperty(`data`, JSON.stringify(data));};
Что получилось
Остаётся только запустить скрипт из меню. При первом запуске Google запросит права - это нормально. После этого откроется окно, в котором можно наблюдать за прогрессом.
Модальное окно для визуализации прогрессаОграничения скрипта
-
Время жизни скрипта ограничено 6 минутами. За это время он успеет создать несколько сотен документов. Можно обойти это ограничение, закинув все функции непосредственно в HTML код модального окна и обращаться к диску по API, но об этом в следующий раз
-
Есть ограничение на количество ежедневно созданных документов. Рано или поздно будет появляться ошибка Exception: Служба была вызвана слишком много раз за день: docs create. Тогда скрипт можно запустить на следующий день
TL;DR
Всё вышеописанное я собрал в таблицу, которую можно скопировать себе(Файл - Создать копию), запустить(Меню - Создать файлы) и получить к себе на диск несколько сотен файлов. При необходимости процедуру повторить.
Спасибо за внимание. Буду рад получить фидбэк по коду. Удачи!