Xpoint
   [напомнить пароль]

Заставить кеш сохраниться на диске

2005-06-05 14:40:19 [обр] Данил Иванов [досье]

Написал расширение с громким названием Cache Fixer.

Выдержка из about/help (пока пишу только, жду перевода it-IT):


Принцип работы
Браузер работает с кешем следующим образом:

  1. При старте браузера с диска в память загружается индексный файл кеша (CACHE_MAP);
  2. В начале файла, расположенного на диске, ставится флаг негодности ("битости") кеша;
  3. ... (работа) ...
  4. При завершении работы индексный файл, находящийся в памяти, записывется обратно на диск. Уже без битого" флага (таким образом, если работа была завершена аварийно, то этот шаг будет пропущен и на диске останется файл с "битым" флагом);
  5. При следующем старте браузер проверяет "битый" флаг в файле CACHE_MAP и, если таковой присутствует, то полностью очищает кеш.

    
Cache Fixer сбрасывает этот флаг в начале старта браузера, предотвращая очистку кеша.
    
Внимание! Так как информация о текущем состоянии кеша при работе браузера загружается в память и лишь на выходе записывается на диск, то все кешированные данные аварийно завершённого сеанса работы будут потеряны; останется информация только о предыдущих сеансах.
    
В связи с этим возможна некоторая нелогичность работы с кешированными данными аварийно завершённого сеанса после перезапуска браузера. Некоторые считают, что это меньшее зло, чем потеря всего кеша. Если вы считаете иначе, то просто не пользуйтесь этим расширением.


Если кратко, то там компонента, подвешенная на событие "profile-after-change", ищущая CACHE_MAP и перезаписывающая его без "dirty bit".

Сам же браузер снимает этот бит в nsDiskCacheMap::Close.

В баге 105843 можно найти другие варианты решения этой проблемы, мне же интересен такой момент: можно ли прикрутить таймер (это не проблема) и заставлять браузер (а вот это не знаю как сделать) сбрасывать текущее состояние CACHE_MAP например, раз в десять минут?

А то сеансы работы бывают достаточно продолжительными и терять их данные при аварийном завершении работы не слишком приятно.

спустя 22 минуты [обр] Владимир Палант [досье]

Вы ведь, наверное, хотите не флаг менять, а кеш из памяти на диск сбрасывать?

var cache = Components.classes['@mozilla.org/network/cache-service;1']
                      .getService(Components.interfaces.nsICacheService);
cache.shutdown();
cache.init();

Вроде бы делает то, что надо?

спустя 6 минут [обр] Данил Иванов [досье]
shutdown/init... Супер. Ну куда только мои глаза всегда смотрят!
спустя 3 часа 8 минут [обр] Данил Иванов [досье]

Радость была немного омрачена тем, что из 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_ ..........................
    }
  },
// .....................................
спустя 21 минуту [обр] Данил Иванов [досье]
Или до shutdown/init можно "достучаться" как-то иначе?
спустя 5 минут [обр] Владимир Палант [досье]

Вроде всё корректно, вы симулируете переход на другой профиль. Боюсь, что вам придётся ждать, пока о проблемах не доложат пользователи.

Вот только: а исправить dirty flag вы не должны? Я ведь так понимаю, что после вызова OnProfileChanged() он опять установлен?

спустя 31 минуту [обр] Данил Иванов [досье]
Кажется, после OnProfileChanged() там уже не было этого флага, но я посмотрю ещё раз, более внимательно. Спасибо.
спустя 3 дня [обр] Данил Иванов [досье]

Фокус не удался.

Боюсь, что вам придётся ждать, пока о проблемах не доложат пользователи.

Пользователи доложили о потерях cookies, списка доверенных сайтов, отказе сохранения сессий расширением TabMix и прочих прелестях.

Т.к. я "плаваю" в Gecko и вообще не программист :), то могу лишь предположить, что здесь поможет

  1. или конкретное указание на объект XPCOM при notifyObservers, чтобы не беспокоить остальные (как найти определённый observer?)
  1. или каким-то образом всё-таки можно добраться до "прямого" вызова 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?

спустя 54 минуты [обр] Данил Иванов [досье]
С кодом немного поторопился, для события "profile-before-change" так же прокручивается цикл, но сути это не меняет — они близнецы, т.е. различить их на выходе из этого цикла я не могу.
спустя 17 минут [обр] Владимир Палант [досье]

Да уж, проблема :-(

Без перекомпиляции добавить доступ к 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() уже целенаправленно.

спустя 17 минут [обр] Данил Иванов [досье]
Я тут закрыл на часик редактор, подумал, в голове с этими обозревателями вроде как устаканилось, а то всё равно немного неправильно к этому вопросу подошёл. Так что сейчас открою снова и попробую продолжить в свете вашего совета.
спустя 18 минут [обр] Владимир Палант [досье]
Прошелся по обсерверам profile-after-change на lxr.mozilla.org и понял, что побочные явления всё-таки будут. Подавляющее большинство обсерверов проигнорируют вызов. Не проигнорирует его nsBookmarksService (только Mozilla, в Firefox почему-то используется другой), перезагрузит букмарки — это неважно. Хуже, что msgMapiSupport (тоже только Mozilla, в Firefox нет мейл-клиента) при вызове с неизвестным параметром заново регистрирует обсервер (а я удивлялся, почему список как будто бы увеличивался из-за моих экспериментов). Эту проблему можно обойти, если искать в списке по profile-before-change. Там больше обсерверов (у меня 16), но кроме того же nsBookmarksService ни один из них глупостей при сообщении nsPref:changed не делает. Только nsCookieService тоже делает QueryInterface() на aSubject, но игнорирует ошибки вместо того, чтобы их возвращать.
спустя 46 минут [обр] Данил Иванов [досье]

Хак не заставил себя долго ждать и, так как я уже успел до этого подсадить свой observer на "nsPref:changed", а при тестировании про это забыл, то сначала удивлялся, почему так часто перезагружаются установки. :) Но, там решаемо, благо, что своё.

Жаль, конечно, что не предусмотрена идентификация этих наблюдателей.

"profile-before-change": хорошо, значит, будем его пытать.

Спасибо за очередную наводку.

спустя 20 часов [обр] Данил Иванов [досье]

Решил пройтись по всем 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) получил, но толку от этого... Вот сижу сейчас, думаю. В принципе, всё логично. Наверняка что-нибудь в духе "В начале запроса заняли место в кеше(?) под данные, ждём ответа, а тут место, вдруг, возьми и пропади. Нееет, так не пойдёт, давайте-ка отменим этот запрос" или что-то другое, но родственное этой логике.

Логично-то оно логично,... но это ужасно. :)

Сдаваться? Или выяснять, что же происходит на самом деле и хакать дальше? По принципу "если сейчас идёт запрос, то никаких сбросов кеша не инициируем" (если, конечно, отлов таких ситуаций не составит больщого труда). Правда, понятно уже, что получается совсем гнусное "нечто", а не расширение.

Ведь, насколько я понимаю, никаких других лазеек не осталось?

Powered by POEM™ Engine Copyright © 2002-2005