Добавляем пользовательский интерфейс

dobavlyaem polzovatelskij interfejs Сценарии на стороне клиента

Если вы читали предыдущую главу, то должны помнить, что она завершалась кратким обзором сценариев, добавляющих интерфейс пользователя к основанному на сокетах сценарию getfile — он передавал файлы через сокеты, используя специфические правила, а не FTP. В конце обзора было отмечено, что FTP предоставляет гораздо более универсальный способ перемещения файлов, потому что серверы FTP широко распространены в Сети. В иллюстративных целях в примере 13.7 приводится видоизмененная версия пользовательского интерфейса из предыдущей главы, реализованная как новый подкласс универсального построителя форм из предыдущей главы, представленного в примере 12.20.

Пример 13.7. PP4E\Internet\Ftp\getfilegui.py

"""

############################################################################ вызывает функцию FTP getfile из многократно используемого класса формы графического интерфейса; использует os.chdir для перехода в целевой локальный каталог (getfile в настоящее время предполагает, что в имени файла отсутствует префикс пути к локальному каталогу);

вызывает getfile.getfile в отдельном потоке выполнения, что позволяет выполнять несколько запросов одновременно и избежать блокировки графического интерфейса на время загрузки; отличается от основанного на сокетах getfilegui, но повторно использует класс Form построителя графического интерфейса; в данном виде поддерживает как анонимный доступ к FTP, так и с указанием имени пользователя;

предостережение: содержимое поля ввода пароля здесь не скрывается за звездочками, ошибки выводятся в консоль, а не в графический интерфейс (потоки выполнения не могут обращаться к графическому интерфейсу в Windows), поддержка многопоточной модели выполнения реализована не на все 100% (существует небольшая задержка между os.chdir и открытием локального выходного файла в getfile) и можно было бы выводить диалог "сохранить как", для выбора локального каталога, и диалог с содержимым удаленного каталога, для выбора загружаемого файла; читателям предлагается самостоятельно добавить эти улучшения;

############################################################################

from tkinter import Tk, mainloop

from tkinter.messagebox import showinfo

import getfile, os, sys, _thread # здесь FTPверсия getfile

from PP4E.Internet.Sockets.form import Form # использовать инструмент форм

class FtpForm(Form):

def __init__(self):

root = Tk()

root.title(self.title)

labels = [‘Server Name’, ‘Remote Dir’, ‘File Name’, ‘Local Dir’, ‘User Name?’, ‘Password?’]

Form.__init__(self, labels, root) self.mutex = _thread.allocate_lock() self.threads = 0

def transfer(self, filename, servername, remotedir, userinfo): try:

self.do_transfer(filename, servername, remotedir, userinfo) print(‘%s of "%s" successful’ % (self.mode, filename))

except:

print(‘%s of "%s" has failed:’ % (self.mode, filename), end=’ ‘) print(sys.exc_info()[0], sys.exc_info()[1]) self.mutex.acquire() self.threads -= 1 self.mutex.release()

def onSubmit(self):

Form.onSubmit(self)

localdir = self.content[‘Local Dir’].get() remotedir = self.content[‘Remote Dir’].get() servername = self.content[‘Server Name’].get() filename = self.content[‘File Name’].get()

username = self.content[‘User Name?’].get()

password = self.content[‘Password?’].get()

userinfo = ()

if username and password:

userinfo = (username, password) if localdir:

os.chdir(localdir)

self.mutex.acquire() self.threads += 1 self.mutex.release() ftpargs = (filename, servername, remotedir, userinfo) _thread.start_new_thread(self.transfer, ftpargs) showinfo(self.title, ‘%s of "%s" started’ % (self.mode, filename))

def onCancel(self):

if self.threads == 0:

Tk().quit()

else:

showinfo(self.title,

‘Cannot exit: %d threads running’ % self.threads)

class FtpGetfileForm(FtpForm):

title = ‘FtpGetfileGui’ mode = ‘Download’ def do_transfer(self, filename, servername, remotedir, userinfo): getfile.getfile(filename, servername, remotedir, userinfo, verbose=False, refetch=True)

if __name__ == ‘__main__’:

FtpGetfileForm() mainloop()

Если вернуться в конец предыдущей главы, можно обнаружить, что эта версия по структуре аналогична приведенной там. В действительности они даже названы одинаково (и отличаются только тем, что находятся в разных каталогах). Однако в данном примере класс умеет пользоваться модулем getfile, использующим протокол FTP, приведенным в начале данной главы, а не основанным на сокетах модулем getfile, с которым мы познакомились в предыдущей главе. Кроме того, эта версия создает большее количество полей ввода, как видно на рис. 13.2.

 

Обратите внимание, что здесь в поле ввода имени файла можно ввести абсолютный путь к файлу. Если его не указать, сценарий будет искать файл в текущем рабочем каталоге, который изменяется после каждой загрузки с сервера и может зависеть от того, где запускается графический интерфейс (то есть текущий каталог будет иным, когда сценарий запускается из программы PyDemos, находящейся в корневом каталоге дерева примеров). При щелчке на кнопке Submit в этом графическом интерфейсе (или нажатии клавиши Enter) сценарий просто передает значения полей ввода формы как аргументы функции FTP getfile.getfile, показанной выше в этом разделе в примере 13.4. Кроме того, он выводит окно, сообщающее о начале загрузки (рис. 13.3).

Рис. 13.3. Информационное окно для FTP-версии модуля getfile

 

Все сообщения о состоянии загрузки, включая сообщения об ошибках FTP, данная версия выводит в окно консоли. Ниже показаны сообщения, которые выводятся в случае успешной загрузки двух файлов и одной неудачной попытки (для удобочитаемости я добавил пустые строки):

C:\\PP4E\Internet\Ftp> getfilegui.py

Server Name

=>

ftp.rmi.net

User Name?

=>

lutz

Local Dir

=>

test

File Name

=>

about-pp.html

Password?

=>

xxxxxxxx

Remote Dir

=>

.

Download of

"about-pp.

html" successful

Server Name

=>

ftp.rmi.net

User Name?

=>

lutz

Local Dir

=>

C:\temp

File Name

=>

ora-lp4e-big.jpg

Password?

=>

xxxxxxxx

Remote Dir

=>

.

Download of

"ora-lp4e-

big.jpg" successful

Server Name

=>

ftp.rmi.net

User Name?

=>

lutz

Local Dir

=>

C:\temp

File Name

=>

ora-lp4e.jpg

Password?

=>

xxxxxxxx

Remote Dir

=>

.

Download of "ora-lp4e.jpg" has failed: <class ‘ftplib.error_perm’>

550 ora-lp4e.jpg: No such file or directory

 

При наличии имени пользователя и пароля загрузчик производит регистрацию под определенной учетной записью. Для анонимного доступа к FTP нужно оставить поля имени пользователя и пароля пустыми.

Теперь, чтобы проиллюстрировать поддержку многопоточной модели выполнения в данном графическом интерфейсе, запустим загрузку большого файла, а затем, пока продолжается загрузка этого файла, попробуем запустить загрузку другого файла. Графический интерфейс остается активным во время загрузки, поэтому просто изменим значения полей ввода и еще раз нажмем кнопку Submit.

Загрузка второго файла начнется и будет выполняться параллельно той, что была запущена первой, так как каждая загрузка выполняется в отдельном потоке выполнения и одновременно могут быть активными несколько соединений с Интернетом. Сам графический интерфейс остается активным во время загрузки только потому, что загрузка происходит в отдельном потоке выполнения — если бы это было не так, даже перерисовка экрана не происходила бы до завершения загрузки.

Мы уже обсуждали потоки выполнения в главе 5, а особенности их использования в графических интерфейсах — в главах 9 и 10, но данный сценарий иллюстрирует некоторые практические аспекты использования потоков выполнения:

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

     Чтобы избежать остановки порожденных потоков загрузки на некоторых платформах, графический интерфейс не должен завершаться, пока продолжается хотя бы одна загрузка. Он следит за количеством потоков, в которых производится загрузка, и выводит окно, если попытаться закрыть графический интерфейс нажатием кнопки Cancel во время загрузки.

В главе 10 мы познакомились со способами, позволяющими обойти правило, запрещающее трогать графический интерфейс из потоков выполнения, и мы будем применять их в примере PyMailGui, в следующей главе. Для обеспечения переносимости действительно нельзя закрывать графический интерфейс, пока счетчик активных потоков не уменьшится до нуля. Для достижения того же эффекта можно было бы использовать модель выхода из модуля threading, представленного в главе 5. Ниже показан вывод, который появляется в окне консоли, когда одновременно выполняется загрузка двух файлов:

C:\\PP4E\Internet\Ftp> python getfilegui.py

Server Name

=>

ftp.rmi.net

User Name?

=>

lutz

Local Dir

=>

C:\temp

File Name

=>

spain08.JPG

Password?

=>

xxxxxxxx

Remote Dir

=>

.

Server Name

=>

ftp.rmi.net

User Name?

=>

lutz

Local Dir

=>

C:\temp

File Name

=>

index.html

Password?

=>

xxxxxxxx

Remote Dir

=>

.

Download of

"index.html"

successful

Download of

"spain08.JPG

" successful

 

Конечно, этот сценарий ненамного полезнее, чем инструмент командной строки, но его можно легко модифицировать, изменив программный код на языке Python, и его вполне достаточно, чтобы считать его простым наброском интерфейса пользователя FTP. Кроме того, поскольку этот графический интерфейс выполняет загрузку в отдельных потоках выполнения, из него можно одновременно загружать несколько файлов, не запуская другие программы клиентов FTP.

Пока мы озабочены графическим интерфейсом, добавим также простой интерфейс и к утилите putfile. Сценарий в примере 13.8 создает диалог для запуска выгрузки файлов на сервер в отдельных потоках выполнения и использует базовую логику работы с протоколом FTP, импортированную из примера 13.5. Он почти не отличается от только что написанного графического интерфейса для getfile, поэтому ничего нового о нем сказать нельзя. В действительности, поскольку операции приема и отправки столь схожи с точки зрения интерфейса, значительная часть логики формы для получения была умышленно выделена в один родовой класс (FtpForm), благодаря чему изменения требуется производить только в одном месте. Таким образом, графический интерфейс к сценарию отправки по большей части повторно использует графический интерфейс к сценарию получения, с измененными метками и методом передачи. Он находится в отдельном файле, что облегчает его запуск как самостоятельной программы.

Пример 13.8. PP4E\Internet\Ftp\putfilegui.py

############################################################################ запускает функцию FTP putfile из многократно используемого класса формы графического интерфейса; см. примечания в getfilegui: справедливыми остаются большинство тех же предупреждений; формы для получения и отправки выделены в единый класс, чтобы производить изменения лишь в одном месте;

############################################################################

from tkinter import mainloop import putfile, getfilegui

class FtpPutfileForm(getfilegui.FtpForm):

title = ‘FtpPutfileGui’

mode = ‘Upload’

def do_transfer(self, filename, servername, remotedir, userinfo): putfile.putfile(filename, servername, remotedir, userinfo, verbose=False)

if __name__ == ‘__main__’:

FtpPutfileForm() mainloop()

Графический интерфейс этого сценария весьма похож на графический интерфейс сценария загрузки файлов, поскольку действует практически тот же программный код. Отправим несколько файлов с клиентского компьютера на сервер. На рис. 13.4 показано состояние графического интерфейса во время отправки одного из файлов.

А ниже приводится вывод в окне консоли при последовательной отправке двух файлов. Здесь выгрузка файлов также выполняется в отдельных потоках, поэтому, если попытаться запустить выгрузку нового файле прежде, чем закончится выгрузка текущего, они перекроются по времени:

C:\\PP4E\Internet\Ftp\test> ..\putfilegui.py

Server Name => ftp.rmi.net

User Name? => lutz

Local Dir => .

File Name => sousa.au

Password? => xxxxxxxx

Remote Dir => .

Upload of "sousa.au" successful

Server Name => ftp.rmi.net

User Name? => lutz

Local Dir => .

File Name => about-pp.html

Password? => xxxxxxxx

Remote Dir => .

Upload of "about-pp.html" successful

Рис. 13.4. Форма ввода для FTP-версии модуляputfile

 

Наконец, можно увязать оба графических интерфейса в единый запускающий сценарий, который умеет запускать интерфейсы получения и отправки независимо от того, в каком каталоге мы находимся при запуске сценария и от платформы, на которой он выполняется. Этот процесс приводится в примере 13.9.

Пример 13.9. PP4E\Internet\Ftp\PyFtpGui.pyw

############################################################################ запускает графические интерфейсы получения и отправки по ftp независимо от каталога, в котором находится сценарий; сценарий не обязательно должен находиться в os.getcwd; можно также жестко определить путь

в $PP4EHOME или guessLocation; можно было бы также так: [from PP4E. launchmodes import PortableLauncher, PortableLauncher(‘getfilegui‘, ‘%s/ getfilegui.py‘ % mydir)()], но в Windows понадобилось бы всплывающее окно DOS для вывода сообщений о состоянии, описывающих выполняемые операции; ############################################################################

import os, sys

print(‘Running in: ‘, os.getcwd())

#  PP3E

#  from PP4E.Launcher import findFirst

#  mydir = os.path.split(findFirst(os.curdir, ‘PyFtpGui.pyw’))[0]

#  PP4E

from PP4E.Tools.find import findlist

mydir = os.path.dirname(findlist(‘PyFtpGui.pyw’, startdir=os.curdir)[0])

if sys.platform[:3] == ‘win’:

os.system(‘start %s\getfilegui.py’ % mydir) os.system(‘start %s\putfilegui.py’ % mydir) else:

os.system(‘python %s/getfilegui.py &’ % mydir) os.system(‘python %s/putfilegui.py &’ % mydir)

Обратите внимание, что здесь мы повторно используем утилиту find из примера 6.13 в главе 6 — на сей раз чтобы определить путь к домашнему каталогу сценария, необходимый для конструирования командных строк. При использовании программ запуска в корневом каталоге дерева примеров или при вызове сценария из командной строки в любом другом каталоге текущий рабочий каталог может не совпадать с каталогом, где находится сценарий. В предыдущем издании вместо поиска своего собственного каталога этот сценарий использовал инструмент из модуля Launcher (аналогичные решения вы найдете в пакете с примерами).

Если запустить этот сценарий, на экране появятся оба графических интерфейса — для получения и отправки — как отдельные независимо выполняемые программы. Альтернативой может быть прикрепление обеих форм к единому интерфейсу. Конечно, можно создать значительно более замысловатые интерфейсы. Например, можно воспользоваться всплывающими диалогами для выбора локальных файлов и отобразить виджеты, сообщающие о ходе выполнения текущей операции загрузки или выгрузки. Можно даже отобразить доступные на удаленном сервере файлы в окне списка, запросив содержимое удаленного каталога через соединение FTP. Однако, чтобы научиться добавлять такие функции, нужно перейти к следующему разделу.

Использованная литература:

Марк Лутц — Программирование на Python, 4-е издание, II том, 2011

Каталог сайтов Всего.ру
Оцените статью
Секреты программирования
Добавить комментарий