«Прочту потом»: трудная судьба оффлайновой коллекции интернет-страничек
Есть виды софта, без которого одни люди жить не могут, а другие даже не представляют, что такое существует и кому-то вообще нужно. Для меня долгие годы такой программой был Macropool WebResearch, позволявший сохранять, читать и организовывать интернет-страницы в некое подобие оффлайновой библиотеки. Уверен, многие из читателей прекрасно обходятся коллекцией ссылок или комбинацией браузера и папки с набором сохранённых документов. Мне же хотелось бы иметь возможность хотя бы отмечать документы как «прочитанные» или «избранные», быстро переходить от одного текста к другому и не зависеть от доступности интернета или конкретного сайта. Бывает, что читать есть время ровно тогда, когда интернета нет (в дороге, например), да и ссылки, к сожалению, нередко оказываются недолговечными.
Видимо, примерно на таких людей и рассчитывали авторы WebResearch. Эта программа была нашпигована самыми разнообразными функциями: каталогизация по разделам и по тегам, редактирование заметок, всевозможные виды экспорта/импорта и так далее. Однако примерно к 2013 году проект перестал обновляться, а затем прекратил существование и сайт разработчика. Ещё несколько лет мне удавалось кататься на этой лошадке, но сначала отвалились браузерные плагины (доступные только для тогдашних версий IE и FireFox), а затем и современные сайты перестали нормально отображаться в просмотрщике на основе старого IE-движка.
Главное окно WebResearch, PC Week/RE №17 (575)
Дорога разочарований
Как только стало ясно, что замены не избежать, я в фоновом режиме взялся за поиск приличного аналога. Мне казалось, особых сложностей здесь не будет, поскольку желания мои крайне скромны. Я был готов обойтись лишь малым подмножеством средств WebResearch, включающим в себя:
- сохранение HTML страницы из браузера с помощью расширения;
- хотя бы минимальные средства каталогизации (переименование, организация каталогов, метки);
- (желательно) поддержка PDF документов;
- любой сносный способ синхронизации коллекции с другими устройствами.
К моему удивлению, ничего похожего мне найти так и не удалось, хотя я честно излазил интернет вдоль и поперёк и внимательно изучил с десяток подходящих по аннотации программ (за исключением Evernote, где похожая по описанию функциональность доступна только по подписке). На сегодняшний день хоть как-то удовлетворяют моим пожеланиям разве что проекты TagSpaces и myBase. Их изучение, вообще говоря, представляет определённый культурологический интерес.
TagSpaces — это такой вот «стильный-модный-молодёжный» органайзер на Electron с красивым сайтом, адаптивной вёрсткой и, конечно, тёмной темой, куда ж без неё. При этом злосчастное оглавление коллекции c модными закруглёнными значками занимает половину экрана, вмещая при этом штук двадцать элементов максимум, а базовые штуки вроде поддержки горячих клавиш или рендеринга просматриваемого документа пишутся по остаточному принципу. В итоге документы отображаются криво, а работа с коллекцией превращается в скучный и трудоёмкий комплекс упражнений с мышью.
Его антипод myBase родом из конца девяностых: здесь помимо чисто функционального интерфейса мы имеем крайне богатый набор настроек и функций. Однако в качестве окна просмотра здесь выступает всё тот же браузер на основе старого IE (что уже делает чтение затруднительным), а все документы хранятся в монолитной базе данных. Если её положить в папку Dropbox, например (других способов синхронизации с другими устройствами всё равно нет), то при малейшем изменении коллекции приходится дожидаться закачивания на сервер сотен мегабайт информации.
Поворотный момент
Вероятно, дальнейшее содержание заметки читателю представляется очевидным: сейчас нам предложат собственный велосипед, который, конечно, окажется на голову выше любого существующего аналога. Как бы да, но не совсем. Я действительно не выдержал мытарств с myBase и TagSpaces и набросал собственный менеджер документов, ссылку на который приведу ближе к концу. Однако этот мелкий проект для личных нужд сам по себе не заслуживал бы отдельной статьи; я пишу в большей степени потому, что мне показалось интересным поделиться опытом, полученным в процессе работы, и целым рядом неприятных сюрпризов, на которые я никак не рассчитывал.
Цели и задачи
Начну с того, что у меня сейчас довольно напряжённая жизнь, и времени на полноценные хобби-проекты попросту нет. Поэтому с самого начала я решил, что готов лепить свой инструмент из любых компонентов, которые попадутся под руку, если это позволит ускорить дело. Кроме того, я пока что берусь реализовать лишь абсолютный минимум функциональности, без которого обойтись никак нельзя.
Формат данных и сохранение страниц
В каком виде хранить веб-страницы на диске? С учётом ранее сформулированных требований мне казалось, что выбор невелик: либо формат сохранения «веб-страница полностью», то есть основной HTML файл и папка со связанными ресурсами, либо формат MHTML. Первый вариант мне сразу показался менее предпочтительным: невелика радость иметь на диске помойку из кучи файлов, из которых понадобится извлекать значимые документы, фильтровать лишнее при поиске и следить за целостностью при копировании. Когда я пытался работать с TagSpaces, мне пришлось пересохранить все свои документы так, чтобы имя папки с ресурсами начиналось с точки: тогда система распознавала их как «скрытые» и не отображала.
Эта проблема скрыта из виду в myBase, поскольку всё хранится в базе данных, но в моём случае принцип простоты взял верх: очень хотелось хранить всё в виде обычных файлов на диске, чтобы не пришлось заниматься реализацией рутинных операций вроде копирования, переименования, удаления и синхронизации.
Формат MHTML переживает не лучшие свои времена. Простой способ сохранять MHTML был выброшен из Chrome этим летом, и я вот даже не знаю, в чём теперь предполагается хранить страницы? Понятно, что возможность пока что никуда не делась, есть сторонние расширения, но в целом это какой-то нехороший признак. Кроме того, сохранение в формате MHTML не поддерживается в Chromium Embedded Framework, что тоже не прибавляет оптимизма.
Параллельно я стал искать простой способ сохранения страниц из браузера в указанную папку. В итоге обе проблемы удалось разрешить малой кровью: я наткнулся на замечательный проект SingleFile, умеющий сохранять содержимое веб-страницы в отдельном независимом HTML-файле. Делается это путём преобразования всех связанных ресурсов в формат base64 и внедрения непосредственно в HTML. Конечно, при этом размер файла растёт, да и содержимое выглядит несколько замусоренным, но в целом подход мне показался надёжным и простым, и я остановился на нём.
SingleFile поставляется как в виде браузерного расширения, так и в виде приложения командной строки. Сейчас я просто пользуюсь расширением: это достаточно удобно, если не считать того, что надо вручную выбирать целевую папку для сохранения. В будущем, вероятно, постараюсь доработать приложение, чтобы упростить этот процесс. Для вызова стороннего приложения из Chrome можно использовать расширение External Application Button — это ещё одно моё полезное открытие. Кстати, приложение уже принесло пользу: с его помощью я сконвертировал коллекцию папок и файлов из TagSpaces в набор самостоятельных HTML-документов.
Хлопоты с GUI и браузером
Мне показалось, что для всевозможных простых операций с файлами и строками хорошо подходит Python, а поскольку в одном из моих рабочих проектов используется wxWidgets, выбор wxPython в качестве основного фреймворка выглядел логичным.
Далее, насмотревшись на косяки с отображением страниц в других программах, я сделал для себя вывод, что единственный надёжный способ справиться с ними — это внедрить в программу визуализатор на основе современного браузера, то есть Chrome или Firefox.
Должен признаться, что в последний раз мне приходилось заниматься чем-либо подобным лет 15 назад, и я не ожидал никаких подвохов. Оказалось, что «просто шлёпнуть браузер на форму» нельзя: как-то вот человечество не сумело надёжно и универсально справиться с этой задачей. Какой-нибудь listbox или кнопку на форму можно поместить в любом GUI-фреймворке, да ещё и сгенерировать кроссплатформенный код, и мне казалось, что в 2019 году отображение HTML тоже должно было быть повсеместно решённой проблемой.
Оказалось, что в wxWidgets, например, стандартный компонент «браузер» является кроссплатформенной обёрткой над системно-зависимым «браузером», который в случае Windows, например, означает Internet Explorer 7, причём ситуация в Windows Forms ничуть не лучше, и версии свежее IE9 доступны только при помощи нетривиальных манипуляций с реестром. Как видите, не я один занимался последние 15 лет другими делами — здесь тоже с места ничего не сдвинулось.
Дальше передо мной стоял выбор: менять фреймворк или искать альтернативный компонент для браузера. Поколебавшись, я решил попробовать сначала второй путь и достаточно быстро наткнулся на проект CEF Python: Python bindings for Chromium Embedded Framework, предназначенный ровно для задачи встраивания Chromium в приложения на Python.
Оцените ситуацию: Python является одним из самых популярных языков программирования в мире, Chrome — по сути монополист на рынке браузеров. При этом CEF Python фактически поддерживается энергией одного парня, сил и здоровья ему. Неужели это больше никому не нужно?…
Впрочем, мне CEF Python в итоге не помог: хотя даже базовый пример интеграции с wxWidgets из репозитория проекта откровенно глючит, я попробовал повозиться с ним подольше, но так и не смог решить все возникающие проблемы. Даже не буду углубляться в тему, вряд ли она того заслуживает.
Я изучил подробнее компоненты на основе Chromium Embedded Framework и в итоге решил попробовать версию для C#. Поскольку работаю я почти всё время с Windows, перспектива отказаться от кроссплатформенности меня, в общем, не особенно смущала.
После некоторой неизбежной возни на старте дело пошло гораздо быстрее: связка CefSharp и Windows Forms оказалась выигрышной, и мне удалось без особых проблем решить большинство поставленных технических задач.
О неиспробованном
В приложение на C# можно попробовать внедрить и FireFox, используя компонент Geckofx, но я про него ничего сказать не могу. Стандартный браузерный компонент фреймворка Qt под названием QWebEngineView основан на Chromium, так что, вероятно, будет работать не хуже CefSharp.
У любителей Qt может возникнуть соблазн откомментировать: мол, брал бы Qt, не имел бы проблем. Возможно, что так оно и есть, но и wxWidgets можно считать если не первой, то второй опцией при выборе GUI фреймворка для приложений на Python или C++. И по моему скромному мнению уж такая вещь как браузер должна встраиваться в любой более-менее развитый GUI фреймворк без плясок с бубном.
WebLibrary
Вернёмся, однако, к моему приложению с рабочим названием WebLibrary. На сегодняшний день выглядит оно (барабанная дробь) вот так:
Помимо чистого и лаконичного интерфейса здесь реализованы лишь самые наибазовые функции:
- Отображение любого указанного каталога в системе как библиотеки документов.
- Просмотр документов в окне браузера. Навигация по списку обычным образом (клавиши курсора, PgUp, PgDn, Home, End), пролистывание в браузере клавишами Space и Shift+Space.
- Переименование документов.
- Пометка документов как прочитанных или избранных с помощью горячих клавиш.
- Сортировка документов по любому полю.
- Обновление окна приложения при любых изменениях в библиотечной папке.
- Сохранение настроек окна при выходе.
Это всё может показаться тривиальной функциональностью, но вот, скажем, сохранение размеров колонок в TagSpaces всё ещё не поддерживается — видимо, у авторов другие приоритеты.
Статус (прочитано/избранное) попросту хранится в имени файла (прочитанный файл doc.html
переименовывается в doc{R,S}.html
). Синхронизация как таковая не реализована, но я попросту держу библиотеку в Dropbox — в конце концов, это всего лишь папка с файлами.
В планах ещё доработать простые вещи вроде перемещения и удаления файлов, а также реализовать пометки произвольными тегами. Если кто захочет помочь — буду только рад.
Выводы
Самые разные. Как я сказал с самого начала, удивительно, насколько инструментарий одного человека может отличаться от инструментария другого. Для меня пользоваться средством вроде WebResearch естественно, и я ощущал почти физический дискомфорт от его отсутствия. При этом, судя по всему, единомышленников у меня немного, иначе бы проблем с поиском аналогов не возникло бы. С другой стороны, аналогичные случаи происходят и с куда более меинстримным софтом: например, Microsoft не собирается обновлять десктопную версию OneNote, так что я вынужден пользоваться версией 2016 года, и рано или поздно придётся с неё тоже куда-то переезжать.
Ещё удивительно то, насколько непросто ориентироваться в нынешнем ландшафте библиотек и фреймворков. Мне по долгу службы редко приходится писать десктопные приложения от начала до конца, и я предполагал, что для моей задачи (одно окно, три компонента, тривиальные взаимодействия) подойдёт буквально любой инструмент для любого языка программирования. Вот прямо берём что угодно и в течение нескольких дней делаем.
Оказалось, что реальность куда менее благожелательна, и нарваться на проблему можно просто на ровном месте. Скажем, есть у меня два splitter-а, с помощью которых можно растянуть окно браузера. Так вот, восстановить их позиции после загрузки в wxWidgets крайне непросто, поскольку система выставляет их в позиции по умолчанию практически после всех доступных мне событий, и приходится заниматься всяческим хакерством, чтобы добиться нужного. Вот кто бы предполагал?
С другой стороны, видно, что в Windows Forms всё заточено под «бизнес-интерфейсы». Практически всё, что требовалось, оказалось доступно из коробки: и сохранение/восстановление настроек приложения, и удобный интерфейс компонентов (скажем, не ожидал, что у компонента TreeView можно запросить полный путь от корня до любого дочернего элемента в виде строки), и нетривиальные средства вроде отслеживателя изменений содержимого папки.
В любом случае время потрачено не зря, а результат можно признать удовлетворительным, так чего же ещё желать от жизни, верно?