XFile

关于XFile XFile架构 利用XFile打造家用文件服务系统 移动端应用 开发历史 下    载 授    权 联系我们

跨平台简化客户端

为了支持各种Linux Unix Mac Windows等多种桌面,我们使用Python3 + wxPython4 编写了这个跨平台桌面客户端,功能进行了大幅度缩减,但是一般的桌面点播和检索功能都已经做进去,这个程序只是我们提供的一个额外的开源框架客户端,主要是为了方便用户在此基础上开发自己想要的功能,但是对我们来说,它只是个备胎客户端; 因为各种平台Python运行的方式都不同 , 例如在windows环境下,您可以在命令行下,使用 python xftp.py来运行这个客户端,也可以写个批处理然后放个快捷方式到桌面上. 或者直接双击xftp.py也可以运行这个程序,但是需要注意的是,为了运行本程序,必须在您的电脑上安装python3 https://www.python.org/  , 此外还需要安装wxpython https://www.wxpython.org

支持的功能:

md5登录/加密md5登录XFile服务器 (并支持手动启用SHA256加密口令以支持未来版本)

新版本已经使用SHA256加密加盐登录协议

文件列表

切换

检索

点播 (新版本强制使用Http流映射功能)

 

 

源代码下载:  点击这里  (windows等平台下可直接双击运行xftp.py]

运行效果图:

 

 

下面是初始版本源代码,但是注意,由于编辑器自动将缩进给吃掉了,所以你不能直接拷贝下面的内容到编辑器,仅供参考

# -*- coding: utf-8 -*-

###########################################################################
## 程序设计 danscort yu (c)2018
##
##
##
###########################################################################
###########################################################################
## 本代码使用wxPython + ftplib 编写 注意 只能在python版本高于3的环境下执行,不支持python2
## 专门适应XFile家庭文件服务器软件的跨平台运行播放客户端
## 代码里硬代码支持中文和英文,当然你也可以扩展支持各种其他语言,外挂也可以,但是我不想在细节上花费太多时间,因为windows平台已经有专用客户端,主要是支持Linux/unix平台用
## 注意 如果要使用标准FTP服务器软件 则必须注销掉SLST HTTP等扩展指令 并且换用不安全的明文口令,
## XFile服务器软件下载 https://www.phoenixp2p.com
## List列表指令采用XFile专用的简化格式列表 去掉SLST 可以切换回标准FTP列表
## 事实上XFile可以支持 例如 http映射,这样可以让网络播放更流畅 我保留了http选择项,用于将来的扩展
## 我们支持XFile加密口令 [加盐md5口令],这里我们套用了移动端验证口令 两种方式 都是加盐的验证 可以抗破解
## 因为各种限制 ,只做了检索支持
## python 和wxPython我都是第一次使用, 不熟悉,项目比较粗糙,无法象native c++开发界面那样精确
## 执行方式 命令行下 输入 python xftp.py 或者建立一个批处理的快捷方式放到桌面上
## 目录下的xftpset文件是外部播放器程序的播放指令等信息 您应该根据你播放器的实际安装目录进行修改
## 在windows端支持 vlc mpv kodi 等主流播放器
## Player_cmd=H:\VLC\vlc.exe "$url"
## Player_cmdunix=vlc.exe "$url"
## 前一个Player_cmd用于windows环境 ,后一个用于非windows环境 ,请根据实际进行修改 $url代表ftp实际连接
## 为了运行本程序,必须在您的电脑上安装python3 https://www.python.org/ , 此外还需要安装wxpython https://www.wxpython.org
## 如果是正式版本,请将全局变量_debugmode 设置为False
##
##
##
##
##
##
##
##
##
## 您可以任意修改本代码 但是请保留本说明部分
## 作者: danscort@phoenixp2p.com
##
###########################################################################
import wx
import sys
import time
import wx.xrc
import socket
import os
import ftplib
from ftplib import FTP
from ftplib import FTP_TLS
import configparser
import hashlib
#from StringIO import StringIO
#import images

###########################################################################
## Class MyFrameMain
###########################################################################
#global m_ftp
#global m_ftps
#global mb_con
##以下是全局变量部分
_ftp=FTP( )
_ftps=FTP_TLS( )
_mbcon=True
_mbssl=False
_httpport=0
_debugmode=True
# 如果是正式发行版本 请修改为 _debugmode=False
_curworkdir="/"
_chineseversion=True
_timersec=0
#播放调用程序 默认是vlc.exe 我们从配置文件读这个播放接口 这里只是预配置
_playercmd='vlc.exe "$url"'
#配置文件
_configfile="xftpset.ini"
_dlgitemfile="dlgitem.ini"
#随机数 XFile专用的随机数字符串
_srandomcode="0"
#全局登录口令 是二次MD5, 但是注意 这个全局字符串有时间限制 可能在服务器某次刷新后就发生变动
_sglobalpass=""


class MyFrameMain ( wx.Frame ):

def __init__( self, parent ):
global _chineseversion
#reload(sys)
#sys.setdefaultencoding('utf-8')
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = "XFtp", pos = wx.DefaultPosition, size = wx.Size( 800,530 ), style = wx.CAPTION|wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )

bSizer3 = wx.BoxSizer( wx.VERTICAL )

bSizer1 = wx.BoxSizer( wx.HORIZONTAL )
bSizer2 = wx.BoxSizer( wx.HORIZONTAL )

bSizer1.SetMinSize( wx.Size( 600,20 ) )
bSizer2.SetMinSize( wx.Size( 600,20 ) )
if(_chineseversion):
self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, "服务器地址 ", wx.DefaultPosition, wx.Size( 60,15 ), 0 )
else:
self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, "Server ", wx.DefaultPosition, wx.Size( 60,15 ), 0 )

self.m_staticText1.Wrap( -1 )

bSizer1.Add( self.m_staticText1, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_textCtrlServer = wx.TextCtrl( self, wx.ID_ANY, "127.0.0.1", wx.DefaultPosition, wx.Size( 100,15 ), 0 )

else:
self.m_textCtrlServer = wx.TextCtrl( self, wx.ID_ANY, "127.0.0.1", wx.DefaultPosition, wx.Size( 100,15 ), 0 )

#self.m_textCtrlServer = wx.TextCtrl( self, wx.ID_ANY, "127.0.0.1", wx.DefaultPosition, wx.Size( 100,15 ), 0 )
bSizer1.Add( self.m_textCtrlServer, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, "端口", wx.DefaultPosition, wx.Size( 30,15 ), 0 )

else:
self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, "Port", wx.DefaultPosition, wx.Size( 30,15 ), 0 )

#self.m_staticText2 = wx.StaticText( self, wx.ID_ANY, u"Port", wx.DefaultPosition, wx.Size( 30,15 ), 0 )
self.m_staticText2.Wrap( -1 )

bSizer1.Add( self.m_staticText2, 0, wx.ALL, 5 )

self.m_textCtrlPort = wx.TextCtrl( self, wx.ID_ANY, "21", wx.DefaultPosition, wx.Size( 45,15 ), 0 )
bSizer1.Add( self.m_textCtrlPort, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, "用户名", wx.DefaultPosition, wx.Size( 50,15 ), 0 )

else:
self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, "User", wx.DefaultPosition, wx.Size( 50,15 ), 0 )


#self.m_staticText3 = wx.StaticText( self, wx.ID_ANY, "User", wx.DefaultPosition, wx.Size( 50,15 ), 0 )
self.m_staticText3.Wrap( -1 )

bSizer1.Add( self.m_staticText3, 0, wx.ALL, 5 )

self.m_textCtrlUsername = wx.TextCtrl( self, wx.ID_ANY, "admin", wx.DefaultPosition, wx.Size( 80,15 ), 0 )
bSizer1.Add( self.m_textCtrlUsername, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, "口令", wx.DefaultPosition, wx.Size( 50,15 ), 0 )

else:
self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, "Password", wx.DefaultPosition, wx.Size( 50,15 ), 0 )

#self.m_staticText4 = wx.StaticText( self, wx.ID_ANY, "Password", wx.DefaultPosition, wx.Size( 50,15 ), 0 )
self.m_staticText4.Wrap( -1 )

bSizer1.Add( self.m_staticText4, 0, wx.ALL, 5 )

self.m_textCtrlPassword = wx.TextCtrl( self, wx.ID_ANY, "88888888", wx.DefaultPosition, wx.Size( 70,15 ), style=wx.TE_PASSWORD )
#这里加入 设置textctrl 是口令模式

bSizer1.Add( self.m_textCtrlPassword, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_checkBoxHttp = wx.CheckBox( self, wx.ID_ANY, "Http流优先", wx.DefaultPosition, wx.Size( 80,15 ), 0 )

else:
self.m_checkBoxHttp = wx.CheckBox( self, wx.ID_ANY, "Http stream", wx.DefaultPosition, wx.Size( 80,15 ), 0 )

#self.m_checkBoxHttp = wx.CheckBox( self, wx.ID_ANY, u"Http stream", wx.DefaultPosition, wx.Size( 65,15 ), 0 )
bSizer1.Add( self.m_checkBoxHttp, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_checkBoxSsl = wx.CheckBox( self, wx.ID_ANY, "SSL加密", wx.DefaultPosition, wx.Size( 60,15 ), 0 )

else:
self.m_checkBoxSsl = wx.CheckBox( self, wx.ID_ANY, "TLS/SSL", wx.DefaultPosition, wx.Size( 60,15 ), 0 )


#self.m_checkBoxSsl = wx.CheckBox( self, wx.ID_ANY, "SSL", wx.DefaultPosition, wx.Size( 45,15 ), 0 )
bSizer1.Add( self.m_checkBoxSsl, 0, wx.ALL, 5 )

if(_chineseversion):
self.m_buttonLogin = wx.Button( self, wx.ID_ANY, "连接", wx.DefaultPosition, wx.DefaultSize, 0 )

else:
self.m_buttonLogin = wx.Button( self, wx.ID_ANY, "Connect", wx.DefaultPosition, wx.DefaultSize, 0 )

#self.m_buttonLogin = wx.Button( self, wx.ID_ANY, "Connect", wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.m_buttonLogin, 0, wx.ALL, 5 )

#这里我决定添加检索功能
self.m_staticinfo=wx.StaticText( self, wx.ID_ANY, "/", wx.DefaultPosition, wx.Size( 220,18 ), wx.ALIGN_LEFT )
self.m_staticinfo.Wrap( -1 )
bSizer2.Add(self.m_staticinfo)
if(_chineseversion):
self.m_staticsearch=wx.StaticText( self, wx.ID_ANY, "关键词:", wx.DefaultPosition, wx.Size( 80,18 ), wx.ALIGN_LEFT )

else:
self.m_staticsearch=wx.StaticText( self, wx.ID_ANY, "Keywords:", wx.DefaultPosition, wx.Size( 80,18 ), wx.ALIGN_LEFT )

#self.m_staticsearch=wx.StaticText( self, wx.ID_ANY, "Keywords:", wx.DefaultPosition, wx.Size( 80,18 ), wx.ALIGN_LEFT )
self.m_staticsearch.Wrap( -1 )
bSizer2.Add(self.m_staticsearch)
self.m_textsearch = wx.TextCtrl( self, wx.ID_ANY, ".mkv", wx.DefaultPosition, wx.Size( 120,18 ), wx.ALIGN_LEFT )
bSizer2.Add( self.m_textsearch, 0, wx.ALL, 5 )
if(_chineseversion):
self.m_buttonSearch = wx.Button( self, wx.ID_ANY, "检索", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT )

else:
self.m_buttonSearch = wx.Button( self, wx.ID_ANY, "Search", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT )

#self.m_buttonSearch = wx.Button( self, wx.ID_ANY, "Search", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT )
bSizer2.Add( self.m_buttonSearch, 0, wx.ALL, 5 )

bSizer3.Add( bSizer1, 1, wx.ALIGN_TOP|wx.FIXED_MINSIZE|wx.LEFT, 5 )
bSizer3.Add( bSizer2, 1, wx.ALIGN_TOP|wx.FIXED_MINSIZE|wx.LEFT, 5 )
bSizerList = wx.BoxSizer( wx.VERTICAL )

self.il = wx.ImageList(16, 16)
#self.idx1 = self.il.Add(images.Smiles.GetBitmap())
#self.sm_up = self.il.Add(images.SmallUpArrow.GetBitmap())
#self.sm_dn = self.il.Add(images.SmallDnArrow.GetBitmap())
self.m_listCtrlFtp = wx.ListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.Size( 800,500 ), wx.LC_REPORT)
self.m_listCtrlFtp.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
bSizerList.Add( self.m_listCtrlFtp, 0, wx.ALL, 5 )
bSizer3.Add( bSizerList, 1, wx.EXPAND, 5 )


self.SetSizer( bSizer3 )
self.Layout()

self.Centre( wx.BOTH )

# Connect Events
self.Bind( wx.EVT_CLOSE, self.OnMyClose )
self.m_buttonLogin.Bind( wx.EVT_BUTTON, self.OnMyButtonClick )
self.m_buttonSearch.Bind( wx.EVT_BUTTON, self.OnMySearchButtonClick )
self.m_listCtrlFtp.Bind( wx.EVT_LIST_COL_CLICK, self.OnMyListColClick )
self.m_listCtrlFtp.Bind( wx.EVT_LIST_ITEM_ACTIVATED, self.OnMyListItemActivated )
self.m_listCtrlFtp.Bind( wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnMyListItemRightClick )
#self.m_listCtrlFtp.Bind( wx.EVT_LIST_COL_RIGHT_CLICK, self.OnMyRightClick)
# for wxMSW
self.m_listCtrlFtp.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnMyRightClick)

# for wxGTK
self.m_listCtrlFtp.Bind(wx.EVT_RIGHT_UP, self.OnMyRightClick)

#insert list columns num attr size filename
#self.m_listCtrlFtp.InsertColumn(0,"Num")
#self.m_listCtrlFtp.InsertColumn(1,"Type")
#self.m_listCtrlFtp.InsertColumn(2,"Size")
#self.m_listCtrlFtp.InsertColumn(3,"Name")
#we need to insert images for list
info = wx.ListItem()
info.Mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
info.Image = -1
info.Align = 0
if(_chineseversion):
info.Text = "编号"
else:
info.Text = "Num"
#info.Text = u"Num"
self.m_listCtrlFtp.InsertColumn(0, info)

info = wx.ListItem()
info.Mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT
#info.Image = -1
info.Align = 0
if(_chineseversion):
info.Text = "类型"
else:
info.Text = "Type"
#info.Text = u"Type"
self.m_listCtrlFtp.InsertColumn(1, info)

info = wx.ListItem()
info.Mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT
#info.Image = -1
info.Align = 0
info.SetWidth(120)
if(_chineseversion):
info.Text = "大小"
else:
info.Text = "Size"
#info.Text = "Size"
self.m_listCtrlFtp.InsertColumn(2, info)

info = wx.ListItem()
info.Mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT
#info.Image = -1
info.Align = 0
if(_chineseversion):
info.Text = "名称"
else:
info.Text = "Name"
#info.Text = "Name"
info.SetWidth(480)
self.m_listCtrlFtp.InsertColumn(3, info)
# Bind all EVT_TIMER events to self.OnTest1Timer
#设置定时器 主要是用来支持定时发送NOOP指令 防止服务器断开连接
self.timer1 = wx.Timer(self,8801)
#必须指定只处理timer1事件
self.Bind(wx.EVT_TIMER, self.OnMyTimerMsg,self.timer1)
#注意 绑定是在这里

self.FuncChangeButton( )
#Load dlg item strings
self.FuncLoadDlgItem( )

def __del__( self ):
pass


# Virtual event handlers, overide them in your derived class
def OnMyClose( self, event ):
#event.Skip()
global _mbcon
global _ftp
global _ftps
global _mbssl
if(_mbcon):
pass
else:
try:
if(mbssl):
_ftps.close( )
else:
_ftp.close( )
except:
pass
self.FuncSaveDlgItem()
event.Skip( )

def FuncSaveDlgItem(self):
#在退出的时候保存参数
global _dlgitemfile
global _debugmode
if(_debugmode):
print("Now will save dlg string to file")
cf=configparser.RawConfigParser()
cf.add_section('Dlgitem')
cf.set('Dlgitem', 'server_addr', self.m_textCtrlServer.GetLineText(0))
cf.set('Dlgitem', 'server_port', self.m_textCtrlPort.GetLineText(0))
cf.set('Dlgitem', 'server_username', self.m_textCtrlUsername.GetLineText(0))
cf.set('Dlgitem', 'server_password', self.m_textCtrlPassword.GetLineText(0))
str1='0'
if(self.m_checkBoxSsl.GetValue()):
str1='1'
cf.set('Dlgitem', 'server_ssl', str1)
str1='0'
if(self.m_checkBoxHttp.GetValue()):
str1='1'
cf.set('Dlgitem', 'server_http', str1)
#cf.set('Dlgitem', 'server_addr', self.m_textCtrlServer.GetLineText(0))
#cf.set('Dlgitem', 'server_addr', self.m_textCtrlServer.GetLineText(0))
with open(_dlgitemfile, 'wt') as configfile:
cf.write(configfile)

return




def FuncLoadDlgItem(self):
#读取主界面的部分参数
global _dlgitemfile
global _debugmode
cf=configparser.ConfigParser()
try:
cf.read(_dlgitemfile)
except IOError as e:
if(_debugmode):
print("Failed to load string file")
return
except :
if(_debugmode):
print("Unknwon error to load string file")
return
#现在保证已经正常打开配置文件 那么尝试读取吧
str1=''
try:
str1=cf.get("Dlgitem","server_addr")
if(len(str1)>=1):
self.m_textCtrlServer.SetLabelText(str1)
str1=''
str1=cf.get("Dlgitem","server_port")
if(len(str1)>=1):
self.m_textCtrlPort.SetLabelText(str1)
str1=''
str1=cf.get("Dlgitem","server_username")
if(len(str1)>=1):
self.m_textCtrlUsername.SetLabelText(str1)
str1=''
str1=cf.get("Dlgitem","server_password")
if(len(str1)>=1):
self.m_textCtrlPassword.SetLabelText(str1)
if(cf.get("Dlgitem","server_ssl")=='1'):
self.m_checkBoxSsl.SetValue(True)
if(cf.get("Dlgitem","server_http")=='1'):
self.m_checkBoxHttp.SetValue(True)

except (configparser.MissingSectionHeaderError,
configparser.NoSectionError,
configparser.NoOptionError) as e:
if(_debugmode):
print("Read string section error")
return
except :
if(_debugmode):
print("Read string section unknown error")
return
return

def FuncLoadConfigFile(self):
#读取配置文件
#默认是当前目录下的xftp.ini
#如果你要支持多国家语言 事实上也可以在这里进行
#我是贪图方便 只做了英文和简体中文 硬编码 当然用c++的客户端我是采用的配置文件实现多国语言支持
global _configfile
global _playercmd
global _debugmode
cf=configparser.ConfigParser()
#cf=configparser.RawConfigParser( )
try:
#cf.read(_configfile,'utf-8')
#with open(_configfile, 'rb') as f:
# content = f.read().decode('utf-8-sig').encode('utf8')
# cf.readfp(StringIO(content))
cf.read(_configfile)


except IOError as e:
if(_debugmode):
print("Failed to load config file")
return

except:
if(_debugmode):
print("Unknwon error to load config file")
return
str1=''
try:
#这里根据不同的操作系统读取不同的执行命令
if(os.name=='nt'):
str1=cf.get("XFtp_Set","Player_cmd")
else:
str1=cf.get("XFtp_Set","Player_cmdunix")
except (configparser.MissingSectionHeaderError,
configparser.NoSectionError,
configparser.NoOptionError) as e:
if(_debugmode):
print("Read section error")
return
except:
if(_debugmode):
print("Unknown error to read section")
str1=''
if(len(str1)<=0):
_playercmd='vlc.exe "$url"'
else:
_playercmd=str1
if(_debugmode):
print(_playercmd)
return




def OnMySearchButtonClick(self, event):
#如果用户按了检索按钮
global _mbcon
global _mbssl
global _ftp
global _ftps
#str6=""
if(_mbcon):
return
str5=self.m_textsearch.GetLineText(0)
if(len(str5)<=1):
return
str5="SKEY "+str5
if(_mbssl):
try:
_ftps.sendcmd(str5)
except (socket.error, socket.gaierror):
self.FuncDisconnect( )
return
except ftplib.error_perm:
return
except:
self.FuncDisconnect( )
return
else:
try:
_ftp.sendcmd(str5)
except (socket.error, socket.gaierror):
self.FuncDisconnect( )
return
except ftplib.error_perm:
return
except:
self.FuncDisconnect( )
return

if(self.FuncGetList()==0):
self.FuncDisconnect()
return

return

def FuncGetList(self):
#如果连接失败 则返回0 如果目录不存在等 则返回 -1
global _chineseversion
global _timersec
data=[]
if(_debugmode):
print("Func getlist is called")
_timersec=0
#这里需要加入获取当前目录的操作函数

if(_mbssl):
try:
#如果需要使用加密获取目录列表 那么每次都要重新发送prot_p
#我这里直接采用明文,因为并不是什么机密内容 单纯将登录过程进行保密已经足够了
_ftps.retrlines('LIST',data.append)
except (socket.error, socket.gaierror):
return 0
except ftplib.error_perm:
return -1
except:
return 0
else:
pass
else:
try:
_ftp.retrlines('LIST',data.append)
except (socket.error, socket.gaierror):
return 0
except ftplib.error_perm:
return -1
except:
return 0
#现在可以保证已经获取到了数据
# 我们的目标服务器是XFile服务器 因此提供的是XFile标准扩展格式
# 格式如下
# 现在处理data数据
# filesie 1/0 modtime |filename or dirname
# filesize=64bit long int
arr1=[]
iline=0
filesize=0
modtime=0
isdir=0
self.m_listCtrlFtp.DeleteAllItems( )
iline=self.m_listCtrlFtp.InsertItem(self.m_listCtrlFtp.GetItemCount( ),"",-1)
self.m_listCtrlFtp.SetItem(iline,0,str(iline))
if(_chineseversion):
self.m_listCtrlFtp.SetItem(iline,1,"目录")
else:
self.m_listCtrlFtp.SetItem(iline,1,"Dir")

#self.m_listCtrlFtp.SetItem(iline,1,"Dir")
self.m_listCtrlFtp.SetItem(iline,2,"0")
self.m_listCtrlFtp.SetItem(iline,3,"..")

for str2 in data :
if(_debugmode):
print(str2)

if(len(str2)<5):
continue
#spilit line by |
i2=str2.find('|')
if(i2<=3 or i2>=len(str2)):
continue
else:
str3=str(str2[i2+1: ])
str3.strip('\n')
str3.strip('\r')
str3.strip(' ')
#文件名提取成功 下面提取文件长度等信息
arr1=str2.split()
if(len(arr1)<3):
continue;
#执行数字转换吧
# 注意格式
filesize=int(arr1[0],16)
isdir=not int(arr1[1])
modtime=int(arr1[2],16)
#完成转换 现在执行插入操作
iline=self.m_listCtrlFtp.InsertItem(self.m_listCtrlFtp.GetItemCount( ),"",-1)
self.m_listCtrlFtp.SetItem(iline,0,str(iline))
if(isdir):
if(_chineseversion):
self.m_listCtrlFtp.SetItem(iline,1,"目录")
else:
self.m_listCtrlFtp.SetItem(iline,1,"Dir")
#self.m_listCtrlFtp.SetItem(iline,1,"Dir")
self.m_listCtrlFtp.SetItem(iline,2,"0")
else:
if(_chineseversion):
self.m_listCtrlFtp.SetItem(iline,1,"文件")
else:
self.m_listCtrlFtp.SetItem(iline,1,"File")
#self.m_listCtrlFtp.SetItem(iline,1,"File")
self.m_listCtrlFtp.SetItem(iline,2,str(filesize))
self.m_listCtrlFtp.SetItem(iline,3,str3)
#定时器
#self.timer1 = wx.Timer(self)
return 1

def FuncGetRandomCode(self):
global _ftp
global _ftps
global _srandomcode
global _mbssl
global _debugmode

str5=""
try:
if(_mbssl):
str5=_ftps.sendcmd("MKEY")
else:
str5=_ftp.sendcmd("MKEY")
except (socket.error, socket.gaierror):
return False

except ftplib.error_perm:
if(_debugmode):
print("Error: server do not support MKEY")
return False
except:
return False
#现在分解返回的参数吧
arr2=[ ]
arr2=str5.split(' ')
if(len(arr2)<3):
if(_debugmode):
print("Error: bad response from server of MKEY")

return False
if(_debugmode):
print(str5)
print(arr2[1])
_srandomcode=arr2[1]
return True







def FunXFileExtend(self):
global _mbcon
global _debugmode
global _mbssl
global _ftp
global _ftps
global _httpport
global _sglobalpass

_sglobalpass=self.m_textCtrlPassword.GetLineText(0)

if(_debugmode):
print("XFileExteng function is called")
str3="SLST 0"
str4=""
_httpport=0
if(_mbssl):
_ftps.set_pasv(True)
try:
str4=_ftps.sendcmd(str3)
except (socket.error, socket.gaierror):
return 0

except ftplib.error_perm:
if(_debugmode):
print("Reponse error for SLST command")
return 0
except:
return 0
#https模式 需要新加入是采用p或者c模式支持
try:
#改用明文模式
_ftps.prot_c()
#_ftps.prot_p( )
#_ftps._prot_p=True

except (socket.error, socket.gaierror):
return 0
except ftplib.error_perm:
if(_debugmode):
print("Reponse error for PROT command")
return 0
except:
return 0
#发送SALT指令获取全局随机数
str3="SALT"
try:
str4=_ftps.sendcmd(str3)
except ftplib.error_perm:
return 0
except:
return 0
#提取服务器的回答
arr8=[]
arr8=str4.split(' ')
if(len(arr8)>=2):
str4=arr8[1]
if(_debugmode):
print(str4)
#执行清理
str4.strip(' ')
str4.strip('\n')
str4.strip('\r')
#这里可以生成全局的登录口令了
strnew=str4
strnew+=self.m_textCtrlPassword.GetLineText(0)
strnew+=str4;
strnew+="()"
if(_debugmode):
print(strnew)
md55=hashlib.md5()
md55.update(strnew.encode(encoding='utf-8'))
strnew=md55.hexdigest( )
if(_debugmode):
print(strnew)
str3=strnew[0:2]
str3+="_"
str3+=strnew[3:]
strnew=str4
strnew+="<>"
strnew+=str3
#再次生成MD5加密口令
if(_debugmode):
print(strnew)
md55=hashlib.md5()
md55.update(strnew.encode(encoding='utf-8'))
strnew=md55.hexdigest( )
str3=strnew[0:2]
str3+="_"
str3+=strnew[3:]
if(_debugmode):
print(str3)
_sglobalpass=str3


#发送http指令获取服务端口
#if(_debugmode):
#print(str4)

str3="HTTP 0"
try:
str4=_ftps.sendcmd(str3)
except ftplib.error_perm:
return 1
except:
return 0

else:
_ftp.set_pasv(True)
try:
str4=_ftp.sendcmd(str3)
except (socket.error, socket.gaierror):
return 0
except ftplib.error_perm:
return 0
except :
return 0
if(_debugmode):
print(str4)

str3="SALT"
try:
str4=_ftp.sendcmd(str3)
except ftplib.error_perm:
return 0
except:
return 0
#提取服务器的回答
arr8=[]
arr8=str4.split(' ')
if(len(arr8)>=2):
str4=arr8[1]
if(_debugmode):
print(str4)
str4.strip(' ')
str4.strip('\n')
str4.strip('\r')
#这里可以生成全局的登录口令了
#
#
strnew=str4
strnew+=self.m_textCtrlPassword.GetLineText(0)
strnew+=str4;
strnew+="()"
if(_debugmode):
print(strnew)
md55=hashlib.md5()
md55.update(strnew.encode(encoding='utf-8'))
strnew=md55.hexdigest( )
if(_debugmode):
print(strnew)
str3=strnew[0:2]
str3+="_"
str3+=strnew[3:]
strnew=str4
strnew+="<>"
strnew+=str3
if(_debugmode):
print(strnew)
#再次生成MD5加密口令
md55=hashlib.md5()
md55.update(strnew.encode(encoding='utf-8'))
strnew=md55.hexdigest( )
str3=strnew[0:2]
str3+="_"
str3+=strnew[3:]
if(_debugmode):
print(str3)
_sglobalpass=str3
#完成第三方登录口令的制作 可以防止破解


#

str3="HTTP 0"
try:
str4=_ftp.sendcmd(str3)
except (socket.error, socket.gaierror):
return 0
except ftplib.error_perm:
return 1
except :
return 0

#对str4 进行分解 提取http端口号
# 返回 200 端口号 无用的0
if(_debugmode):
print(str4)
arr5=[]
arr5=str4.split(' ')
if(len(arr5)>=2):
_httpport=int(arr5[1],10)
else:
_httpport=0
#完成分解操作 无论用户是否钩选优先使用Http 都会执行一次 我是简化代码 当然如果你有兴趣可以加入判断来减少执行
#

#如果成功设置了简化模式输出 那么就返回成功 虽然我们有更多的方法来验证服务器是XFile服务器
# 但是用这个指令的方法更简单
# 将来如果有需要 可以将口令采用加盐Hash方式进行验证 这样更不容易被破解
#
if(_debugmode):
print("Success get http number="+str(_httpport))

return 1

def FuncChangeButton(self):
global _mbcon
global _chineseversion
if(_mbcon):
if(_chineseversion):
self.m_buttonLogin.SetLabelText("连接")
else:
self.m_buttonLogin.SetLabelText("Connect")
self.m_listCtrlFtp.DeleteAllItems( )
self.m_staticinfo.SetLabelText("/")
self.m_buttonSearch.Disable( )
self.m_textsearch.Disable( )
self.m_textCtrlPassword.Enable( )
self.m_textCtrlPort.Enable( )
self.m_textCtrlServer.Enable( )
self.m_textCtrlUsername.Enable( )
self.m_checkBoxSsl.Enable( )
self.m_checkBoxHttp.Enable( )

else:
if(_chineseversion):
self.m_buttonLogin.SetLabelText("中断")
else:
self.m_buttonLogin.SetLabelText("Disconnect")
self.m_buttonSearch.Enable( )
self.m_textsearch.Enable( )
self.m_textCtrlPassword.Disable( )
self.m_textCtrlPort.Disable( )
self.m_textCtrlServer.Disable( )
self.m_textCtrlUsername.Disable( )
self.m_checkBoxSsl.Disable( )
self.m_checkBoxHttp.Disable( )


def OnMyButtonClick( self, event ):
global _mbcon
global _ftp
global _ftps
global _debugmode
global _mbssl
global _srandomcode
str7=""


if(_mbcon):
#load player file again
self.FuncLoadConfigFile()
#connect the server
strserver=self.m_textCtrlServer.GetLineText(0)
strusername=self.m_textCtrlUsername.GetLineText(0)
strpassword=self.m_textCtrlPassword.GetLineText(0)
iportnum=int(self.m_textCtrlPort.GetLineText(0))
if(_debugmode):
print(strserver)
print(strusername)
print(strpassword)
print("portnum="+str(iportnum))
if(self.m_checkBoxSsl.GetValue()):
if(_debugmode):
print("Now will in TLS-SSL connection mode")
_mbssl=True
_ftps.encoding='utf-8'
else:
if(_debugmode):
print("Now will in Standard connection mode")
_mbssl=False
_ftp.encoding='utf-8'
if(len(strserver)<=0 or len(strusername)<=0 or len(strpassword)<=0 or iportnum<=0 or iportnum>=65535):
#pop dialog , parameters error
dlg=wx.MessageDialog(self,"Bad parameters found","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
else:
if(_debugmode):
print("Debug: now will try to connect remote server")
if(_mbssl):
try:
str7=_ftps.connect(host=strserver,port=iportnum,timeout=23)
except (socket.error, socket.gaierror):
dlg=wx.MessageDialog(self,"Failed to connect server with ftp ssl","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
except:
dlg=wx.MessageDialog(self,"Failed to connect server with ftp ssl 2","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
if(_debugmode):
print(str7)
if(self.FuncGetRandomCode()==False):
dlg=wx.MessageDialog(self,"Bad server type, only support XFile(www.phoenixp2p.com)","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
#这里添加转换 因为明文不安全 这里转换成md5模式的口令
# 注意 这是XFile服务器才支持的格式 普通FTP服务器是不支持的

strpassword=_srandomcode+strpassword
if(_debugmode):
print(strpassword)

#生成MD5加密口令
mhash=hashlib.md5(strpassword.encode(encoding='UTF-8'))
str7=mhash.hexdigest()

if(_debugmode):
print(str7)
strpassword="__"+str7
try:
_ftps.login(strusername,strpassword)
except ftplib.error_perm:
_ftps.close()
dlg=wx.MessageDialog(self,"Failed to login server, check your username and password","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
except:
_ftps.close( )
dlg=wx.MessageDialog(self,"Failed to login server, check your username and password 2","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy( )

return
#现在可以保证已经登录到服务器了 那么尝试获取列表吧 是否加密呢 这是个问题
#乱用SSL会导致性能问题
#注意上面不能用quit 因为quit遇到特殊的服务器回答 会引发投递异常


else:
try:
str7=_ftp.connect(host=strserver,port=iportnum,timeout=15)
except (socket.error, socket.gaierror):
dlg=wx.MessageDialog(self,"Failed to connect server with ftp","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy( )
return
except:
dlg=wx.MessageDialog(self,"Failed to connect server with ftp 2","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
if(_debugmode):
print(str7)
if(self.FuncGetRandomCode()==False):
dlg=wx.MessageDialog(self,"Bad server type, only support XFile(www.phoenixp2p.com)","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return

strpassword=_srandomcode+strpassword
if(_debugmode):
print(strpassword)

#生成MD5加密口令
mhash=hashlib.md5(strpassword.encode(encoding='UTF-8'))
str7=mhash.hexdigest()


if(_debugmode):
print(str7)

strpassword="__"+str7

try:
_ftp.login(strusername,strpassword)
except ftplib.error_perm:
_ftp.close()
dlg=wx.MessageDialog(self,"Failed to login server, check your username and password","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy()
return
except (socket.error, socket.gaierror):
_ftp.close()
dlg=wx.MessageDialog(self,"Disconnected by server","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
return
except:
_ftp.close()
dlg=wx.MessageDialog(self,"Disconnected by server 2","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal( )
dlg.Destroy( )
return
if(self.FunXFileExtend( )==0):
dlg=wx.MessageDialog(self,"Server does not support XFILE command","Error",wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
if(_mbssl):
try:
_ftps.close()
except (socket.error, socket.gaierror):
return
except:
return


else:
try:
_ftp.close( )
except (socket.error, socket.gaierror):
return
except:
return

return
#直接失败 返回吧 事实上上面的close也可能抛出异常 因为概率太小 我直接忽略了
#如果成功了 那么调用列表函数进行刷新操作吧
# 并修改当前目录
#连接成功
_mbcon=False
self.FuncGetList( )

if(_mbcon==False):
#self.timer1.SetOwner(None,8801)
self.timer1.Start(10000)
#每10秒触发一次

#连接成功
#_mbcon=False
self.FuncChangeButton( )
else:
if(_mbssl):
try:
_ftps.close( )
except (socket.error, socket.gaierror):
pass
except:
pass
else:
try:
_ftp.close( )
except (socket.error, socket.gaierror):
pass
except:
pass
self.timer1.Stop()
_mbcon=True
self.FuncChangeButton( )

#关闭定时器
event.Skip()

def OnMyListColClick( self, event ):
event.Skip()

def OnMyListItemActivated( self, event ):
global _debugmode
global _mbcon
global _mbssl
global _ftp
global _ftps
global _httpport
global _curworkdir
global _playercmd
global _sglobalpass
if(_debugmode):
print("List item actived called")
itnum=self.m_listCtrlFtp.GetFirstSelected( )
if(itnum==-1):
return
if(_mbcon):
return
if(itnum==0):
#返回上级目录
#
self.OnPopupRoot(event)
return
else:
#注意 有可能是检索得到的结果
# 也就是带目录的完整结果
str1=self.m_listCtrlFtp.GetItemText(itnum,3)
str2=self.m_listCtrlFtp.GetItemText(itnum,1)
if(_debugmode):
print(str1)
print(str2)
if(len(str2)<1):
return
if(len(str1)<=0):
return
#根据目录或者文件分别处理
if(str2=="File" or str2=="文件"):
#这是文件
if(_debugmode):
print("Found it is file or 文件")
if(str1.find("/")>=0):
#这是完整的带目录文件
if(_debugmode):
print("Found file is abs dir + file")
pass
else:
#这是普通的文件
# 需要添加当前目录
str1=_curworkdir+"/"+str1
#然后根据os 分别处理播放
# 是否支持http映射呢 这以后进行扩展吧 注意http 也有可能是tls/ssl
#///////这里先注销掉http 流播放模式
##if(self.m_checkBoxHttp.IsChecked() and _httpport!=0):
#如果是http模式
#
##else:
#按标准FTP进行播放
if(_debugmode):
print(str1)
if(True):

strurl="ftp://"

strurl+=self.m_textCtrlUsername.GetLineText(0)
strurl+=":"
#strurl+=self.m_textCtrlPassword.GetLineText(0)
strurl+=_sglobalpass
strurl+="@"
strserver=self.m_textCtrlServer.GetLineText(0)
if(strserver.find(':')>=0 and strserver.find('[')<0):
#如果是ipv6地址 并且简单的判断没有加入[]
strserver='['+strserver
strserver+=']'

#如果端口号不是21 应该加上端口号
strurl+=strserver
#加入服务器地址
#请注意 ipv6地址加入端口号,在某些移动平台的播放器,会解析失败 不过原因是那些播放器软件本身解析的问题
strurl+=":"
strurl+=self.m_textCtrlPort.GetLineText(0)
strurl+=str1
#由于windows 目录文件可能有空格 这里使用双引号
#strurl='"'+strurl
#strurl+='"'

#到这里完成了完整的ftp url 构成
if(_debugmode):
print(strurl)
#根据os的不同进行调用吧
#mpv 有py_mpv , 可以直接在python里非常简单的调用播放器
#但是由于升级很频繁 我们还是建议你调用外部播放器方式来实现
#虽然
if(os.name=='nt'):
# 这是windows 操作系统
#如果存在mpv 那么使用mpv 进行播放
#如果存在vlc 那么使用vlc 进行播放
str10=_playercmd
str10=str10.replace('$url',strurl)
if(_debugmode):
print(str10)
#os.system(str10)
os.popen(str10)
#if(_debugmode):
#print()
else:
#按Linux进行处理 posix
str10=_playercmd
str10=str10.replace('$url',strurl,1)
if(_debugmode):
print(str10)
#os.system(str10)
os.popen(str10)

else:
#这是目录 注意可能是完整目录
if(_debugmode):
print("this is dir")
#if(str1.find("/")>=0):
# #这是绝对目录
# pass
#else:
# #这是相对目录
# # 调用切换目录函数
# pass
if(self.FuncChangeDir(str1)==False):
self.FuncDisconnect( )
else:
if(self.FuncGetWorkDir( )==False):
self.FuncDisconnect( )
if(_debugmode):
print("change dir done")

#event.Skip()

def FuncGetWorkDir(self):
global _mbssl
global _ftp
global _ftps
global _curworkdir
global _debugmode
global _timersec
strres=""
_timersec=0
if(_debugmode):
print("FuncGetWorkDir funciton is called")
if(_mbssl):
try:
strres=_ftps.sendcmd("PWD")
except (socket.error, socket.gaierror):
return False
except ftplib.error_perm:
return False
except:
return False
else:
try:
strres=_ftp.sendcmd("PWD")
except (socket.error, socket.gaierror):
return False
except ftplib.error_perm:
return False
except:
return False
#分解服务器应答
#
#
#需要提取出"""内容
if(_debugmode):
print(strres)
print("Now will change work dir")
arr6=[]
arr6=strres.split('"')
if(len(arr6)<3):
return False
_curworkdir=arr6[1]
if(_debugmode):
print(_curworkdir)
print("Success get cur workdir")
self.m_staticinfo.SetLabelText(_curworkdir)
return True

def FuncDisconnect(self):
#中断连接
global _ftp
global _ftps
global _mbcon
global _mbssl
if(_mbcon):
return
if(_mbssl):
try:
_ftps.close( )
except:
pass
else:
try:
_ftp.close( )
except:
pass
_mbcon=True
self.m_listCtrlFtp.DeleteAllItems( )
self.m_staticinfo.SetLabelText("/")
self.timer1.Stop( )
self.FuncChangeButton( )






def FuncChangeDir(self,sdir):
#切换目录 同时已经刷新了列表
global _ftp
global _ftps
global _mbssl
global _curworkdir
if(len(sdir)<=0):
sdir="/"
scmd="CWD "
scmd+=sdir
bsucc=True
#切换目录
if(_mbssl):
#ssl
try:
_ftps.sendcmd(scmd)
except (socket.error, socket.gaierror):
#_ftps.close( )
return False
except ftplib.error_perm:
bsucc=False

else:
try:
_ftp.sendcmd(scmd)
except (socket.error, socket.gaierror):
#_ftp.close( )
return False
except ftplib.error_perm:
bsucc=False
#可能目录切换失败 那么切换到根目录吧
if(bsucc==False):
sdir="/"
scmd="CWD /"
if(_mbssl):
try:
_ftps.sendcmd(scmd)
except (socket.error, socket.gaierror):
#_ftps.close( )
return False
except ftplib.error_perm:
#_ftps.close( )
return False
else:
try:
_ftp.sendcmd(scmd)
except (socket.error, socket.gaierror):
#_ftps.close( )
return False
except ftplib.error_perm:
#_ftps.close( )
return False
#现在可以保证已经成功了 那么执行列表操作吧
#
if(self.FuncGetList( )==0):
return False
#_curworkdir=sdir
#self.m_staticinfo.SetLabelText(sdir)
return True
#完成操作



def OnMyRightClick(self, event):
global _chineseversion
if not hasattr(self, "popupIDRoot"):
self.popupIDRoot = wx.NewIdRef()
self.popupIDUp = wx.NewIdRef()
self.popupIDList = wx.NewIdRef()
self.popupIDPlay = wx.NewIdRef()
self.popupIDRefresh = wx.NewIdRef()
self.Bind(wx.EVT_MENU, self.OnPopupRoot, id=self.popupIDRoot)
self.Bind(wx.EVT_MENU, self.OnPopupUp, id=self.popupIDUp)
self.Bind(wx.EVT_MENU, self.OnPopupRefresh, id=self.popupIDRefresh)
self.Bind(wx.EVT_MENU, self.OnPopupPlay, id=self.popupIDPlay)
self.Bind(wx.EVT_MENU, self.OnPopupList, id=self.popupIDList)
# pop menu
menu = wx.Menu()
# add some items
if(_chineseversion):
#如果是中文版本
menu.Append(self.popupIDRoot, "根目录")
menu.Append(self.popupIDUp, "上级目录")
menu.Append(self.popupIDList, "列表")
menu.Append(self.popupIDPlay, "播放")
menu.Append(self.popupIDRefresh, "刷新")
else:
#英文版本
menu.Append(self.popupIDRoot, "Root")
menu.Append(self.popupIDUp, "Upgread")
menu.Append(self.popupIDList, "List")
menu.Append(self.popupIDPlay, "Play")
menu.Append(self.popupIDRefresh, "Refresh")

# Popup the menu. If an item is selected then its handler
# will be called before PopupMenu returns.
self.PopupMenu(menu)
menu.Destroy()

def OnMyListItemRightClick( self, event ):
#event.Skip( )
global _debugmode
if(_debugmode):
print("OnMyListItemRightClick function")
event.Skip( )

def OnPopupRoot(self, event):
#event.Skip( )
global _mbcon
global _timersec
if(_mbcon):
return
#修改定时器计数为0
_timersec=0
if(self.FuncChangeDir("/")==False):
self.FuncDisconnect( )
return
else:
if(self.FuncGetWorkDir()==False):
self.FuncDisconnect()


return

def OnPopupUp(self, event):
#event.Skip( )
global _mbcon
global _timersec
if(_mbcon):
return
_timersec=0
if(self.FuncChangeDir("..")==False):
self.FuncDisconnect( )
return



def OnPopupList(self, event):
#event.Skip( )
global _mbcon
global _timersec

i3=self.m_listCtrlFtp.GetFirstSelected( )
if(i3==-1):
return
if(_mbcon):
return

str22=self.m_listCtrlFtp.GetItemText(i3,1)
if(str22=='File' or str22=='文件'):
return
_timersec=0
self.OnMyListItemActivated(event)

def OnPopupPlay(self, event):
#event.Skip( )
global _mbcon
global _timersec

i3=self.m_listCtrlFtp.GetFirstSelected( )
if(i3==-1):
return
if(_mbcon):
return
str22=self.m_listCtrlFtp.GetItemText(i3,1)
if(str22=='Dir' or str22=='目录'):
return
_timersec=0
self.OnMyListItemActivated(event)

def OnPopupRefresh(self, event):
#event.Skip( )
global _mbcon
global _timersec
if(_mbcon):
return
_timersec=0
if(self.FuncGetList()==0):
self.FuncDisconnect( )
return

def OnMyTimerMsg(self,event):
global _mbcon
global _debugmode
global _timersec
if(_debugmode):
print("OnTimer msg")
if(_mbcon):
#连接已经中断 那么直接断开吧 是否需要使用锁定?
return
_timersec+=1
if(_timersec>=6):
#发送Noop指令给服务器吧
_timersec=0
if(self.FuncSendRawCmd("NOOP")==False):
self.FuncDisconnect( )

event.Skip( )

def FuncSendRawCmd(self,scmd):
global _mbcon
global _debugmode
global _ftp
global _ftps
global _mbssl
if(_debugmode):
print("Found timer to send noop command now")
if(len(scmd)<=0):
scmd="NOOP"
try:
if(_mbssl):
_ftps.sendcmd(scmd)
else:
_ftp.sendcmd(scmd)
except (socket.error, socket.gaierror):
return False
except ftplib.error_perm:
return True
except :
return False
return True








if __name__ == '__main__':
# When this module is run (not imported) then create the app, the
# frame, show it, and start the event loop.
app = wx.App()
frm = MyFrameMain(None)
frm.Show()
app.MainLoop()
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
(c) 2015-2023 phoenixp2p.com