Реинкарнация скрипта, опубликованного в одном из предыдущих постов. Хранение данных в свойствах скрипта - не самая лучшая идея, о чем недвусмысленно сказано в разделе "ограничения" официальной документации, поэтому в текущем перерождении кода в качестве хранилища данных я решил использовать ScriptDB. Напомню, что первоначальный вариант кода был ответом на вопрос одного из сообществ Google+ и являлся скорее вектором, чем готовым решением. Насколько текущий вариант ближе к решению - судить вам.
Почему.
По причине ограничения в 9kB на размер свойства скрипта, предыдущий вариант кода имел возможность сохранить лишь ограниченное количество информации о файлах и каталогах. В ответ на это ограничение в сообществе разработчиков GAS появилось решение - сохранять данные о каждом объекте (файле, каталоге) в отдельно взятое свойство скрипта... что увеличивает лимит до 500kB.
Короче, ввиду интереса, проявленного к коду, встречайте... в красном углу ринга ... :)
Как это можно применить.
Для начала предлагаю проверить скорость работы Drive Service, для чего пробежимся по всем файлам и каталогам.
Выполняем код, проверяем журнал (Ctrl + Enter), у меня следующие показатели:
Что же будет если попытаться получить свойства всех этих объектов, да еще и рекурсивно (подразумевая, что рекурсия тоже кушает ресурсы путем увеличения стека контекстов)?
Выполним код. Я ждал, пока хватало сил...
Откроем отчет о выполнении (Вид - Отчет о выполнении):
Похоже лимит выполнения скрипта - 6 минут. Прокрутим отчет вверх:
Каждый раз получая свойства файла или каталога в коде у нас происходил запрос на сервер, а плюс еще итераторы... не удивительно, что времени не хватило.
ОК, замахнемся на каталог поменьше.
Выполним код, откроем отчет о выполнении.
Я уложился в 100 секунд. Это прорыв :).
Выполним обновление хранилища - заполним ScriptDB документами JSON, содержащими информацию о свойствах объектов (файлов, каталогов).
Откроем журнал.
Проверим отчет о выполнении.
Посчитаем количество документов в хранилище - то же, что и количество объектов (файлов, каталогов) в целевом каталоге (каталоге, который мы планируем мониторить на предмет изменений свойств дочерних объектов).
Прочие свойства и методы объекта по имени GDriveDog достаточно подробно описаны в коде, разобраться не составит труда.
Заключительный пример - функция, которая будет выполнять всю работу по мониторингу изменений в целевом каталоге и отправке уведомлений на Email - на самом деле единственный код, который необходимо добавить к коду объекта.
Выполняем, открываем отчет о выполнении.
Проверяем почту.
Еще один тест: один файл удалим (но у него в целевом каталоге этажом ниже осталась копия), еще один отмечаем звездочкой, выполняем код, проверяем почту.
Поясню, по порядку:
- файл отмечен звездочкой
- ссылка на файл присутствовала в двух каталогах, из одного мы его удалили, поэтому количество родительских объектов уменьшилось с 2 до 1
- одновременно с удалением ссылки на файл уменьшилось количество файлов в каталоге с 3 до 2
Для запуска функции myFunction() через определенные промежутки времени не забываем создать триггер (по этому и другим вопросам обращаемся к предыдущему посту).
Почему.
По причине ограничения в 9kB на размер свойства скрипта, предыдущий вариант кода имел возможность сохранить лишь ограниченное количество информации о файлах и каталогах. В ответ на это ограничение в сообществе разработчиков GAS появилось решение - сохранять данные о каждом объекте (файле, каталоге) в отдельно взятое свойство скрипта... что увеличивает лимит до 500kB.
Короче, ввиду интереса, проявленного к коду, встречайте... в красном углу ринга ... :)
var GDriveDog = (function () {
function GDriveDog(db, itemId) { // конструктор
try {
this.db = db;
this.dbSize = db.query({}).getSize(); // размер хранилища, заодно проверяем наличие объекта
} catch(e) {
throw new Error(e.message);
return;
}
if (typeof itemId === 'string') {
try{
this.root = DriveApp.getFolderById(itemId);
} catch(e) {
throw new Error(e.message);
return;
}
this.id = itemId;
} else {
this.root = DriveApp.getRootFolder();
this.id = this.root.getId();
}
this.name = this.root.getName();
}
GDriveDog.prototype.getDb = function() { // получаем содержимое хранилища
var ret = [], db = this.db, res = db.query({id: this.id}), item;
while (res.hasNext()) {
item = res.next();
ret = ret.concat(item['data']);
}
return ret;
}
GDriveDog.prototype.clearDb = function() { // удаляем содержимое хранилища
var db = this.db, res = db.query({id: this.id});
while (res.hasNext()) {
db.remove(res.next());
}
return true;
}
GDriveDog.prototype.updateDb = function(arr) { // обновляем содержимое хранилища
this.clearDb(); // удаляем
var db = this.db, data = arr || this.get(); // получаем
var res = db.saveBatch([{id: this.id, data: data}], false); // обновляем
if (db.allOk(res)) {
return true;
}
return false; // не удалось сохранить все объекты
}
GDriveDog.prototype.get = function() { // получаем свойства каталогов и файлов
var ret = [];
getPropsRec(this.root);
return ret;
function getPropsRec(folder) {
var obj = getProps(folder, true); // каталог
if (!match(obj)) ret.push(obj); // пропускаем одни и те же объекты
var files = folder.getFiles();
while (files.hasNext()) { // файлы
var obj = getProps(files.next(), false);
if (!match(obj)) ret.push(obj);
}
var folders = folder.getFolders();
while (folders.hasNext()) { // каталоги
getPropsRec(folders.next()); // рекурсия
}
}
// ищем повторяющиеся данные - на случай если объекты находятся сразу в нескольких каталогах
function match(obj) {
for (var i=0; i<ret.length; i++) {
if (ret[i].id == obj.id) return true;
}
}
// получаем массив id
function getIdArr(iterator) {
var ret = [];
while (iterator.hasNext()) {
ret.push(iterator.next().getId());
}
return ret;
}
// получаем массив email
function getEmailArr(arr) {
var ret = [];
for (var i=0; i<arr.length; i++) {
ret.push(arr[i].getEmail());
}
return ret;
}
// получаем свойства объектов
function getProps(item, isFolder) {
var ret = {
'created': String(item.getDateCreated()),
'description': item.getDescription(),
'editors': getEmailArr(item.getEditors()),
'id': item.getId(),
'updated': String(item.getLastUpdated()),
'name': item.getName(),
'owner': item.getOwner().getEmail(),
'parents': getIdArr(item.getParents()),
'sharingAccess': String(item.getSharingAccess()),
'sharingPermission': String(item.getSharingPermission()),
'size': item.getSize(),
'openUrl': item.getUrl(),
'viewers': getEmailArr(item.getViewers()),
'shareableByEditors': item.isShareableByEditors(),
'starred': item.isStarred(),
'trashed': item.isTrashed()
}
if (isFolder) {
ret.files = getIdArr(item.getFiles());
ret.folders = getIdArr(item.getFolders());
} else {
ret.downloadUrl = item.getDownloadUrl();
ret.type = item.getMimeType();
}
return ret;
}
}
GDriveDog.prototype.compare = function() { // сравниваем свойства объектов с данными хранилища
var ret = [], arr2 = this.get(), arr1 = this.getDb();
for (var i=0; i<arr2.length; i++) { // получаем новые
var gotIt = false;
for (var j=0; j<arr1.length; j++) {
if (arr1[j].id == arr2[i].id) {
gotIt = true;
break;
}
}
if (!gotIt) ret.push({'obj': arr2[i], 'prop': 'inserted'});
}
for (var i=0; i<arr1.length; i++) { // получаем удаленные из корзины и сравниваем
var gotIt = false;
for (var j=0; j<arr2.length; j++) {
if (arr1[i].id == arr2[j].id) {
gotIt = true;
for (var prop in arr1[i]) { // сравниваем свойства
var obj = {};
if (prop == 'updated' || prop == 'created') { // к датам - особый подход
obj.prop1 = new Date(arr1[i][prop]).getTime();
obj.prop2 = new Date(arr2[j][prop]).getTime();
} else if (prop == 'files' || prop == 'folders' || prop == 'parents' || prop == 'editors' || prop == 'viewers') { // к массивам тоже
obj.prop1 = arr1[i][prop].length;
obj.prop2 = arr2[j][prop].length;
} else {
obj.prop1 = arr1[i][prop];
obj.prop2 = arr2[j][prop];
}
if (obj.prop1 != obj.prop2) {
ret.push({'obj': arr2[j], 'prop': prop});
}
}
break;
}
}
if (!gotIt) ret.push({'obj': arr1[i], 'prop': 'deleted'}); // удаленные из корзины
}
this.updateDb(arr2);
return ret;
}
GDriveDog.prototype.getMessage = function(arr) { // собираем сообщение
var ret = [], updated = arr || this.compare();
for (var i=0; i<updated.length; i++) {
var arr = [];
switch (updated[i].prop) {
case 'inserted':
arr.push('Новый объект:');
arr.push('- дата создания - ' + updated[i].obj.created);
break;
case 'deleted':
arr.push('Объект удален:');
break;
case 'created':
arr.push('Изменилась дата создания:');
arr.push('- дата создания - ' + updated[i].obj.created);
break;
case 'updated':
arr.push('Изменилась дата редактирования:');
arr.push('- дата редактирования - ' + updated[i].obj.updated);
break;
case 'description':
arr.push('Изменилось описание:');
arr.push('- описание - ' + updated[i].obj.description);
break;
case 'name':
arr.push('Изменилось имя:');
break;
case 'owner':
arr.push('Изменился владелец:');
arr.push('- владелец - ' + updated[i].obj.owner);
break;
case 'parents':
arr.push('Изменилось количество родительских объектов:');
arr.push('- количество - ' + updated[i].obj.parents.length);
break;
case 'size':
arr.push('Изменился размер:');
arr.push('- размер - ' + updated[i].obj.size);
break;
case 'editors':
arr.push('Изменилось количество редакторов:');
arr.push('- количество - ' + updated[i].obj.editors.length);
break;
case 'viewers':
arr.push('Изменилось количество обозревателей:');
arr.push('- количество - ' + updated[i].obj.viewers.length);
break;
case 'files':
arr.push('Изменилось количество файлов:');
arr.push('- количество - ' + updated[i].obj.files.length);
break;
case 'folders':
arr.push('Изменилось количество каталогов:');
arr.push('- количество - ' + updated[i].obj.folders.length);
break;
case 'starred':
if (updated[i].obj.starred) {
arr.push('Объект отмечен звездочкой:');
} else {
arr.push('Отметка объекта звездочкой снята:');
}
break;
case 'trashed':
if (updated[i].obj.trashed) {
arr.push('Объект перемещен в корзину');
} else {
arr.push('Объект восстановлен из корзины');
}
break;
default:
arr.push('Свойства объекта изменились:');
arr.push('- свойство - ' + updated[i].prop);
arr.push('- значение - ' + updated[i].obj[updated[i].prop]);
break;
}
arr.push('- имя - ' + updated[i].obj.name);
arr.push('- ссылка - ' + updated[i].obj.openUrl);
ret.push(arr.join('\n'));
}
return ret;
}
return GDriveDog;
})();
Как это можно применить.
Для начала предлагаю проверить скорость работы Drive Service, для чего пробежимся по всем файлам и каталогам.
var t = Date.now(), i = 0, j = 0;
var files = DriveApp.getFiles(), folders = DriveApp.getFolders();
while (files.hasNext()) {
i++;
files.next();
}
while (folders.hasNext()) {
j++;
folders.next();
}
Logger.log('Файлов: ' + i + '\nКаталогов: ' + j + '\nВремя: ' + (Date.now() - t) + ' ms');
Выполняем код, проверяем журнал (Ctrl + Enter), у меня следующие показатели:
Что же будет если попытаться получить свойства всех этих объектов, да еще и рекурсивно (подразумевая, что рекурсия тоже кушает ресурсы путем увеличения стека контекстов)?
// первый аргумент - ScriptDb var gdd = new GDriveDog(ScriptDb.getMyDb()); gdd.get();
Выполним код. Я ждал, пока хватало сил...
Откроем отчет о выполнении (Вид - Отчет о выполнении):
Похоже лимит выполнения скрипта - 6 минут. Прокрутим отчет вверх:
Каждый раз получая свойства файла или каталога в коде у нас происходил запрос на сервер, а плюс еще итераторы... не удивительно, что времени не хватило.
ОК, замахнемся на каталог поменьше.
// второй аргумент - id каталога var gdd = new GDriveDog(ScriptDb.getMyDb(), '0B0YcK5KeNe1tYUhsajN4bVpiZWs'); gdd.get();
Выполним код, откроем отчет о выполнении.
Я уложился в 100 секунд. Это прорыв :).
Выполним обновление хранилища - заполним ScriptDB документами JSON, содержащими информацию о свойствах объектов (файлов, каталогов).
var gdd = new GDriveDog(ScriptDb.getMyDb(), '0B0YcK5KeNe1tYUhsajN4bVpiZWs');
Logger.log('Обновление хранилища: ' + gdd.updateDb());
Откроем журнал.
Проверим отчет о выполнении.
Посчитаем количество документов в хранилище - то же, что и количество объектов (файлов, каталогов) в целевом каталоге (каталоге, который мы планируем мониторить на предмет изменений свойств дочерних объектов).
var gdd = new GDriveDog(ScriptDb.getMyDb(), '0B0YcK5KeNe1tYUhsajN4bVpiZWs');
Logger.log('Количество документов: ' + gdd.dbSize);
Выполним код, проверим журнал.
Прочие свойства и методы объекта по имени GDriveDog достаточно подробно описаны в коде, разобраться не составит труда.
Заключительный пример - функция, которая будет выполнять всю работу по мониторингу изменений в целевом каталоге и отправке уведомлений на Email - на самом деле единственный код, который необходимо добавить к коду объекта.
function myFunction() {
var gdd = new GDriveDog(ScriptDb.getMyDb(), '0B0YcK5KeNe1tYUhsajN4bVpiZWs');
var msg = gdd.getMessage();
if (msg.length > 0) {
msg.unshift('Количество объектов: ' + msg.length);
GmailApp.sendEmail(
Session.getActiveUser().getEmail(),
'Обнаружены изменения на Диске Google, каталог - ' + gdd.name,
msg.join('\n')
);
}
}
Выполняем, открываем отчет о выполнении.
Проверяем почту.
Еще один тест: один файл удалим (но у него в целевом каталоге этажом ниже осталась копия), еще один отмечаем звездочкой, выполняем код, проверяем почту.
Поясню, по порядку:
- файл отмечен звездочкой
- ссылка на файл присутствовала в двух каталогах, из одного мы его удалили, поэтому количество родительских объектов уменьшилось с 2 до 1
- одновременно с удалением ссылки на файл уменьшилось количество файлов в каталоге с 3 до 2
Для запуска функции myFunction() через определенные промежутки времени не забываем создать триггер (по этому и другим вопросам обращаемся к предыдущему посту).












Комментариев нет:
Отправить комментарий
Комментарий будет опубликован после модерации