Предыстория
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="https://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 запросит права - это нормально. После этого откроется окно, в котором можно наблюдать за прогрессом.
![Модальное окно для визуализации прогресса Модальное окно для визуализации прогресса](https://habrastorage.org/getpro/habr/upload_files/3e5/ff9/e23/3e5ff9e23190741413c1302857efb12a.png)
Ограничения скрипта
Время жизни скрипта ограничено 6 минутами. За это время он успеет создать несколько сотен документов. Можно обойти это ограничение, закинув все функции непосредственно в HTML код модального окна и обращаться к диску по API, но об этом в следующий раз
Есть ограничение на количество ежедневно созданных документов. Рано или поздно будет появляться ошибка Exception: Служба была вызвана слишком много раз за день: docs create. Тогда скрипт можно запустить на следующий день
TL;DR
Всё вышеописанное я собрал в таблицу, которую можно скопировать себе(Файл - Создать копию), запустить(Меню - Создать файлы) и получить к себе на диск несколько сотен файлов. При необходимости процедуру повторить.
Спасибо за внимание. Буду рад получить фидбэк по коду. Удачи!