- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF134:Python
Материал из Linuxformat.
- 'Python Реальные проекты, оттачивающие навыки хакера
Python: Будем грузить подкасты
Python |
---|
Python для профессионалов |
---|
Python + PyGame |
---|
Python + Web |
---|
Python + Clutter |
---|
- Познакомьтесь с прекрасным инструментом для создания и выпуска приложений от команды Ubuntu. Ник Вейч настраивается на любимый подкаст…
На этом эпическом и зрелищном уроке наша задача, ни много ни мало, написать полноценное настольное приложение, создать для него пакет Debian и выпустить его в большой мир. Думаете, невозможно создать полноценное работающее настольное приложение, которое и вправду что-то делает, в рамках четырехстраничного урока? В чем-то вы правы; вот мы и расширили урок этого номера в два раза, до восьми страниц. И на сей раз у нас есть сильный союзник – Quickly.
Quickly [англ. «быстро»] – это набор средств разработки, с прилагаемыми шаблонами, для создания подобия RAD-среды (Rapid Application Development – быстрая разработка приложений). Оно довольно скоростное: можно создать и запустить приложение за несколько секунд – просмотрите далее пошаговое руководство и убедитесь наглядно. Однако, хоть мы и можем создать работающее приложение, в нем не будет ни грамма функционала, необходимого для мегазагручика подкастов TuxRadar (а его-то мы и собираемся делать). И все же пробегитесь по тексту, чтобы понять, как все работает, затем вернитесь сюда – и мы разберемся с подкастами.
Что нам понадобится
Наш урок основан на Quickly, инструменте разработки Ubuntu; поэтому будет намного проще, если вы возьмете Ubuntu (например, с диска LXF133). Тогда просто установите пакеты quickly и python-feedparser из репозитория, и все будет готово.
Если вы распаковали код с LXFDVD в удобное место, можете использовать для урока его – настроенное Quickly уже готово к работе, и можно использовать команды Quickly.
Создаем приложение
Итак, мы ознакомились с базовыми механизмами работы Quickly; пора сделать что-то полезное. Как уже сказано, мы намерены создать загрузчик подкастов. Понятно, придется повозиться с новостными лентами и тому подобным, но для начала припасем место для хранения загружаемых файлов. Мы обязаны дать пользователям возможность выбрать место хранения (или создать новое, если надо); значит, необходим диалог, в котором они смогут это сделать.
Весьма удобно, что шаблон нашего приложения содержит редактор параметров. В нем еще ничего нет, но это уже хорошее начало. В таких ситуациях обычно лучше начать с интерфейса пользователя, чем с самого кода – хотя бы потому, что это поможет узнать имена объектов и их сигналы до написания кода, объединяющего их. Итак, запустите Glade командой quickly design в каталоге приложения.
Откроется Glade с главным UI-файлом проекта, но нам-то нужен другой, так что откройте правильный, с именем PreferencesMyAppDialog.ui. После его открытия вы увидите посередине приятную пустоту. Первым делом разместим в ней элемент vbox, вертикальный контейнер – он размещает свои дочерние элементы по вертикали. Щелкните в левой панели на иконке vbox, а затем в серой области, чтобы разместить его. Всплывет диалог, запрашивающий, сколько элементов вы хотите разместить – оставьте их 3. Пока мы употребим только два из них, но это не беда – один будет про запас, поскольку, вероятнее всего, он понадобится нам позднее. Но не запасайте слишком много: добавить элементы в вертикальный контейнер просто, а вот удалить – большая морока.
В верхней части разместим метку, чтобы пользователь понимал, что происходит. Выберите элемент Label в панели слева и щелкните в верхней части vbox. Сейчас в ней написано просто «Label», но текст можно изменить при помощи инспектора свойств справа внизу.
Поместив более осмысленный текст в свойстве Метка, добавьте какой-нибудь способ выбора каталога. Таковых можно придумать много, но GTK содержит специальную кнопку именно для этой цели, и было бы глупо ею не воспользоваться. Выберите объект FileDialogButton в панели слева и поместите его в срединный слот.
Нам нужно изменить некоторые свойства. На вкладке «Основные» прокручивайте список вниз, пока не наткнетесь на пункт «Действие». Измените его на «Выбор каталога». Стандартное действие – это выбор файла, но это не то, что нам нужно. После выбора этого действия, щелчок на кнопке приведет к заполнению списка соответствующим содержимым. Мы можем опробовать ее уже в Glade – просто щелкните на иконке у правого края кнопки.
Однако прежде чем сохранить наш новый диалог, сделайте еще одну важную вещь. Когда кто-нибудь выбирает каталог, мы должны как-то узнать, что же он выбрал. Для этого мы подаем сигнал от объекта, сообщающий, что некое значение изменилось. С точки зрения кода, нужно написать метод-обработчик, работающий с данными, но обработчик необходимо указать здесь при дизайне UI-файла. Щелкните на вкладке «Сигналы». Вы увидите, что сигналов, к которым можно подключиться, полно. Нас интересует сигнал из раздела ‘GTKFileChooser’ – откройте его. В первом столбце дважды щелкните справа от названия ‘current-folder-changed’ и введите имя обработчика ‘folder_changed’. Теперь, стоит пользователю выбрать другой каталог, мы узнаем об этом и изменим соответствующий параметр приложения.
Не забудьте сохранить файл перед выходом из Glade. Теперь пришло время ввести quickly save ‘prefs dialog’, что передаст ваши новые файлы системе управления версиями (см. совет Quickly Сохраняйте почаще).
Что в имени твоем?
Не исключено, что вы считаете UI-файлы Glade просто XML-кодом, однако вы не сможете просто перенести их из одного проекта в другой без значительного рефакторинга. Они напичканы ссылками на имя проекта, так что вы не сможете даже просто импортировать их в ваш проект – лучше позволить Quickly создать для вас базовые UI-файлы.
Храните здесь
Прежде чем погрузиться в код, чтобы сделать этот шедевр интерфейса рабочим, прикинем, где мы будем все размещать: ведь главный смысл настроек в том, что они сохраняются на будущее. Так что требуется место для хранения. Данные будут типа настроек приложения, лент, на которые мы подпишемся, и, возможно, многого другого, о чем мы еще не задумывались. Лучшее всего выбрать нечто легкодоступное. Имеется множество популярных и хорошо документированных модулей Python для сохранения настроек, вроде ConfigObj, ConfigParser и т. д., но мы используем нечто иное: базу данных под названием CouchDB.
Возможно, вы сочтете, что база данных для хранения пары параметров – это перебор, но в ее пользу есть веский довод: она уже является частью шаблона проекта, который Quickly использует для генерации нашего приложения, а коли она есть, надо ею пользоваться.
На самом деле в проект включена не только база, но и код для доступа к ней; остается добавить там и сям несколько строк. Если вы желаете сперва ознкомиться с CouchDB, просмотрите раздел Что такое CouchDB?
Запустите Gedit командой quickly edit, и все файлы с кодом откроются автоматически. Найдите PreferencesMyAppDialog.py с кодом обработки настроек. Неважно, понимаете ли вы весь этот код или нет, но чтение его вам поможет. Здесь происходит несколько вещей: несколько функций инициализации, генерирующих окно диалога, и некий код для работы с базой данных. К счастью, в нем имеются комментарии, и найти его будет не сложно. При выполнении данный код проверяет, не заданы ли уже некоторые настройки; если нет, он открывает базу данных (и запускает CouchDB, если она еще не запущена) и пытается загрузить их из нее. Если там их нет, необходимо ввести в код установку значений по умолчанию, что мы далее и сделаем. Здесь есть небольшой код для кнопок ‘OK’ и ‘Cancel’, которые вскоре появятся.
Что за штука – CouchDB?
- Итак, я понимаю, что CouchDB — еще одна база данных, как SQL и всякое такое?
- Не совсем. CouchDB – скорее движок хранилища документов. В отличие от базы данных, он просто хранит сведения, которые вы ему передаете, почти их не структурируя, легким для доступа способом. Жесткой схемы данных нет (все организовано в виде пар ключ:значение, по типу объекта-словаря в Python), что весьма удобно для нас.
- Он может хранить только текст? А как насчет других данных, которые я захочу туда поместить?
- Значение ключа может быть практически любым типом данных Python – числом, строкой и набором различных объектов, списком и даже другим словарем. Вы можете добавлять другие объекты – например, файлы с изображениями – в качестве приложений к базе данных, но более эффективно хранить их другим способом.
- Значит, тот, кто возьмет мое приложение для своей работы, должен запустить еще один сервер базы данных?
- Нет, об этом в Python позаботится библиотека Desktopcouch. Она запускает базу данных по любому требованию. CouchDB разработан нетребовательным к ресурсам, так что скорее всего вы даже не почувствуете, что он запустился. Если, конечно, вы не следите за ним…
- То есть как это — «следить за ним»?
- Ну, одно из самых замечательных свойств этой базы данных – то, что она создана с прицелом на web. Она была разработана для работы с несложными API, реализованными через HTTP, чтобы сделать ее полезной для web-приложений. Базируясь на принципах ACID (для фанатов аббревиатур: Atomicity, Consistency, Isolation и Durability – Атомарность, Согласованность, Изолированность, Долговечность), она создавалась с целью быть постоянно в рабочем состоянии, чтобы сопротивляться сбоям или отключению. Она может выполнять всякие полезные действия, вроде P2P-репликаций. Для наших целей, следует лишь направить браузер по адресу file:///home/<имя_пользователя>/.local/share/desktop-couch/couchdb.html – там вы обнаружите все базы данных, используемые desktop-couch.
- Выходит, это нечто вроде MySQL-Admin?
- Похоже, только работать с этим проще. Использование web-интерфейса для изменения данных в любой базе весьма просто и, конечно же, отлаживать транзакции базы данных также легко.
Сброс CouchDB
Когда наше приложение ищет сохраненные настройки при старте, оно послушно загружает то, что находит. Но если вы начнете добавлять в разные вещи код, их может не оказаться в базе данных, и ничего не произойдет. В этом случае проще всего удалить базу данных CouchDB из браузера. Перейдите в file:///home/<имя_пользователя>/.local/share/desktop-couch/couchdb.html и просто выделите и удалите базу данных вашего приложения. При следующем запуске она будет создана из стандартных значений.
Сохраняйте почаще
Иногда нам случается напортачить. Для файлов исходных текстов это обычно не проблема, но для файлов интерфейса Glade практически фатально, потому что в них много необратимых действий.
По этой причине Quickly имеет встроенную систему управления версиями. Это не просто способ упаковки и публикации проекта на Launchpad; это полезная вещь по сути для всего. Если ввести команду quickly save ‘комментарий к новой версии’, то новая версия вашего проекта занесется в систему контроля версий Bazaar. Проверить сохраненные версии можно, скомандовав bzr log – выведется список всех выполненных транзакций, вместе с заметками и датой-временем. Если угробилось вообще все, введите в главном каталоге приложения bzr revert – и файл с с исходными текстами, графический интерфейс и любые другие файлы восстановятся из предыдущей версии.
Подсказка-бонус: при желании восстановить лишь часть проекта, выполните quickly design и quickly edit, укажите команду revert и сохраните файлы, которые хотите оставить как были.
Сохраняем вводимые значения
Итак, мы решили сохранять в настройках каталог, куда будут загружаться файлы. Согласно коду, настройки хранятся в объекте-словаре с именем self._preferences (часть ‘self’ означает, что это часть данных для объекта-диалога, создаваемого при вызове этого кода). Здесь есть даже подходящее место для размещения умолчаний – в методе с именем _load_preferences, а также имеется комментарий TODO, сообщающий, что здесь-то и следует разместить значения по умолчанию. Здесь и введите
self._preferences[“savedir”]=”~/”
(Убедитесь, что в коде должное количество отступов, иначе получите ошибку). Это кусочек магии сокращений в Linux. Символ «тильда» (~) в записи пути к файлу распознается как путь к домашней директории пользователя, так что эта строка автоматически устанавливает его в качестве каталога сохранений, если пользователь не изменит это.
Теперь добавим обработчик сигнала, который мы ввели при разработке интерфейса пользователя. Разместить его можно в любом месте класса, но, наверно, лучшим вариантом будет непосредственно перед строкой, начинающейся с def ok(self,widget,... – обработчиком нажатия клавиши OK:
def folder_changed(self, widget, data=None): self._preferences[“savedir”]=widget.get_current_folder()
Все очень просто. В PyGTK, виджет, подающий сигнал, включает самого себя в обратный вызов обработчика. Как мы знаем, это сигнал от FileChooserDialog; мы также знаем, что у него есть метод добывания текущего выбранного значения (в виде строки), и можно просто сохранить их в нашем словаре настроек.
Тут, однако, встает интересный вопрос: с каким значением появляется виджет? Ответ таков: если мы ранее ничего не определили, то запускается он с пустым значением. Это может обескуражить пользователя; нужно заранее включить некоторый код, чтобы корректно задать значение. Для этого потребуется раздобыть ссылку на сам виджет.
Код в нашем файле использует самостоятельный сборщик приложения для создания реального диалога из файла .ui, который мы сохранили, так что на графический интерфейс ссылаются локально через объект self.builder. Имя виджета мы помнимn (filechooserbutton1), и можем просто разыскать этот объект и сохранить в нем переменную с настройками при помощи подходящего метода:
o=self.builder.get_object(‘filechooserbutton1’) o.set_current_folder(self._preferences[“savedir”])
Лучше всего разместить это где-то сразу после создания диалога и загрузки настроек. К счастью, вы вновь обнаружите полезную заметку где-то в строке 60: #TODO: code for other initialization actions should be added here [#Сделать: код других действий инициализации следует разместить здесь], так что добавьте его сразу после нее (точный номер строки зависит от других функций, таких как генерация лицензии и прочее – просто призовите свой интеллект, чтобы найти ее!).
Итак, мы создали место для хранения настроек, привязали сигнал к методу получения нового значения при его изменении пользователем и не забыли взять значение и поместить его в виджет при открытии диалога. Код для кнопки OK позаботится о сохранении любых изменений. Надо еще что-нибудь?
Что произойдет, если пользователь изменит каталог, а затем передумает и нажмет кнопку Cancel [Отмена]? Согласно текущему коду, настройки изменяются (но не сохраняются) в момент, когда пользователь выбирает другой каталог. Если он нажмет Cancel, то каталог хотя и не сохранится, но останется в памяти как выбранный параметр настройки. Следует учесть это и дать знать основному приложению об изменении параметров, просто перезагрузив их из базы данных при закрытии диалога. Для этого нужно изменить главный файл MyApp.py (ну или как вы там его назвали), а также обеспечить, чтобы настройки загружались при старте приложения. Код, выполняющий это, практически тот же, и не будь он всего из двух строк, его следовало бы оформить в виде функции:
dlg = PreferencesPlopDialog.PreferencesPlopDialog() self.preferences = dlg.get_preferences()
Здесь просто создается (но не показывается) диалог, а затем из него берутся настройки. Место для этого кода в основном файле – приблизительно около строки 90 (после комментария # Code for other initialization actions should be added here [остальной код инициализации разместить здесь]) и строки 103 (после комментария # Make any updates based on changed preferences here [Любые изменения на основе параметров настройки выпол-нять здесь]).
Этого должно хватить. Теперь запустите приложение и посмотрите, как оно работает. Протестируйте выбор каталога и закройте и откройте приложение вновь, прежде чем мы двинемся дальше.
ОК, до сих пор мы вас щадили, поскольку вы, скорее всего, новички в Quickly, но теперь вы знаете дорогу, и нам следует ускориться, если мы хотим завершить наш загрузчик подкастов – уж извините, каждое нажатие кнопки разжевывать не будем. Если вы в чем-то не уверены, всегда можно обратиться к файлам проекта на LXFDVD!
Наша следующая задача – создать основной интерфейс, так что наберите quickly design и загрузите соответствующий файл. Удалите имеющиеся там объекты, но вертикальный контейнер можно оставить.
Нам нужен способ показать список источников и список файлов выбранного источника. Это можно сделать по-разному, но в нашем приложении мы воспользуемся просмотром в горизонтальной панели, чтобы два списка разделялись вертикальной чертой. Действуйте: разместите ее в пустом контейнере.
Внутрь каждой панели следует поместить контейнер Прокручиваемое окно (возьмем его в том же разделе палитры слева), это поможет просматривать списки большой длины.
Теперь в правой панели разместите объект Дерево. Это похоже на список, и мы будем применять его для отображения графики, идентифицирующей подкасты, на которые мы подписаны. При размещении объекта Дерево Glade потребует указать его модель. Просто создайте новую. Модель – это обычный список типов данных и меток, использующихся для манипуляций с данными или отображения их в интерфейсе пользователя. Мы начнем с четырех значений – guint (целое без знака) с именем source_id, gchararray (массив символов – строка) с именем source_name, еще один gchararray с именем source_url и GdkPixBuf (массив пикселей) с именем source_img.
Что это было?
Последний тип – изображение; по умолчанию, его-то мы и будем отображать справа. Щелкните по объекту Дерево в древовидном списке объектов и выберите пункт меню Edit, чтобы редактировать свойства объекта более подробно в отдельном окне. Прокручивайте вниз, пока не увидите пункт «Столбец всплывающей подсказки», и введите значение «1». Этим мы выбираем первый столбец только что созданного списка (модели) в качестве всплывающей подсказки к каждой записи. Теперь перейдите на вкладку Иерархия и нажмите внизу списка кнопку Добавить, и добавьте одну колонку. Справа задается заголовок и набор свойств. Теперь щелкните на добавленном столбце правой кнопкой мыши и в появившемся меню выберите ‘Добавить дочерний элемент Изображение’. В панели справа установите для пункта Объект значок (Pixbuf) в значение 3, или выберите source_img в выпадающем списке. Это позволит отображать наше изображения в списке в ячейках.
Перед выходом добавьте ссылку на обработчик сигнала. На вкладке Сигналы объекта Дерево, найдите пункт ‘row-activated’ и добавьте запись с именем ‘row_chosen’. Сохраните все!
Пришло время поработать с кодом. Прежде всего нам нужен хитрый метод для извлечения изображения-иконки для конкретной ленты. Мы можем указать URL и извлечь изображение Pixbuf. Легко! (Это если вы следили за нашей серией.)
def fetch_image(self,url): import feedparser,urllib #найдем имя изображения f=feedparser.parse(url) name = f.feed.image.href imgfile,data = urllib.urlretrieve(name) img=gtk.gdk.pixbuf_new_from_file(imgfile) img=img.scale_simple(150,150,gtk.gdk.INTERP_BILINEAR) return img
На досуге можно подумать и о кэшировании изображений (помните: имена файлов могут конфликтовать, так что не помешает добавлять к ним и хэш URL’а); но пока оставим это. Они, конечно, будут загружаться при каждом запросе, но благодаря их малому размеру вы едва ли почувствуете серьезное снижение скорости.
Объедините этот метод с другими в главном файле MyApp.py, и раз уж файл открыт, просмотрите код, следующий после комментария # Code for other initialization actions should be added here [остальной код инициализации разместить здесь]. Добавьте следующий код:
store1=self.builder.get_object(“liststore1”) for index, sub in enumerate(self.preferences[‘subs’]): # структура данных subs должна # содержать name и url img=self.fetch_image(sub[1]) store1.append([index,sub[0],sub[1],img])
Переменная store1 содержит объект liststore, генерируемый в UI, и мы можем заполнить его данными. Я так вижу, что данные о наших лентах будут храниться в настройках в виде списка: имя ленты и URL (мы сделаем это через минуту). Используя URL, мы добудем изображение и поместим весь набор в хранилище списка. Просмотр в цикле (по списку списков) создает дополнительную переменную – номер итерации; его мы используем для создания идентификатора ID. Осталось только вернуться в файл PreferencesMyAppDialog.py и добавить следующее к переменным по умолчанию:
self._preferences[“subs”]=“Tux Radar podcast (ogg) “http:// www.tuxradar.com/files/podcast/podcast_ogg.rss”
Поместите это сразу после умолчаний savedir, введенных нами ранее. Сохраните все опять и вновь запустите приложение. Теперь вы должны увидеть загруженный логотип TuxRadar в левой панели!
Создание приложений с графическим интерфейсом начинает напоминать упражнение в прокладке труб. В нашем первом списке необходимо что-то сделать, если кто-то выберет в нем элемент. Механизм действий такой: перехватываем сигнал, подаваемый списком, а затем делаем нечто с другим списком; надо лишь соединить между собой части кода. Нас не волнует, когда они будут исполняться – это дело пользователя; главное – продумать все последствия предпринимаемых действий.
Пользуйтесь журналом
Древний прием нашпиговывать программу операторами вывода различных значений и объектов для проверки – это отстой. Оно, конечно, помогает, но после отладки какой-либо части кода их нужно вычищать, не то ваш код разбухнет и при каждом запуске консоль будет заполняться мусором. Основной файл программы, создаваемый Quickly, инициализирует журнал, создаваемый соответствующим модулем Python, если вы запустите приложение с ключом -v (это можно делать и в Quickly). Так что для вывода сообщений просто используйте:
logging.debug(“ prefs= %s”,self.preferences[‘subs’])
Этот метод следует стандартным правилам подстановки; его прелесть в том, что вывод он производит, только если его об этом просят.
Трубопроводы
Прежде чем подключаться ко второму списку, не худо узнать, что в нем содержится. Откройте Glade снова и разместите Дерево во втором прокручиваемом окне. В созданном для него представлении списком следует создать несколько столбцов – boolean (логическое) с именем downloaded [загружено], gfloat (число с плавающей точкой) с именем progress [прогресс] и восемь gchararrays – их я назавл title [название], subtitle [подзаголовок], summary [описание], tags [тэги], size [размер], duration [длительность], media_type [тип содержимого] и media_url [URL содержимого]. Их назначение ясно из имен, и, честно говоря, дело до них скорее всего не дойдет.
Естественно было бы хранить все эти данные в объекте и копировать его в модель списка при необходимости, но в итоге у вас получится две копии всех данных, и забота об их синхронизации станет проблемой – накопив опыт, вы придете к выводу, что проще хранить данные в списке-представлении и ссылаться на него при необходимости, а не опрашивать переменные на каждом шагу. При добавлении столбцов в само Дерево, нет нужды добавлять все, однако первым добавьте cellrendererprogress и свяжите его с данными о прогрессе выполнения.
Для этого Дерева желательно убедиться, что в метке Общие для свойства События установлено значение получать все. Также добавьте сигнал с именем file_chosen в row-activated. Мы создадим для него метод обработки двойного щелчка на файле.
Для извлечения данных и содержимого в ваш список-представление добавьте такой код в главный файл:
def row_chosen(self,treeview,selection,treeviewcolumn): “”” Обработчик сигнала для выбора элемента в строке источника””” import feedparser listmodel=treeview.get_model() store2=self.builder.get_object(“liststore2”) store2.clear() savedir=self.preferences[“savedir”] index=listmodel.get_iter(selection[0]) source_url =listmodel.get_value(index,2) # получаем список файлов f=feedparser.parse(source_url) for entry in f.entries: media_url=entry.enclosures[0][“href”] logging.debug(“media url = %s”,media_url) fname=os.path.split(media_url)[1] #проверяем что файл уже загру жен downloaded=os.path.exists(os.path.join(savedir,fname)) progress=0.0 if downloaded: progress=100 title= entry.title if entry.has_key(“title”) else “Unnamed” subtitle= entry.subtitle if entry.has_key(“subtitle”) else “no information” summary=entry.summary if entry.has_key(“summary”) else “no information” tags = ‘’ if entry.has_key(“tags”): for item in tags: tags+=item.term size= entry.enclosures[0].length duration= entry.itunes_duration if entry.has_key(‘itunes_duration’) else “??:??” media_type= entry.enclosures[0].type logging.debug(“data:%s”,[downloaded,progress,title,subtitle,summary,tags,size,duration,media_type,media_url]) store2.append([downloaded,progress,title,subtitle,summary,tags,size,duration,media_type,media_url])
Здесь нет ничего сверхсложного. Мы рассматривали чтение RSS-лент в предыдущих учебниках: при любой обработке RSS-лент, в формате Atom или нет, незачем искать добра от добра, а точнее – замечательного модуля Feedparser от Марка Пилгрима [Mark Pilgrim] (http://www.feedparser.org). По скрытым от человечества причинам этот модуль не включен в стандартный пантеон при установке приложений Ubuntu, но добавить его проще простого: sudo apt-get install python-feedparser. Подлинная красота этого модуля в том, что он прочесывает все мыслимые форматы лент и выдает приятный объект Python, с которым уже можно работать, не путаясь со всякими узлами, проверками используемой версии RSS и прочим. Передаете ему URL ленты – получаете четкую структуру. Для ленты подкастов возвращаемый объект будет содержать различные контейнеры entry [запись], с информацией о каждом доступном эпизоде. Они разбиваются на несколько стандартных полей – заголовок, описание, тэги, а также информацию о дате и URL для медиа-содержимого, что вполне достаточно для создания таблицы эпизодов.
Для выбора файла…
С этим разобрались; теперь обработаем сигнал, получаемый при выборе файла. Добавьте следующий метод:
def file_chosen(self, treeview,selection,column): import thread listmodel=treeview.get_model() index=listmodel.get_iter(selection[0]) downloaded=listmodel.get_value(index,0) if downloaded: self.file_play(listmodel.get_value(index,9)) else: self.file_download(listmodel,index)
Он определяет, какой файл выбран, и проверяет, не загружен ли он. Если да, подставляется ссылка на другой обработчик:
def file_play(self,url): “”” Инициа лизирует PlayerDialog и проиг рывает за данный файл””” logging.debug(“caught signal for file play”) filename=os.path.split(url)[1] filename=os.path.join(self.preferences[“savedir”],filename) player=PlayerDialog.PlayerDialog() player.set_playfile(filename) player.run() player.destroy()
Вообще-то здесь обращаются к новому диалогу. Чтобы все работало, добавьте диалог через Quickly, включив его в описание проекта. Для этого в каталоге проекта введите quickly add dialog Player Об остальном позаботится Quickly. Затем вы можете открыть эти файлы и изменить их при помощи quickly edit и quickly design. Наш интерфейс диалога – это всего лишь сигнал, показывающий: что-то случилось, и мы отреагировали. Если честно, то и в коде проигрывателя мало что происходит. Это просто быстрый способ издать звук.
В конце метода finish_initializing нужно создать проигрыватель из модуля gst (ах да, необходимо добавить в начало файла import gst):
self.player=gst.element_factory_make(“playbin”,”PodPlayer”)
Затем мы просто реализуем метод проигрывания файла:
def set_playfile(self,filename): #получает путь к файлу и запускает проигрыватель uri =”file://”+filename print uri self.player.set_property(“uri”,uri) self.player.set_state(gst.STATE_PLAYING)
Да не забудьте остановить проигрыватель. Не обольщайтесь, что объект будет уничтожен вместе с модальным диалогом: звук в Linux – штука капризная, и вы неоднократно можете столкнуться с ситуацией упорного проигрывания MP3‑файлов, хотя предполагалось, что воспроизведение остановлено. Можно просто поместить вызов остановки проигрывателя в обработчик кнопок OK и Cancel.
self.player.set_state(gst.STATE_PAUSED)
Этот код позаботится о том, чтобы подкасты не перетрудились.
Осталось позаботиться о самой загрузке медиа-файлов. Для получения файлов воспользуемся urllib.urlretrieve(): это часть стандартного пакета Python, и она работает достаточно хорошо. В этом коде есть два хитрых трюка, и его следует немного пояснить.
def file_download(self,listmodel,index): import urllib,thread url=listmodel.get_value(index,9) savename=os.path.split(url)[-1] savename=os.path.join(self.preferences[“savedir”],savename) reporthook = lambda a,b,c : self.progress_update(listmodel,index,a,b,c) thread.start_new_thread( urllib.urlretrieve,(url,savename,reporthook))
Метод urlretrieve предусматривает обратный вызов. Беда в том, что последний возвращает лишь данные о загруженных блоках, и ничего о загружаемом файле. Запустив сразу несколько загрузок, мы уже не сможем рассматривать их по отдельности. Я в общем против применения лямбда-функций (функций, встроенных в Python) – они нередко усложняют понимание кода. Однако в данном случае использование лямбда-функций в качестве прокси означает, что мы можем добавлять полезную информацию к отклику процесса загрузки и передавать ее в следующую функцию. Прежде чем браться за это, рассмотрим еще одну используемую здесь уловку – а именно, потоки.
Приятные потоки
Простой случай потоков (см. последнюю строку) означает, что можно запускать поток, представив функцию и ее аргументы в виде кортежа. Без потоков работа приложения будет приостановлена до тех пор, пока файл не загрузится – оно не будет ни на что откликаться, и пользователь может решить, что оно зависло. Вдобавок наш удобный индикатор прогресса будет бесполезен. Для создания обратной связи нам необходим второй метод:
def progress_update(self, listmodel,index,a,b,c): “”” Функция обратного вызова, отражающая прогресс в таблице. Необходимы потоки!””” blocks = float(c/b) progress=float(a/blocks)*100 if progress > 100: progress = 100 listmodel.set(index,0,True) listmodel.set(index,1,progress)
Здесь вычисляется процент завершенности и соответственно обновляется модель представления списка. Можно запустить несколько загрузок одновременно, и приложение будет обновлять их все. Исходная идея была в том, чтобы индикатор прогресса отображался под каждым элементом на всю ширину окна. К сожалению, GTK не поддерживает такого напрямую (по крайней мере, пока), но если вы желаете создать ваш собственный вариант отрисовки, то сделайте это и дайте нам знать, пожалуйста!
Чтобы заставить потоки работать, припишите следующее в конец основного файла, сразу после import optparse:
import gobject gobject.threads_init()
Если этого не сделать, GTK-часть приложения не поладит с модулем потоков Python.
Летите на волю
Если вы пробовали приложение по ходу урока, то теперь увидите, что оно вполне готово к употреблению. Конечно, доработать еще есть что, и, кстати, в исходном коде в сети или на DVD разбросано множество примечаний и задач [TODO]. Для начала, несколько пунктов меню не работают! Ох, и еще одна загвоздка – мы так и не дали пользователю возможность добавлять свои ленты подкастов (это если им мало одного TuxRadar...).
Однако мы должны выполнить последнюю часть нашей миссии, то есть выпустить приложение на волю. Командами Quickly это делается легко, нужна только учетная запись на Launchpad. Если у вас такой нет, создать ее недолго; но чтобы выгружать данные в PPA, потребуется также иметь ключ GPG и открытый ключ SSH, для проверки ваших файлов.
Ну, а для простого создания Deb-файла и того не надо. Просто введите команду quickly package, и скрипты потрудятся за вас, создав готовый к распространению Deb-пакет. Если вы упорно желаете выгрузиться на Launchpad, сделайте это командой quickly release, просто следуя ее инструкциям. Однако хорошей идеей будет сначала представиться Bazaar, а затем убедиться, что ваш почтовый адрес соответствует учетной записи Launchpad:
bzr whoami ‘My Name <email@example.com>’
Quickly размещается на Launchpad (https://launchpad.net/quickly); создав свою учетную запись, вы сможете заходить, задавать вопросы и тусоваться с крутыми кодерами.
Шаг за шагом: Создаем простое приложение Quickly
- 1 Создаем в Quickly
- Чтобы начать создавать свое приложение, откройте терминал и создайте новый каталог для всех ваших Quickly-проектов командой mkdir Quickly. Войдите в этот каталог (cd Quickly) – и вы готовы к созданию и сборке вашего первого приложения. Введите quickly create ubuntuapplication MyApp, и все закрутится. Quickly создаст каталог и сгенерирует в нем все необходимые файлы, а именно – файлы интерфейса GTK, файлы Python, базы данных и системы управления версиями. По завершении приложение запустится.
- 2 Проектируем в Quickly
- Войдите в каталог проекта myapp и скомандуйте quickly design. Запустится Glade, редактор графических интерфейсов для GTK, и в нем автоматически откроется главное окно вашего приложения. Все файлы интерфейса пользователя хранятся в одном и том же месте, так что любой диалог вашего приложения можно легко изменить. Немного порезвитесь в Glade, если ранее вы с ним не сталкивались. Свойства каждого объекта легко изменить при помощи инструментов, расположенных в правом нижнем углу. Здесь мы меняли текст в нашей метке. Не забудьте сохранить проект перед выходом!
- 3 Редактируем в Quickly
- Quickly использует редактор Gedit из Gnome, прекрасно подходящий для неинтенсивной работы. Кроме того, он отслеживает файлы вашего проекта, поэтому для их изменения, находясь в терминале в главном каталоге приложения, просто введите quickly edit. Gedit появится и откроет все Python-файлы, связанные с проектом, так что вы можете начать создание новых функций. Из других файлов вы можете пожелать изменить файл AUTHORS в главном каталоге проекта. Просто введите краткое сообщение об авторе, чтобы все знали, что это ваше. Сохраните файл и выйдите.
- 4 Запускаем в Quickly
- Перед тем, как вы запустите свое слегка измененное приложение, сделайте еще кое-что. В командной строке введите quickly license. Это создаст сообщение об авторском праве на основании той информации, что вы ввели в файле AUTHORS, и уведомление GPL v3 (по умолчанию, хотя это можно изменить) в заголовке каждого файла с исходными кодами; ну, и еще там всякое. Для запуска вашего приложения введите quickly run. Когда оно откроется, выберите в меню Help > О программе. Вы увидите, что диалог изменился и теперь содержит ваше сообщение об авторском праве и информацию о лицензии!
Отладка в Quickly
Даже маститые кодеры здесь у нас в Башне LXF иногда ошибаются в суждениях. Вообще-то люди, имеющие привычку все оценивать, считают, что 80 % времени разработки ПО уходит на исправление ошибок. Но исправление ошибок – вовсе не каторга: это путь к открытиям (как мы пытаемся убедить сами себя), дающий нам ощущение востребованности и полезности.
Для Python имеется несколько прекрасных инструментов отладки, но Quickly поставляется с одним уже настроенным и готовым к работе, так что мы воспользуемся им! Возможно, ранее вы не сталкивались с Winpdb, а он содержит все функции для пошагового перемещения между точками останова, анализа пространства имен и проверки поведения переменных. Он определенно быстрее стандартного PDB, и узнать о нем больше можно на http://winpdb.org/docs.
Исходные тексты
Исходные тексты имеются на LXFDVD, но если вам лень их искать, у вас нет DVD-привода или вы читаете это в онлайн-версии, переведенной на русский, затем на польский и вновь на английский, то исходные тексты можно скачать напрямую с сайта Ubuntu Launchpad. Посетите http://code.launchpad.net/podofile или воспользуйтесь Bazaar для повторного создания локального репозитория командой bzr branch lp:podofile. Архив содержит также файлы настройки Quickly, так что если вы уже установили Quickly, то можете поиграть с ним самостоятельно.