Заставить кеш сохраниться на диске
Написал расширение с громким названием Cache Fixer.
Выдержка из about/help (пока пишу только, жду перевода it-IT):
Принцип работы
Браузер работает с кешем следующим образом:
- При старте браузера с диска в память загружается индексный файл кеша (CACHE_MAP);
- В начале файла, расположенного на диске, ставится флаг негодности ("битости") кеша;
- ... (работа) ...
- При завершении работы индексный файл, находящийся в памяти, записывется обратно на диск. Уже без битого" флага (таким образом, если работа была завершена аварийно, то этот шаг будет пропущен и на диске останется файл с "битым" флагом);
- При следующем старте браузер проверяет "битый" флаг в файле CACHE_MAP и, если таковой присутствует, то полностью очищает кеш.
Cache Fixer сбрасывает этот флаг в начале старта браузера, предотвращая очистку кеша.
Внимание! Так как информация о текущем состоянии кеша при работе браузера загружается в память и лишь на выходе записывается на диск, то все кешированные данные аварийно завершённого сеанса работы будут потеряны; останется информация только о предыдущих сеансах.
В связи с этим возможна некоторая нелогичность работы с кешированными данными аварийно завершённого сеанса после перезапуска браузера. Некоторые считают, что это меньшее зло, чем потеря всего кеша. Если вы считаете иначе, то просто не пользуйтесь этим расширением.
Если кратко, то там компонента, подвешенная на событие "profile-after-change", ищущая CACHE_MAP и перезаписывающая его без "dirty bit".
Сам же браузер снимает этот бит в nsDiskCacheMap::Close.
В баге 105843 можно найти другие варианты решения этой проблемы, мне же интересен такой момент: можно ли прикрутить таймер (это не проблема) и заставлять браузер (а вот это не знаю как сделать) сбрасывать текущее состояние CACHE_MAP например, раз в десять минут?
А то сеансы работы бывают достаточно продолжительными и терять их данные при аварийном завершении работы не слишком приятно.
Вы ведь, наверное, хотите не флаг менять, а кеш из памяти на диск сбрасывать?
var cache = Components.classes['@mozilla.org/network/cache-service;1'] .getService(Components.interfaces.nsICacheService); cache.shutdown(); cache.init();
Вроде бы делает то, что надо?
Радость была немного омрачена тем, что из nsICacheService убрали init/shutdown ("Methods called by nsCacheProfilePrefObserver") и уже этот observer вызывает nsCacheService::OnProfileShutdown/OnProfileChanged.
Ниже кусок кода; посмотрел, вроде дисковый кэш начинает обновляться в процессе работы, но один вопрос не даёт покоя: корректно ли это решение? Не приведёт ли к каким-то побочным эффектам?
function nsCacheFixer() { CACHE_FIXER_OBSERVER_SERVICE.addObserver(this,"profile-after-change",false); this._timeOut = 600000; // 10 min } nsCacheFixer.prototype = { // ..................................... // Для последующего изменения пользователем через настройки _setTimeout: function(min) { this._timeOut = (min * 60000); this._setTimer(); }, _setTimer: function() { if (this._timer) { this._timer.cancel(); } else { this._timer = CACHE_FIXER_TIMER_CONTRACTID.createInstance(nsITimer); } this._timer.initWithCallback(this, this._timeOut, nsITimer.TYPE_REPEATING_SLACK); }, // nsITimer notify: function() { CACHE_FIXER_OBSERVER_SERVICE.notifyObservers(null, "profile-before-change", false); CACHE_FIXER_OBSERVER_SERVICE.notifyObservers(null, "profile-after-change", false); }, // nsIObserver observe: function(aSubject, aTopic, aData) { if (aTopic == "profile-after-change") { if (this._notFirstTime) return; this._notFirstTime = true; this._setTimer(); // ... работаем с _CACHE_MAP_ .......................... } }, // .....................................
Вроде всё корректно, вы симулируете переход на другой профиль. Боюсь, что вам придётся ждать, пока о проблемах не доложат пользователи.
Вот только: а исправить dirty flag вы не должны? Я ведь так понимаю, что после вызова OnProfileChanged() он опять установлен?
Фокус не удался.
Боюсь, что вам придётся ждать, пока о проблемах не доложат пользователи.
Пользователи доложили о потерях cookies, списка доверенных сайтов, отказе сохранения сессий расширением TabMix и прочих прелестях.
Т.к. я "плаваю" в Gecko и вообще не программист :), то могу лишь предположить, что здесь поможет
- или конкретное указание на объект XPCOM при notifyObservers, чтобы не беспокоить остальные (как найти определённый observer?)
- или каким-то образом всё-таки можно добраться до "прямого" вызова shutdown/init (придётся писать что-то на C++?)
По первому пункту.
Пытался найти observer, который относится к CacheService, следующим способом:
var enumerator = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService) .enumerateObservers("profile-after-change"); var tempO = new Array(); var tempObs = new Array(); var i=0; while (enumerator.hasMoreElements()) { tempO[i] = enumerator.getNext(); tempObs[i] = tempO[i].QueryInterface(Components.interfaces.nsIObserver); i++; }
Всего их там в 4 штуки, вместе с моим, (FF1.1/20050531), но указать на какой-то конкретный для вызова
tempObs[?].observe(tempO[?], "profile-before-change", false); tempObs[?].observe(tempO[?], "profile-after-change", false);
не получилось. Скорее всего, просто потому, что в ходе работы происходит удаление/добавление observer'ов, например, при вызове
// nsCacheService.cpp nsresult nsCacheProfilePrefObserver::Remove() { .... rv = observerService->RemoveObserver(this, "profile-before-change"); .... }
и получается, что нельзя надеяться на то, что на всём протяжении работы браузера tempObs[x] так и останется tempObs[x], а не станет tempObs[y]. В общем, суть проблемы — "безымянность" enumerator, нельзя определить что именно представляет собой конкретный tempObs[x].
По пункту второму.
Насколько тяжело (и возможно ли вообще) "влезть" в nsCacheService и расширить интерфейс, сделав доступным вызов shutdown/init?
Да уж, проблема :-(
Без перекомпиляции добавить доступ к shutdown/init невозможно. Боюсь, что и с C++ кодом это не получится без большууууууущего хака. Нужный обсервер тоже распознать сложно, никаких "знаков различия" у него нет. Но раз уж вы занялись хаками, можете попробовать такой:
var enumerator = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService) .enumerateObservers("profile-after-change"); while (enumerator.hasMoreElements()) { var observer = enumerator.getNext() .QueryInterface(Components.interfaces.nsIObserver); try { observer.observe(null, 'nsPref:changed', null); } catch (e) { if (e.name == 'NS_ERROR_INVALID_POINTER') alert("Попался, голубчик!!!"); } }
У меня в Mozilla определены 6 обсерверов для этого события, но остальные либо просто не слушают изменения установок, либо игнорируют сообщение из-за неправильного названия установки. Только nsCacheProfilePrefObserver пытается сделать QueryInterface() на aSubject, исходя из того, что там nsIPrefBranch — и выдаёт исключение, соответственно. По этому исключению его можно распознать и вызвать observe() уже целенаправленно.
Хак не заставил себя долго ждать и, так как я уже успел до этого подсадить свой observer на "nsPref:changed", а при тестировании про это забыл, то сначала удивлялся, почему так часто перезагружаются установки. :) Но, там решаемо, благо, что своё.
Жаль, конечно, что не предусмотрена идентификация этих наблюдателей.
"profile-before-change": хорошо, значит, будем его пытать.
Спасибо за очередную наводку.
Решил пройтись по всем observer на "profile-before-change", "profile-after-change" и "xpcom-shutdown", для вычисления того, который сидит на всех трёх событиях. Получил ровно 1шт. + оставил подстраховку в виде try/catch на "nsPref:changed". Много времени этот поиск не занимает, да и после обнаружения заветного наблюдателя его можно запомнить в this._cacheObserver. Одни положительные моменты (те же закладки перестают перезагрузаться), всё отлично. В добавок, если есть методы init/shutdown для кеша, то их вызываем сразу, без поисков всяких observer.
На одном из тестов выставил время сброса кеша в 10 секунд, попытался скачать такой вот файл
<?php echo("1"); sleep(11); echo("2"); ?>
через "Save Link As...". Не получилось (даже окно выбора пути сохранения файла не показалось). Подумал, что с менеджером загрузок что-то связано, попробовал через Download Manager загрузить "толстую" картинку, за это время кеш успел пару раз "сохраниться". Не побилась, хоть это радует.
Потом просто вбил url этого .php в адресную строку и... получил непередаваемые ощущения. :) Файл не загрузился. Т.е. если во время запроса кеш "перезагружается", то запрос останавливается.
Желаемое (точная идентификация nsCacheProfilePrefObserver) получил, но толку от этого... Вот сижу сейчас, думаю. В принципе, всё логично. Наверняка что-нибудь в духе "В начале запроса заняли место в кеше(?) под данные, ждём ответа, а тут место, вдруг, возьми и пропади. Нееет, так не пойдёт, давайте-ка отменим этот запрос" или что-то другое, но родственное этой логике.
Логично-то оно логично,... но это ужасно. :)
Сдаваться? Или выяснять, что же происходит на самом деле и хакать дальше? По принципу "если сейчас идёт запрос, то никаких сбросов кеша не инициируем" (если, конечно, отлов таких ситуаций не составит больщого труда). Правда, понятно уже, что получается совсем гнусное "нечто", а не расширение.
Ведь, насколько я понимаю, никаких других лазеек не осталось?