diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..18274d35 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.pythonPath": "python3", + "python.formatting.provider": "yapf" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 10b5362a..6614b994 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Daniel van Flymen +Copyright (c) 2018 doudoudzj Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 31c3e0c3..00000000 --- a/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[dev-packages] - -[requires] -python_version = "3.6" - -[packages] - -flask = "==0.12.2" -requests = "==2.18.4" - diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 4d21bf73..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,109 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "45af71184b5b013f58a8601f7f87c9f0b882afe19f197ce45d6d08e46d615159" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.6.2", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "4.10.0-35-generic", - "platform_system": "Linux", - "platform_version": "#39~16.04.1-Ubuntu SMP Wed Sep 13 09:02:42 UTC 2017", - "python_full_version": "3.6.2", - "python_version": "3.6", - "sys_platform": "linux" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", - "sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5" - ], - "version": "==2017.7.27.1" - }, - "chardet": { - "hashes": [ - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", - "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" - ], - "version": "==6.7" - }, - "flask": { - "hashes": [ - "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", - "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" - ], - "version": "==0.12.2" - }, - "idna": { - "hashes": [ - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" - ], - "version": "==2.6" - }, - "itsdangerous": { - "hashes": [ - "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" - ], - "version": "==0.24" - }, - "jinja2": { - "hashes": [ - "sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054", - "sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff" - ], - "version": "==2.9.6" - }, - "markupsafe": { - "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" - ], - "version": "==1.0" - }, - "requests": { - "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" - ], - "version": "==2.18.4" - }, - "urllib3": { - "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" - ], - "version": "==1.22" - }, - "werkzeug": { - "hashes": [ - "sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d", - "sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26" - ], - "version": "==0.12.2" - } - }, - "develop": {} -} diff --git a/README.md b/README.md index e59a6d31..b8c47ee6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,4 @@ -# 用Python从零开始创建区块链 - -本文是博客:[用Python从零开始创建区块链](http://learnblockchain.cn/2017/10/27/build_blockchain_by_python/) 的源码. -翻译自[Building a Blockchain](https://medium.com/p/117428612f46) - -[博客地址](http://learnblockchain.cn/2017/10/27/build_blockchain_by_python/)| [英文README](https://github.com/xilibi2003/blockchain/blob/master/README-en.md) +# 基于 Python + Tkinter 的区块链桌面应用程序 ## 安装 @@ -56,10 +51,4 @@ $ docker run --rm -p 82:5000 blockchain $ docker run --rm -p 83:5000 blockchain ``` -## 贡献 -[深入浅出区块链](http://learnblockchain.cn/) 想做好的区块链学习博客。 -[博客地址](https://github.com/xilibi2003/learnblockchain) 欢迎大家一起参与贡献,一起推动区块链技术发展。 - - - diff --git a/README-en.md b/README_EN.md similarity index 89% rename from README-en.md rename to README_EN.md index 0f46b5b1..6fb94921 100644 --- a/README-en.md +++ b/README_EN.md @@ -1,6 +1,5 @@ -# Learn Blockchains by Building One +# A Blockchain Application based on Tkinter in Python -This is the source code for my post on [Building a Blockchain](https://medium.com/p/117428612f46). ## Installation diff --git a/app.py b/app.py new file mode 100644 index 00000000..77aac342 --- /dev/null +++ b/app.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""Application""" + +import os +from tkinter import Tk + +import lib.global_variable as gv +from components import app_view + +gv.init_global_variable() +gv.set_variable("APP_BUNDLE_ID", "org.crogram.blockchain-node") +gv.set_variable("APP_NAME", "区块链客户端GUI") +gv.set_variable("APP_PATH", os.path.dirname(__file__)) # 当前目录 +gv.set_variable("APP_VERSION", "0.1.1") + + +class App(Tk): + """Application Class""" + + def __init__(self): + + Tk.__init__(self) + + app_view.client(self) + + self.mainloop() + + +if __name__ == "__main__": + App() diff --git a/blockchain.py b/blockchain.py deleted file mode 100644 index 2e8c91e5..00000000 --- a/blockchain.py +++ /dev/null @@ -1,285 +0,0 @@ -import hashlib -import json -from time import time -from typing import Any, Dict, List, Optional -from urllib.parse import urlparse -from uuid import uuid4 - -import requests -from flask import Flask, jsonify, request - - -class Blockchain: - def __init__(self): - self.current_transactions = [] - self.chain = [] - self.nodes = set() - - # 创建创世块 - self.new_block(previous_hash='1', proof=100) - - def register_node(self, address: str) -> None: - """ - Add a new node to the list of nodes - - :param address: Address of node. Eg. 'http://192.168.0.5:5000/' - """ - - parsed_url = urlparse(address) - self.nodes.add(parsed_url.netloc) - - def valid_chain(self, chain: List[Dict[str, Any]]) -> bool: - """ - Determine if a given blockchain is valid - - :param chain: A blockchain - :return: True if valid, False if not - """ - - last_block = chain[0] - current_index = 1 - - while current_index < len(chain): - block = chain[current_index] - print(f'{last_block}') - print(f'{block}') - print("\n-----------\n") - # Check that the hash of the block is correct - if block['previous_hash'] != self.hash(last_block): - return False - - # Check that the Proof of Work is correct - if not self.valid_proof(last_block['proof'], block['proof']): - return False - - last_block = block - current_index += 1 - - return True - - def resolve_conflicts(self) -> bool: - """ - 共识算法解决冲突 - 使用网络中最长的链. - - :return: 如果链被取代返回 True, 否则为False - """ - - neighbours = self.nodes - new_chain = None - - # We're only looking for chains longer than ours - max_length = len(self.chain) - - # Grab and verify the chains from all the nodes in our network - for node in neighbours: - response = requests.get(f'http://{node}/chain') - - if response.status_code == 200: - length = response.json()['length'] - chain = response.json()['chain'] - - # Check if the length is longer and the chain is valid - if length > max_length and self.valid_chain(chain): - max_length = length - new_chain = chain - - # Replace our chain if we discovered a new, valid chain longer than ours - if new_chain: - self.chain = new_chain - return True - - return False - - def new_block(self, proof: int, previous_hash: Optional[str]) -> Dict[str, Any]: - """ - 生成新块 - - :param proof: The proof given by the Proof of Work algorithm - :param previous_hash: Hash of previous Block - :return: New Block - """ - - block = { - 'index': len(self.chain) + 1, - 'timestamp': time(), - 'transactions': self.current_transactions, - 'proof': proof, - 'previous_hash': previous_hash or self.hash(self.chain[-1]), - } - - # Reset the current list of transactions - self.current_transactions = [] - - self.chain.append(block) - return block - - def new_transaction(self, sender: str, recipient: str, amount: int) -> int: - """ - 生成新交易信息,信息将加入到下一个待挖的区块中 - - :param sender: Address of the Sender - :param recipient: Address of the Recipient - :param amount: Amount - :return: The index of the Block that will hold this transaction - """ - self.current_transactions.append({ - 'sender': sender, - 'recipient': recipient, - 'amount': amount, - }) - - return self.last_block['index'] + 1 - - @property - def last_block(self) -> Dict[str, Any]: - return self.chain[-1] - - @staticmethod - def hash(block: Dict[str, Any]) -> str: - """ - 生成块的 SHA-256 hash值 - - :param block: Block - """ - - # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes - block_string = json.dumps(block, sort_keys=True).encode() - return hashlib.sha256(block_string).hexdigest() - - def proof_of_work(self, last_proof: int) -> int: - """ - 简单的工作量证明: - - 查找一个 p' 使得 hash(pp') 以4个0开头 - - p 是上一个块的证明, p' 是当前的证明 - """ - - proof = 0 - while self.valid_proof(last_proof, proof) is False: - proof += 1 - - return proof - - @staticmethod - def valid_proof(last_proof: int, proof: int) -> bool: - """ - 验证证明: 是否hash(last_proof, proof)以4个0开头 - - :param last_proof: Previous Proof - :param proof: Current Proof - :return: True if correct, False if not. - """ - - guess = f'{last_proof}{proof}'.encode() - guess_hash = hashlib.sha256(guess).hexdigest() - return guess_hash[:4] == "0000" - - -# Instantiate the Node -app = Flask(__name__) - -# Generate a globally unique address for this node -node_identifier = str(uuid4()).replace('-', '') - -# Instantiate the Blockchain -blockchain = Blockchain() - - -@app.route('/mine', methods=['GET']) -def mine(): - # We run the proof of work algorithm to get the next proof... - last_block = blockchain.last_block - last_proof = last_block['proof'] - proof = blockchain.proof_of_work(last_proof) - - # 给工作量证明的节点提供奖励. - # 发送者为 "0" 表明是新挖出的币 - blockchain.new_transaction( - sender="0", - recipient=node_identifier, - amount=1, - ) - - # Forge the new Block by adding it to the chain - block = blockchain.new_block(proof, None) - - response = { - 'message': "New Block Forged", - 'index': block['index'], - 'transactions': block['transactions'], - 'proof': block['proof'], - 'previous_hash': block['previous_hash'], - } - return jsonify(response), 200 - - -@app.route('/transactions/new', methods=['POST']) -def new_transaction(): - values = request.get_json() - - # 检查POST数据 - required = ['sender', 'recipient', 'amount'] - if not all(k in values for k in required): - return 'Missing values', 400 - - # Create a new Transaction - index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) - - response = {'message': f'Transaction will be added to Block {index}'} - return jsonify(response), 201 - - -@app.route('/chain', methods=['GET']) -def full_chain(): - response = { - 'chain': blockchain.chain, - 'length': len(blockchain.chain), - } - return jsonify(response), 200 - - -@app.route('/nodes/register', methods=['POST']) -def register_nodes(): - values = request.get_json() - - nodes = values.get('nodes') - if nodes is None: - return "Error: Please supply a valid list of nodes", 400 - - for node in nodes: - blockchain.register_node(node) - - response = { - 'message': 'New nodes have been added', - 'total_nodes': list(blockchain.nodes), - } - return jsonify(response), 201 - - -@app.route('/nodes/resolve', methods=['GET']) -def consensus(): - replaced = blockchain.resolve_conflicts() - - if replaced: - response = { - 'message': 'Our chain was replaced', - 'new_chain': blockchain.chain - } - else: - response = { - 'message': 'Our chain is authoritative', - 'chain': blockchain.chain - } - - return jsonify(response), 200 - - -if __name__ == '__main__': - from argparse import ArgumentParser - - parser = ArgumentParser() - parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') - args = parser.parse_args() - port = args.port - - app.run(host='127.0.0.1', port=port) diff --git a/components/__init__.py b/components/__init__.py new file mode 100644 index 00000000..359fa84a --- /dev/null +++ b/components/__init__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +__all__ = [ + "app_menu", + "app_view" +] diff --git a/components/app_menu.py b/components/app_menu.py new file mode 100644 index 00000000..12b9b350 --- /dev/null +++ b/components/app_menu.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""菜单""" + +from tkinter import Menu +from window import win_about + + +class AppMenu(): + """菜单类""" + + def __init__(self, master=None): + self.root = master + self.menu_bar = None + self.menu_file = None + self.menu_edit = None + self.menu_view = None + self.menu_bookmark = None + self.menu_window = None + self.menu_help = None + + self.init_menu() + + self.window_about = None + + def init_menu(self): + + menu_bar = Menu(self.root) + self.menu_bar = menu_bar + + self.init_menu_file() + self.init_menu_edit() + self.init_menu_view() + self.init_menu_server() + self.init_menu_bookmark() + self.init_menu_window() + self.init_menu_help() + + self.root["menu"] = menu_bar + + def init_menu_file(self): + + menu_file = Menu(self.menu_bar) + menu_file.add_command(label="新建文件", accelerator="Command+Shift+N") + menu_file.add_command(label="新建文件夹", accelerator="Command+N") + menu_file.add_separator() + menu_file.add_command(label="新建窗口", accelerator="Shift+N") + menu_file.add_command(label="新建标签", accelerator="Shift+T") + menu_file.add_separator() + menu_file.add_command(label="关闭窗口", accelerator="Shift+Alt+N") + menu_file.add_command(label="关闭标签", accelerator="Shift+Alt+T") + menu_file.add_separator() + menu_file.add_command(label="打开", accelerator="Command+Q") + menu_file.add_command(label="最近文件", accelerator="Command+Q") + menu_file.add_separator() + menu_file.add_command(label="保存") + menu_file.add_command(label="另存为") + menu_file.add_separator() + menu_file.add_command(label="关闭") + menu_file.add_separator() + menu_file.add_command(label="显示正在被编辑的文件") + menu_file.add_separator() + menu_file.add_command(label="退出", command=self.app_exit) + self.menu_bar.add_cascade(label="文件", menu=menu_file) + self.menu_file = menu_file + + def init_menu_edit(self): + + menu_edit = Menu(self.menu_bar) + menu_edit.add_command(label="复制", accelerator="Command+C") + menu_edit.add_command(label="粘贴", accelerator="Command+V") + menu_edit.add_command(label="移动", accelerator="Command+M") + menu_edit.add_command(label="重命名") + menu_edit.add_separator() + menu_edit.add_command(label="查找", accelerator="Command+F") + menu_edit.add_command(label="全选", accelerator="Command+A") + menu_edit.add_command(label="取消全选", accelerator="Command+Shift+A") + menu_edit.add_separator() + menu_edit.add_command(label="复制到", accelerator="Command+Alt+C") + menu_edit.add_command(label="移动到", accelerator="Command+Alt+M") + menu_edit.add_separator() + menu_edit.add_command(label="复制文件名", accelerator="Command+Shift+C") + menu_edit.add_command(label="复制当前路径", accelerator="Command+Shift+D") + menu_edit.add_command(label="复制当前文件夹", accelerator="Command+Shift+C") + menu_edit.add_separator() + menu_edit.add_command(label="预览", accelerator="Command+Alt+P") + menu_edit.add_command(label="查看信息", accelerator="Command+Alt+I") + menu_edit.add_separator() + menu_edit.add_command(label="删除") + menu_edit.add_command(label="移到回收站") + self.menu_bar.add_cascade(label="编辑", menu=menu_edit) + self.menu_edit = menu_edit + + def init_menu_view(self): + + menu_view = Menu(self.menu_bar) + menu_view.add_checkbutton(label="工具栏") + menu_view.add_checkbutton(label="快速连接栏") + menu_view.add_checkbutton(label="本地文件") + menu_view.add_checkbutton(label="状态栏") + menu_view.add_separator() + menu_view.add_command(label="刷新", accelerator="Command+R") + menu_view.add_separator() + + menu_sort = Menu(self.menu_bar) + menu_sort.add_command(label="文件名") + menu_sort.add_command(label="文件大小") + menu_sort.add_command(label="文件类型") + menu_sort.add_command(label="修改时间") + menu_view.add_cascade(label="排序", menu=menu_sort) + + self.menu_bar.add_cascade(label="查看", menu=menu_view) + self.menu_view = menu_view + + def init_menu_server(self): + + menu_server = Menu(self.menu_bar) + menu_server.add_command(label="取消操作", accelerator="Command+N") + menu_server.add_command(label="暂停操作") + menu_server.add_command(label="重新连接", accelerator="Command+R") + menu_server.add_command(label="断开连接", accelerator="Command+D") + menu_server.add_separator() + menu_server.add_command(label="传输", accelerator="Alt+H") + menu_server.add_command(label="队列") + menu_server.add_command(label="同步") + menu_server.add_command(label="续传") + menu_server.add_command(label="续传暂停的队列") + menu_server.add_separator() + menu_server.add_command(label="搜索远程文件", accelerator="Ctrl+S") + menu_server.add_command(label="发送Raw命令") + menu_server.add_command(label="发送Shell命令") + menu_server.add_command(label="输入自定义命令", accelerator="Ctrl+C") + menu_server.add_separator() + menu_server.add_command(label="显示文件后缀名", accelerator="Ctrl+N") + menu_server.add_command(label="显示隐藏文件", accelerator="Ctrl+N") + menu_server.add_separator() + menu_server.add_command(label="连接信息") + self.menu_bar.add_cascade(label="服务器", menu=menu_server) + self.menu_server = menu_server + + def init_menu_bookmark(self): + + menu_bookmark = Menu(self.menu_bar) + menu_bookmark.add_command(label="添加书签", accelerator="Command+B") + menu_bookmark.add_command(label="添加到书签", accelerator="Command+Shift+B") + menu_bookmark.add_command(label="管理书签", accelerator="Ctrl+B") + menu_bookmark.add_command(label="导入书签", accelerator="Ctrl+I") + menu_bookmark.add_cascade(label="最近连接") + self.menu_bookmark = menu_bookmark + self.menu_bar.add_cascade(label="书签", menu=menu_bookmark) + + def init_menu_window(self): + + menu_window = Menu(self.menu_bar) + menu_window.add_command(label="最小化", accelerator="Command+H", command=self.app_mini) + menu_window.add_command(label="缩放") + + menu_window.add_separator() + menu_window.add_command(label="全屏") + menu_window.add_command(label="置顶") + menu_window.add_separator() + menu_window.add_command(label="12323456") + menu_window.add_command(label="343453455") + self.menu_bar.add_cascade(label="窗口", menu=menu_window) + self.menu_window = menu_window + + def init_menu_help(self): + + menu_help = Menu(self.menu_bar) + menu_help.add_command(label="检查更新", accelerator="Ctrl+N") + menu_help.add_command(label="显示欢迎对话框", accelerator="Ctrl+W") + menu_help.add_separator() + menu_help.add_command(label="获得帮助", accelerator="Ctrl+G") + menu_help.add_command(label="报告问题", accelerator="Ctrl+R") + menu_help.add_command(label="关于我们", accelerator="Ctrl+A", command=self.about) + self.menu_help = menu_help + self.menu_bar.add_cascade(label="帮助", menu=menu_help) + + def app_exit(self): + self.root.quit() + + def app_mini(self): + self.root.iconify() + + def about(self): + if self.window_about is not None: + # self.window_about.destroy() + print("已打开") + else: + self.window_about = win_about.About(self.root) + self.window_about.protocol(name="WM_DELETE_WINDOW", func=self.close_about) + + def close_about(self): + print("已关闭-关于") + # self.window_about.destroy() + self.window_about = None diff --git a/components/app_view.py b/components/app_view.py new file mode 100644 index 00000000..e357cd74 --- /dev/null +++ b/components/app_view.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- +"""FTP客户端GUI""" + +import os +import time +from ftplib import FTP +from tkinter import (Button, Entry, Frame, IntVar, Label, Listbox, Menu, + StringVar, Tk, filedialog) +from tkinter.ttk import Scrollbar, Treeview + +from lib.functions import set_window_center +import lib.global_variable as glv +from components import app_menu + + +class client(): + def __init__(self, master): + self.root = master + + self.root.title(glv.get_variable("APP_NAME")) + self.root.minsize(800, 600) + set_window_center(self.root, 800, 600) + self.root.resizable(False, False) + self.root.update() + + self.var_port = IntVar(value=3333) + self.var_address = StringVar(value="0.0.0.0") + self.default_timeout = IntVar(value=-999) + self.path_remote = StringVar() + self.path_local = StringVar() + self.inputFileName = "" + + self.filelist_local = None + self.filelist_remote = None + + self.toolbar_box = None # 工具按钮栏 + self.quickbar_box = None # 快速连接栏 + self.footer_box = None # 底部状态栏 + self.content_box = None # 主要内容视图容器 + self.remote_box = None # 远程服务端资源 + self.local_box = None # 本地资源 + + self.ftp_connect = FTP() + self.init_view() + + def init_view(self): + """界面""" + + app_menu.AppMenu(self.root) + self.init_frame() + self.init_toolbar() + self.init_header() + self.init_remote() + self.init_local() + self.init_footer() + + def init_frame(self): + """基本框架""" + + # 工具按钮栏 + self.toolbar_box = Frame(self.root, relief="ridge", bd=1) + self.toolbar_box.pack(fill="x", expand=None, side="top", anchor="n") + + # 快速连接栏 + self.quickbar_box = Frame(self.root, relief="ridge", bd=1) + self.quickbar_box.pack(fill="x", expand=None, side="top", anchor="n") + + # 底部状态栏 + self.footer_box = Frame(self.root, relief="ridge", bd=1) + self.footer_box.pack(fill="x", expand=None, side="bottom", anchor="s") + + # 主要内容视图容器 + self.content_box = Frame(self.root, relief="ridge", bd=0) + self.content_box.pack(fill="both", expand=True) + + # 远程服务端文件列表 + self.remote_box = Frame(self.content_box, relief="ridge", bd=0) + self.remote_box.pack(fill="both", expand=True, side="right") + + # 本地文件列表 + self.local_box = Frame(self.content_box, relief="ridge", bd=0) + self.local_box.pack(fill="both", expand=True, side="left") + + def init_header(self): + """头部栏""" + + Label(self.quickbar_box, text="主机:").pack(side="left") + self.entry_ip = Entry(self.quickbar_box, textvariable=self.var_address) + self.entry_ip["width"] = 15 + self.entry_ip.pack(side="left") + + Label(self.quickbar_box, text="账号:").pack(side="left") + self.entry_user = Entry(self.quickbar_box, width=15) + self.entry_user.pack(side="left") + + Label(self.quickbar_box, text="密码:").pack(side="left") + self.entry_passwd = Entry(self.quickbar_box, show="*", width=15) + self.entry_passwd.pack(side="left") + + Label(self.quickbar_box, text="端口:").pack(side="left") + self.entry_port = Entry(self.quickbar_box, textvariable=self.var_port) + self.entry_port["width"] = 5 + self.entry_port.pack(side="left") + + button_connect = Button(self.quickbar_box, text="快速连接") + button_connect["command"] = self.ftp_login + button_connect["width"] = 10 + button_connect.pack(side="left") + + def init_toolbar(self): + """工具栏""" + + button_connect = Button(self.toolbar_box, text="快速连接") + button_connect["command"] = self.ftp_login + button_connect.pack(side="left") + + button_disconnect = Button(self.toolbar_box, text="断开") + button_disconnect["command"] = self.ftp_quit + button_disconnect.pack(side="left") + + button_reflash = Button(self.toolbar_box, text="刷新") + button_reflash["command"] = self.flash_remote + button_reflash.pack(side="left") + + def init_remote(self): + """远程文件列表""" + + btn_bar = Frame(self.remote_box, relief="ridge", bd=1) + btn_bar.pack(fill="x", expand=False, side="top") + Label(btn_bar, text="远程:").pack(fill="x", expand=None, side="left") + path = Entry(btn_bar, textvariable=self.path_remote) + path.pack(fill="x", expand=True, side="left") + + Button( + btn_bar, text="打开", command=self.select_path_remote).pack( + fill="x", expand=None, side="left") + Button(btn_bar, text="重连", command=self.ftp_login).pack(side="left") + Button(btn_bar, text="断开", command=self.ftp_quit).pack(side="left") + Button( + btn_bar, text="刷新", command=self.flash_remote).pack( + fill="x", expand=None, side="right") + + file_list = Frame(self.remote_box, relief="ridge", bd=0) + file_list.pack(fill="both", expand=True, side="top") + # Listbox + # self.filelist_remote = Listbox(self.remote_box) + # self.filelist_remote.bind("", self.click_db) + # self.filelist_remote.pack(fill="both", expand=True, side="top") + + tree = Treeview(file_list, show="headings") + # 列索引ID + tree["columns"] = ("pra", "id", "user", "group", "size", "date", + "name") + # 表头设置 + tree.heading("pra", text="权限") + tree.heading("id", text="ID") + tree.heading("user", text="用户") + tree.heading("group", text="组") + tree.heading("size", text="大小") + tree.heading("date", text="最后修改日期") + tree.heading("name", text="文件名") + + tree.column("pra", width="100") + tree.column("id", width="50", anchor="center") + tree.column("user", width="50") + tree.column("group", width="50") + tree.column("size", width="50") + tree.column("date", width="50") + tree.column("name", width="50") + + self.filelist_remote = tree + + vbar = Scrollbar(file_list, orient="vertical", command=tree.yview) + tree.configure(yscrollcommand=vbar.set) + tree.grid(row=0, column=0, sticky="nswe") + vbar.grid(row=0, column=1, sticky="ns") + + def init_local(self): + """本地文件列表""" + + btns = Frame(self.local_box, relief="ridge", bd=1) + btns.pack(fill="x", expand=False, side="top") + Label(btns, text="本地:").pack(fill="x", expand=None, side="left") + Entry( + btns, textvariable=self.path_local).pack( + fill="x", expand=True, side="left") + Button( + btns, text="打开", command=self.select_path_local).pack( + fill="x", expand=None, side="left") + Button( + btns, text="刷新", command=self.flash_local).pack( + fill="x", expand=None, side="right") + + self.filelist_local = Listbox(self.local_box) + self.filelist_local.bind("", self.click_db) + self.filelist_local.pack(fill="both", expand=True, side="top") + + def init_footer(self): + """底部状态栏""" + + Label(self.footer_box, text="欢迎使用").pack(side="left") + Label(self.footer_box, text="欢迎使用").pack(fill="x", side="left") + ct = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + self.clock = Label(self.footer_box, text=ct) + self.clock.pack(side="right") + self.clock.after(1000, self.trickit) + self.trickit() + + def trickit(self): + ct = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + self.clock["text"] = ct + self.clock.update() + self.clock.after(1000, self.trickit) + + def ftp_login(self): + self.ftp_connect.connect(self.var_address.get().strip(), + int(self.entry_port.get().strip())) + self.ftp_connect.login(self.entry_user.get(), self.entry_passwd.get()) + self.flash_remote() # 加载列表 + + def ftp_quit(self): + if self.ftp_connect is not None: + self.ftp_connect.quit() + + def ftp_close(self): + if self.ftp_connect is not None: + self.ftp_connect.close() + + def flash_remote(self): + file_list = [] + self.ftp_connect.dir("", file_list.append) + for x in file_list: + i = x.split() # 或者filename = x.split("\t")[列的起始值:列的终止值] + self.filelist_remote.insert( + "", + "end", + text="", + values=(i[0], i[1], i[2], i[3], i[4], i[5:8], i[-1])) + + def flash_local(self): + filelist = os.listdir(self.path_local.get()) + print(filelist) + if self.filelist_local.size() > 0: + self.filelist_local.delete(0, "end") + + for i in range(len(filelist)): + self.filelist_local.insert("end", filelist[i]) + + # file_list = [] + # self.ftp_connect.dir("", file_list.append) + # for x in file_list: + # i = x.split() # 或者filename = x.split("\t")[列的起始值:列的终止值] + # self.filelist_local.insert( + # "", + # "end", + # text="", + # values=(i[0], i[1], i[2], i[3], i[4], i[5:8], i[-1])) + + def click_db(self, event): + self.download() + + def download(self): + inputFileName = self.filelist_remote.get( + self.filelist_remote.curselection()) + file_handler = open(self.path_remote.get() + "/" + inputFileName, + "wb").write + self.ftp_connect.retrbinary( + "RETR %s" % os.path.basename(inputFileName), file_handler, 1024) + + def select_path_local(self): + path = filedialog.askdirectory() + if os.path.isdir(path): + self.path_local.set(path) + + def select_path_remote(self): + path = filedialog.askdirectory() + if os.path.isdir(path): + self.path_remote.set(path) + + def quit(self): + self.root.quit()