├── .gitignore ├── FileTransfer.pro ├── LICENSE ├── README.md ├── demonstrate └── FileTransfer.gif ├── image ├── winIcon.ico └── winIcon.png ├── qml.qrc ├── qml ├── DiscoverPage.qml ├── FileProgress.qml ├── FileView.qml ├── FlatInput.qml ├── GlowRectangle.qml ├── MyButton.qml ├── MyToolTip.qml ├── TransferPage.qml └── main.qml └── src ├── connectionmanager.cpp ├── connectionmanager.h ├── discoverconnection.cpp ├── discoverconnection.h ├── fileapi.cpp ├── fileapi.h ├── fileblock.cpp ├── fileblock.h ├── filemanager.cpp ├── filemanager.h ├── filetransfer.cpp ├── filetransfer.h ├── framelesswindow.cpp ├── framelesswindow.h ├── main.cpp ├── scanneritem.cpp ├── scanneritem.h ├── transfersocket.cpp └── transfersocket.h /.gitignore: -------------------------------------------------------------------------------- 1 | release/ 2 | debug/ 3 | *.user 4 | *.Debug 5 | *.Release 6 | Makefile 7 | *.stash 8 | *.rc 9 | *.exe 10 | *.qmlc -------------------------------------------------------------------------------- /FileTransfer.pro: -------------------------------------------------------------------------------- 1 | QT += quick widgets network concurrent 2 | 3 | CONFIG += c++11 4 | 5 | win32{ 6 | RC_ICONS += image/winIcon.ico 7 | } 8 | 9 | # The following define makes your compiler emit warnings if you use 10 | # any Qt feature that has been marked deprecated (the exact warnings 11 | # depend on your compiler). Refer to the documentation for the 12 | # deprecated API to know how to port your code away from it. 13 | DEFINES += QT_DEPRECATED_WARNINGS 14 | 15 | # You can also make your code fail to compile if it uses deprecated APIs. 16 | # In order to do so, uncomment the following line. 17 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 18 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 19 | 20 | HEADERS += \ 21 | src/connectionmanager.h \ 22 | src/fileapi.h \ 23 | src/fileblock.h \ 24 | src/filetransfer.h \ 25 | src/filemanager.h \ 26 | src/discoverconnection.h \ 27 | src/framelesswindow.h \ 28 | src/transfersocket.h \ 29 | src/scanneritem.h 30 | 31 | SOURCES += \ 32 | src/connectionmanager.cpp \ 33 | src/fileapi.cpp \ 34 | src/fileblock.cpp \ 35 | src/filetransfer.cpp \ 36 | src/framelesswindow.cpp \ 37 | src/main.cpp \ 38 | src/filemanager.cpp \ 39 | src/discoverconnection.cpp \ 40 | src/transfersocket.cpp \ 41 | src/scanneritem.cpp 42 | 43 | RESOURCES += qml.qrc 44 | 45 | # Default rules for deployment. 46 | qnx: target.path = /tmp/$${TARGET}/bin 47 | else: unix:!android: target.path = /opt/$${TARGET}/bin 48 | !isEmpty(target.path): INSTALLS += target 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 mengps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FileTransfer 2 | 3 | 用于传输文件的工具 4 | 5 | 一般为局域网环境下使用(热点) 6 | 7 | ------ 8 | 9 | ![Image text](demonstrate/FileTransfer.gif) 10 | 11 | 图为本地测试,局域网亦可用 12 | 13 | `说明` 扫描使用UDP 14 | 15 | `说明` 传输使用TCP 16 | 17 | `说明` 每一个文件使用单独的线程传输 18 | 19 | ------ 20 | 21 | ### 许可证 22 | 23 | 使用 `MIT LICENSE` 24 | 25 | ------ 26 | 27 | ### 开发环境 28 | 29 | 使用Qt/Qml开发 30 | 31 | Windows 10,Qt 5.13.0 32 | 33 | 基本未使用高版本特性,保证`Qt Version >= 5.0`即可,建议是`Qt 5.5 ~ 5.7` 34 | 35 | ------ 36 | 37 | `注意` 该软件仅用于学习,而不适合作为实际软件使用 -------------------------------------------------------------------------------- /demonstrate/FileTransfer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/FileTransfer/bd50fd0da90db60696ee58f54ca7ee4b7f7989a2/demonstrate/FileTransfer.gif -------------------------------------------------------------------------------- /image/winIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/FileTransfer/bd50fd0da90db60696ee58f54ca7ee4b7f7989a2/image/winIcon.ico -------------------------------------------------------------------------------- /image/winIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mengps/FileTransfer/bd50fd0da90db60696ee58f54ca7ee4b7f7989a2/image/winIcon.png -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | qml/FileView.qml 4 | qml/main.qml 5 | qml/MyToolTip.qml 6 | qml/MyButton.qml 7 | qml/GlowRectangle.qml 8 | qml/FileProgress.qml 9 | qml/TransferPage.qml 10 | qml/DiscoverPage.qml 11 | qml/FlatInput.qml 12 | 13 | 14 | -------------------------------------------------------------------------------- /qml/DiscoverPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import an.item 1.0 4 | 5 | Item { 6 | id: root 7 | clip: true 8 | property bool connected: false; 9 | property string connectName: ""; 10 | 11 | ScannerItem { 12 | id: scanner 13 | clip: true 14 | anchors.fill: parent 15 | } 16 | 17 | GlowRectangle { 18 | id: nameLabel 19 | radius: 5 20 | height: 50 21 | clip: true 22 | color: "#D7C2F9" 23 | glowColor: color 24 | anchors.left: parent.left 25 | anchors.leftMargin: 70 26 | anchors.right: parent.right 27 | anchors.rightMargin: 70 28 | anchors.top: parent.top 29 | anchors.topMargin: -radius 30 | 31 | Row { 32 | width: 270 33 | height: 28 34 | anchors.centerIn: parent 35 | spacing: 20 36 | 37 | Text { 38 | id: nameText 39 | width: 20 40 | height: 28 41 | verticalAlignment: Text.AlignVCenter 42 | text: qsTr("昵称:") 43 | } 44 | 45 | FlatInput { 46 | id: nameInput 47 | width: 100 48 | height: 24 49 | selectByMouse: true 50 | text: qsTr("未命名") 51 | placeholderText: qsTr("取个名字吧~") 52 | onTextChanged: discoverCon.name = text; 53 | } 54 | 55 | Text { 56 | id: stateLabel 57 | width: 140 58 | height: 28 59 | verticalAlignment: Text.AlignVCenter 60 | text: "状态:" + "" + (root.connected ? "已连接到" + root.connectName : "未连接") + "" 61 | } 62 | } 63 | } 64 | 65 | MyButton { 66 | text: "扫描" 67 | widthMargin: 12 68 | heightMargin: 8 69 | anchors.centerIn: parent 70 | onClicked: { 71 | scanner.start(); 72 | accessPoints.clear(); 73 | discoverCon.discover(); 74 | } 75 | } 76 | 77 | Connections { 78 | target: discoverCon 79 | onNewAccessPoint: { 80 | accessPoints.append({"name": name}); 81 | } 82 | onNewConnection: { 83 | scanner.stop(); 84 | root.connected = true; 85 | root.connectName = name; 86 | } 87 | } 88 | 89 | Rectangle { 90 | id: apBackground 91 | clip: true 92 | radius: 5 93 | color: "#00FFFFFF" 94 | width: 150 95 | height: 300 96 | anchors.top: nameLabel.bottom 97 | anchors.topMargin: 50 98 | anchors.right: parent.right 99 | anchors.rightMargin: 50 100 | 101 | Text { 102 | id: apLabel 103 | anchors.top: parent.top 104 | anchors.topMargin: 10 105 | anchors.horizontalCenter: parent.horizontalCenter 106 | text: qsTr("扫描到:") 107 | color: "red" 108 | font.bold: true 109 | font.pointSize: 11 110 | } 111 | 112 | ListView { 113 | id: listView 114 | clip: true 115 | anchors.top: apLabel.bottom 116 | anchors.topMargin: 10 117 | anchors.bottom: parent.bottom 118 | anchors.left: parent.left 119 | anchors.leftMargin: 5 120 | anchors.right: parent.right 121 | spacing: 4 122 | ScrollBar.vertical: ScrollBar { 123 | policy: ScrollBar.AsNeeded 124 | } 125 | displaced: Transition { 126 | NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad } 127 | } 128 | model: ListModel { id: accessPoints } 129 | delegate: Component { 130 | Rectangle { 131 | width: listView.width - 20 132 | height: 32 133 | radius: 2 134 | border.color: "#777" 135 | color: hovered ? "#559EF2FA" : "#55556677" 136 | property bool hovered: false 137 | 138 | MouseArea { 139 | anchors.fill: parent 140 | hoverEnabled: true 141 | onEntered: parent.hovered = true; 142 | onExited: parent.hovered = false; 143 | onClicked: { 144 | scanner.stop(); 145 | discoverCon.connectToName(name); 146 | fileTransfer.setAccessPoint(name); 147 | root.connected = true; 148 | root.connectName = name; 149 | accessPoints.clear(); 150 | } 151 | } 152 | 153 | Text { 154 | anchors.centerIn: parent 155 | text: qsTr(name) 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /qml/FileProgress.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import QtQml.Models 2.12 4 | import an.file 1.0 5 | 6 | Item { 7 | id: root 8 | 9 | property alias model: delegateModel.model 10 | 11 | Component { 12 | id: delegate 13 | 14 | Rectangle { 15 | id: wrapper 16 | width: listView.width - 12 17 | height: 64 18 | radius: 2 19 | clip: true 20 | border.color: "black" 21 | property bool complete: false; 22 | property int itemIndex: DelegateModel.itemsIndex; 23 | 24 | MouseArea { 25 | hoverEnabled: true 26 | acceptedButtons: Qt.LeftButton | Qt.RightButton 27 | anchors.fill: parent 28 | onClicked: { 29 | //当右键点击并且已经传输完成时才可删除 30 | if (mouse.button === Qt.RightButton && wrapper.complete) 31 | delegateModel.items.remove(wrapper.itemIndex); 32 | } 33 | } 34 | 35 | Text { 36 | id: number 37 | color: "red" 38 | text: "[" + (wrapper.itemIndex + 1) + "]" 39 | anchors.left: parent.left 40 | anchors.leftMargin: 5 41 | anchors.top: parent.top 42 | anchors.topMargin: 5 43 | } 44 | 45 | Text { 46 | id: name 47 | clip: true 48 | height: 16 49 | width: parent.width - 8 50 | anchors.top: number.bottom 51 | anchors.topMargin: 2 52 | anchors.horizontalCenter: parent.horizontalCenter 53 | horizontalAlignment: contentWidth < width ? Text.AlignHCenter : Text.AlignLeft; 54 | elide: Text.ElideLeft 55 | } 56 | 57 | Rectangle { 58 | id: progressBar 59 | radius: 4 60 | clip: true 61 | border.color: "gray" 62 | anchors.top: name.bottom 63 | anchors.horizontalCenter: parent.horizontalCenter 64 | height: 12 65 | width: parent.width - 8 66 | property real oldRatio: 0; 67 | property real ratio: offset / fileSize; 68 | onRatioChanged: { 69 | name.text = fileName + " (" + fileApi.convertByte((ratio - oldRatio) * fileSize) 70 | + "/s)" 71 | animation.to = progressBar.ratio * progressBar.width; 72 | animation.restart(); 73 | if (offset == fileSize) { 74 | wrapper.complete = true; 75 | progressRect.width = Qt.binding(function(){ return progressBar.width }); 76 | progressText.text += " [完成]"; 77 | } 78 | oldRatio = ratio; 79 | } 80 | 81 | NumberAnimation { 82 | id: animation 83 | running: false 84 | target: progressRect 85 | duration: 1000 86 | property: "width" 87 | easing.type: Easing.Linear 88 | } 89 | 90 | Rectangle { 91 | id: progressRect 92 | radius: 4 93 | height: parent.height 94 | width: 0 95 | color: "#C4D6FA" 96 | } 97 | } 98 | 99 | Text { 100 | id: progressText 101 | clip: true 102 | height: 16 103 | width: parent.width - 8 104 | anchors.top: progressBar.bottom 105 | anchors.horizontalCenter: parent.horizontalCenter 106 | horizontalAlignment: contentWidth < width ? Text.AlignHCenter : Text.AlignLeft; 107 | elide: Text.ElideRight 108 | text: fileApi.convertByte(offset) + " / " + fileApi.convertByte(fileSize) 109 | } 110 | } 111 | } 112 | 113 | ListView { 114 | id: listView 115 | clip: true 116 | focus: true 117 | anchors.fill: parent 118 | anchors.topMargin: 8 119 | anchors.bottomMargin: 14 120 | anchors.leftMargin: 8 121 | anchors.rightMargin: 2 122 | spacing: 10 123 | displaced: Transition { 124 | NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad } 125 | } 126 | model: DelegateModel { 127 | id: delegateModel 128 | delegate: delegate 129 | } 130 | ScrollBar.vertical: ScrollBar { 131 | width: 10 132 | policy: ScrollBar.AsNeeded 133 | } 134 | onModelChanged: { 135 | positionViewAtEnd(); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /qml/FileView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import QtQml.Models 2.1 4 | 5 | Item { 6 | id: root 7 | 8 | property alias fileUrls: myModel 9 | 10 | function addFile(fileName) { 11 | myModel.append({ fileName : fileName }); 12 | } 13 | 14 | function cleanup() { 15 | myModel.clear(); 16 | } 17 | 18 | Component { 19 | id: delegate 20 | 21 | Rectangle { 22 | width: 60 23 | height: 60 24 | radius: 2 25 | clip: true 26 | color: hovered ? "#AAA" : "transparent"; 27 | property bool hovered: false; 28 | 29 | Image { 30 | id: image 31 | width: 32 32 | height: 32 33 | anchors.top: parent.top 34 | anchors.topMargin: 2 35 | anchors.horizontalCenter: parent.horizontalCenter 36 | source: fileApi.fileToIcon(fileName) 37 | } 38 | 39 | Text { 40 | id: name 41 | width: parent.width - 4 42 | anchors.top:image.bottom 43 | anchors.topMargin: 8 44 | anchors.bottom: parent.bottom 45 | anchors.horizontalCenter: parent.horizontalCenter 46 | text: fileApi.fileName(fileName) 47 | clip: true 48 | horizontalAlignment: contentWidth < width ? Text.AlignHCenter : Text.AlignLeft; 49 | elide: Text.ElideRight 50 | } 51 | 52 | MyToolTip { 53 | visible: parent.hovered 54 | text: name.text 55 | } 56 | 57 | MouseArea { 58 | anchors.fill: parent 59 | hoverEnabled: true 60 | onEntered: parent.hovered = true; 61 | onExited: parent.hovered = false; 62 | onClicked: myModel.remove(index); 63 | } 64 | } 65 | } 66 | 67 | GridView { 68 | id: gridView 69 | clip: true 70 | focus: true 71 | cellWidth: 65 72 | cellHeight: 65 73 | anchors.fill: parent 74 | anchors.margins: 8 75 | displaced: Transition { 76 | NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad } 77 | } 78 | model: ListModel { 79 | id: myModel 80 | } 81 | delegate: delegate 82 | ScrollBar.vertical: ScrollBar { 83 | policy: ScrollBar.AsNeeded 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /qml/FlatInput.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 as Con212 3 | 4 | Item { 5 | id: root 6 | clip: true 7 | 8 | property alias color: background.color 9 | property alias horizontalAlignment: field.horizontalAlignment 10 | property alias selectionColor: field.selectionColor 11 | property alias font: field.font 12 | property alias selectByMouse: field.selectByMouse 13 | property alias hoverEnabled: field.hoverEnabled 14 | property alias text: field.text 15 | property alias placeholderText: field.placeholderText 16 | property alias validator: field.validator 17 | property alias passwordCharacter: field.passwordCharacter 18 | property alias passwordMaskDelay: field.passwordMaskDelay 19 | property alias echoMode: field.echoMode 20 | property alias leftPadding: field.leftPadding 21 | property alias rightPadding: field.rightPadding 22 | 23 | Con212.TextField { 24 | id: field 25 | anchors.fill: parent 26 | selectionColor: "#09A3DC" 27 | background: GlowRectangle { 28 | id: background 29 | width: root.width 30 | height: 1 31 | color: "#09A3DC" 32 | glowColor: "#09A3DC" 33 | anchors.top: parent.bottom 34 | anchors.topMargin: -2 35 | radius: 0 36 | glowRadius: 0.5 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /qml/GlowRectangle.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtGraphicalEffects 1.12 3 | 4 | Item { 5 | id: root 6 | 7 | property alias color: backRect.color; 8 | property alias radius: backRect.radius; 9 | property alias gradient: backRect.gradient; 10 | property alias spread: backEffect.spread; 11 | property alias glowColor: backEffect.color; 12 | property alias glowRadius: backEffect.glowRadius; 13 | 14 | RectangularGlow { 15 | id: backEffect 16 | antialiasing: true 17 | anchors.fill: backRect 18 | glowRadius: 1 19 | spread: 0.2 20 | cornerRadius: backRect.radius + glowRadius 21 | } 22 | 23 | Rectangle { 24 | id: backRect 25 | antialiasing: true 26 | anchors.fill: parent 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /qml/MyButton.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | 3 | GlowRectangle { 4 | id: root 5 | width: text.width + widthMargin * 2 6 | height: text.height + heightMargin * 2 7 | radius: 6 8 | glowRadius: 6 9 | color: hovered ? hoverColor : Qt.lighter(hoverColor, 1.18); 10 | 11 | property alias text: text.text; 12 | property alias fontSize: text.font.pointSize; 13 | property bool hovered: false; 14 | property int widthMargin: 10; 15 | property int heightMargin: 4; 16 | property color textColor: text.color; 17 | property color hoverColor: "#A0A0A0"; 18 | 19 | signal clicked(); 20 | signal pressed(); 21 | signal released(); 22 | signal entered(); 23 | signal exited(); 24 | 25 | Text { 26 | id: text 27 | font.family: "微软雅黑" 28 | color: "#333" 29 | x: widthMargin 30 | y: heightMargin 31 | } 32 | 33 | MouseArea { 34 | id: mouseArea 35 | anchors.fill: parent 36 | hoverEnabled: true 37 | onPressed: { 38 | root.pressed(); 39 | text.x += 1; 40 | text.y += 1; 41 | } 42 | onReleased: { 43 | root.released(); 44 | root.clicked(); 45 | text.x -= 1; 46 | text.y -= 1; 47 | } 48 | onEntered: root.hovered = true; 49 | onExited: root.hovered = false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /qml/MyToolTip.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | 4 | ToolTip { 5 | id: root 6 | font.pointSize: 9 7 | font.family: "微软雅黑" 8 | opacity: 0 9 | background: Rectangle { 10 | radius: 4 11 | color: "#AAFFFFFF" 12 | border.color: "#888" 13 | border.width: 1 14 | } 15 | 16 | NumberAnimation { 17 | id: animation 18 | target: root 19 | running: false 20 | property: "opacity" 21 | from: 0 22 | to: 1 23 | duration: 700 24 | easing.type: Easing.InOutQuad 25 | } 26 | 27 | onVisibleChanged: if (visible) animation.restart(); 28 | } 29 | -------------------------------------------------------------------------------- /qml/TransferPage.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Dialogs 1.3 3 | 4 | Item { 5 | 6 | Column { 7 | height: 400 8 | anchors.left: parent.left 9 | anchors.leftMargin: 50 10 | anchors.right: separator1.left 11 | anchors.rightMargin: 50 12 | anchors.verticalCenter: parent.verticalCenter 13 | spacing: 20 14 | clip: true 15 | 16 | Item { 17 | width: parent.width 18 | height: 300 19 | 20 | FileDialog { 21 | id: fileDialog 22 | title: "选择文件" 23 | folder: shortcuts.desktop 24 | nameFilters: ["All files (*)"] 25 | selectMultiple: true 26 | onAccepted: { 27 | for(var i = 0; i < fileUrls.length; i++) { 28 | fileView.addFile(fileUrls[i]); 29 | } 30 | } 31 | } 32 | 33 | Text { 34 | id: sendText 35 | anchors.verticalCenter: openFile.verticalCenter 36 | anchors.left: parent.left 37 | text: qsTr("放入需要发送的文件(可拖入)") 38 | } 39 | 40 | MyButton { 41 | id: openFile 42 | text: "选择文件" 43 | hoverColor: "#F5D2B5" 44 | anchors.left: sendText.right 45 | anchors.leftMargin: 8 46 | onClicked: fileDialog.open(); 47 | } 48 | 49 | Rectangle { 50 | width: parent.width 51 | height: 250 52 | anchors.top: sendText.bottom 53 | anchors.topMargin: 15 54 | anchors.left: parent.left 55 | border.color: "black" 56 | radius: 5 57 | 58 | DropArea { 59 | anchors.fill: parent; 60 | onDropped: { 61 | if(drop.hasUrls) { 62 | for(var i = 0; i < drop.urls.length; i++) { 63 | console.log(drop.urls[i]); 64 | fileView.addFile(drop.urls[i]); 65 | } 66 | } 67 | } 68 | } 69 | 70 | FileView { 71 | id: fileView 72 | anchors.fill: parent 73 | } 74 | } 75 | } 76 | 77 | Item { 78 | id: name 79 | width: parent.width 80 | height: 40 81 | 82 | Row { 83 | anchors.centerIn: parent 84 | width: deleteButton.width + deleteButton.width + 20 85 | height: deleteButton.height 86 | spacing: 20 87 | 88 | MyButton { 89 | id: deleteButton 90 | hoverColor: "#F5D2B5" 91 | text: "全部清空" 92 | onClicked: fileView.cleanup(); 93 | } 94 | 95 | MyButton { 96 | id: sendButton 97 | hoverColor: "#F5D2B5" 98 | text: "发送" 99 | onClicked: { 100 | for (var i = 0; i < fileView.fileUrls.count; i++) 101 | fileTransfer.sendFile(fileView.fileUrls.get(i).fileName); 102 | fileView.cleanup(); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | 110 | Rectangle { 111 | id: separator1 112 | width: 5 113 | height: parent.height 114 | x: 380 115 | color: "#DDD" 116 | property real minX: 200; 117 | property real maxX: root.width - 100; 118 | 119 | MouseArea { 120 | anchors.fill: parent 121 | hoverEnabled: true 122 | property point startPoint: Qt.point(0, 0); 123 | property point fixedPont: Qt.point(0, 0); 124 | 125 | onEntered: cursorShape = Qt.SizeHorCursor; 126 | onExited: cursorShape = Qt.ArrowCursor; 127 | onPressed: startPoint = Qt.point(mouseX, mouseY); 128 | onPositionChanged: { 129 | if(pressed) { 130 | var offsetX = mouse.x - startPoint.x; 131 | if ((separator1.x + offsetX) >= separator1.minX && (separator1.x + offsetX) <= separator1.maxX) 132 | separator1.x += offsetX; 133 | } 134 | } 135 | } 136 | } 137 | 138 | GlowRectangle { 139 | id: sendFileLabel 140 | z: -1 141 | radius: 5 142 | height: 28 143 | color: "#D7C2F9" 144 | glowColor: color 145 | anchors.left: separator2.left 146 | anchors.leftMargin: 20 147 | anchors.right: parent.right 148 | anchors.rightMargin: 20 149 | anchors.top: parent.top 150 | anchors.topMargin: -radius 151 | 152 | Text { 153 | anchors.centerIn: parent 154 | text: qsTr("发送文件") 155 | } 156 | } 157 | 158 | FileProgress { 159 | id: sendFileProgress 160 | model: fileManager.writeFiles 161 | anchors.left: separator2.left 162 | anchors.right: parent.right 163 | anchors.top: sendFileLabel.bottom 164 | anchors.topMargin: 2 165 | anchors.bottom: separator2.top 166 | } 167 | 168 | Rectangle { 169 | id: separator2 170 | height: 5 171 | anchors.left: separator1.right 172 | anchors.right: parent.right 173 | y: 240 174 | color: "#DDD" 175 | property real minY: 150; 176 | property real maxY: root.height - 100; 177 | 178 | MouseArea { 179 | anchors.fill: parent 180 | hoverEnabled: true 181 | property point startPoint: Qt.point(0, 0); 182 | property point fixedPont: Qt.point(0, 0); 183 | 184 | onEntered: cursorShape = Qt.SizeVerCursor; 185 | onExited: cursorShape = Qt.ArrowCursor; 186 | onPressed: startPoint = Qt.point(mouseX, mouseY); 187 | onPositionChanged: { 188 | if(pressed) { 189 | var offsetY = mouse.y - startPoint.y; 190 | if ((separator2.y + offsetY) >= separator2.minY && (separator2.y + offsetY) <= separator2.maxY) 191 | separator2.y += offsetY; 192 | } 193 | } 194 | } 195 | } 196 | 197 | GlowRectangle { 198 | id: receiveFileLabel 199 | z: -1 200 | radius: 5 201 | height: 28 202 | color: "#D7C2F9" 203 | glowColor: color 204 | anchors.left: separator2.left 205 | anchors.leftMargin: 20 206 | anchors.right: parent.right 207 | anchors.rightMargin: 20 208 | anchors.top: separator2.bottom 209 | anchors.topMargin: -radius 210 | 211 | Text { 212 | anchors.centerIn: parent 213 | text: qsTr("接收文件") 214 | } 215 | } 216 | 217 | FileProgress { 218 | id: receiveFileProgress 219 | model: fileManager.readFiles 220 | anchors.left: separator2.left 221 | anchors.right: parent.right 222 | anchors.top: receiveFileLabel.bottom 223 | anchors.topMargin: 2 224 | anchors.bottom: parent.bottom 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import QtQuick.Window 2.12 4 | import an.window 1.0 5 | 6 | FramelessWindow { 7 | id: root 8 | visible: true 9 | width: 640 10 | height: 480 11 | minimumWidth: 640 12 | minimumHeight: 480 13 | title: qsTr("File Transfer") 14 | color: "#D4E6FF" 15 | 16 | GlowRectangle { 17 | id: windowTitle 18 | radius: 5 19 | z: 5 20 | height: 40 21 | width: parent.width 22 | color: "#F4D1B4" 23 | glowColor: color 24 | anchors.horizontalCenter: parent.horizontalCenter 25 | anchors.top: parent.top 26 | anchors.topMargin: -radius 27 | 28 | Text { 29 | anchors.horizontalCenter: parent.horizontalCenter 30 | anchors.bottom: parent.bottom 31 | anchors.bottomMargin: 10 32 | text: qsTr("文件传输") 33 | } 34 | 35 | MyButton { 36 | heightMargin: 8 37 | anchors.right: parent.right 38 | anchors.top: parent.top 39 | text: "X" 40 | onClicked: root.close(); 41 | } 42 | } 43 | 44 | Container { 45 | id: tabBar 46 | width: 50 47 | anchors.top: windowTitle.bottom 48 | anchors.bottom: parent.bottom 49 | 50 | contentItem: ListView { 51 | interactive: false 52 | model: tabBar.contentModel 53 | snapMode: ListView.SnapOneItem 54 | orientation: ListView.Vertical 55 | } 56 | 57 | Rectangle { 58 | width: tabBar.width 59 | height: tabBar.height / 2 60 | color: { 61 | if (pressed) return "#88AAAAAA" 62 | else if (tabBar.currentIndex == 0) 63 | return "#88333333"; 64 | else return "transparent"; 65 | } 66 | property bool pressed: false; 67 | 68 | Text { 69 | anchors.centerIn: parent 70 | font.pointSize: 10 71 | text: "扫\n描" 72 | } 73 | 74 | MouseArea { 75 | anchors.fill: parent 76 | onPressed: parent.pressed = true; 77 | onReleased: parent.pressed = false; 78 | onClicked: tabBar.setCurrentIndex(0); 79 | } 80 | } 81 | 82 | Rectangle { 83 | width: tabBar.width 84 | height: tabBar.height / 2 85 | color: { 86 | if (pressed) return "#88AAAAAA" 87 | else if (tabBar.currentIndex == 1) 88 | return "#88333333"; 89 | else return "transparent"; 90 | } 91 | property bool pressed: false; 92 | 93 | Text { 94 | anchors.centerIn: parent 95 | font.pointSize: 10 96 | text: "发\n送" 97 | } 98 | 99 | MouseArea { 100 | anchors.fill: parent 101 | onPressed: parent.pressed = true; 102 | onReleased: parent.pressed = false; 103 | onClicked: tabBar.setCurrentIndex(1); 104 | } 105 | } 106 | } 107 | 108 | SwipeView { 109 | clip: true 110 | interactive: false 111 | orientation: Qt.Vertical 112 | anchors.left: tabBar.right 113 | anchors.right: parent.right 114 | anchors.top: windowTitle.bottom 115 | anchors.bottom: tabBar.bottom 116 | currentIndex: tabBar.currentIndex 117 | 118 | Page { 119 | DiscoverPage { 120 | anchors.fill: parent 121 | } 122 | } 123 | 124 | Page { 125 | TransferPage { 126 | anchors.fill: parent 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/connectionmanager.cpp: -------------------------------------------------------------------------------- 1 | #include "discoverconnection.h" 2 | #include "fileblock.h" 3 | #include "filemanager.h" 4 | #include "connectionmanager.h" 5 | #include "transfersocket.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | ConnectionManager::ConnectionManager(QObject *parent) 14 | : QTcpServer (parent) 15 | { 16 | listen(QHostAddress::Any, 43800); 17 | } 18 | 19 | ConnectionManager::~ConnectionManager() 20 | { 21 | 22 | } 23 | 24 | void ConnectionManager::incomingConnection(qintptr handle) 25 | { 26 | QThread *thread = new QThread; 27 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 28 | TransferSocket *socket = new TransferSocket; 29 | if (!socket->setSocketDescriptor(handle)) { 30 | qDebug() << "Socket Error: " << socket->errorString(); 31 | } else { 32 | emit hasNewConnection(socket); 33 | qDebug() << "Connected Socket: " << handle; 34 | } 35 | socket->moveToThread(thread); 36 | thread->start(); 37 | } 38 | -------------------------------------------------------------------------------- /src/connectionmanager.h: -------------------------------------------------------------------------------- 1 | #ifndef CONNECTIONMANAGER_H 2 | #define CONNECTIONMANAGER_H 3 | 4 | #include 5 | 6 | class ConnectionManager : public QTcpServer 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | ConnectionManager(QObject *parent = nullptr); 12 | ~ConnectionManager(); 13 | 14 | signals: 15 | void hasNewConnection(QTcpSocket *socket); 16 | 17 | protected: 18 | virtual void incomingConnection(qintptr handle); 19 | }; 20 | 21 | #endif // CONNECTIONMANAGER_H 22 | -------------------------------------------------------------------------------- /src/discoverconnection.cpp: -------------------------------------------------------------------------------- 1 | #include "discoverconnection.h" 2 | #include 3 | 4 | DiscoverConnection::DiscoverConnection(QObject *parent) 5 | : QUdpSocket (parent) 6 | { 7 | bind(QHostAddress::Any, 43801); 8 | connect(this, &QUdpSocket::readyRead, this, &DiscoverConnection::processDatagram); 9 | } 10 | 11 | DiscoverConnection *DiscoverConnection::instance() 12 | { 13 | static DiscoverConnection d; 14 | return &d; 15 | } 16 | 17 | DiscoverConnection::~DiscoverConnection() 18 | { 19 | 20 | } 21 | 22 | QString DiscoverConnection::getName(const QHostAddress &address) const 23 | { 24 | return m_accessPoints.key(address); 25 | } 26 | 27 | QHostAddress DiscoverConnection::getAddress(const QString &name) const 28 | { 29 | return m_accessPoints[name]; 30 | } 31 | 32 | QString DiscoverConnection::name() const 33 | { 34 | return m_name; 35 | } 36 | 37 | void DiscoverConnection::setName(const QString &name) 38 | { 39 | if (name != m_name) { 40 | m_name = name; 41 | emit nameChanged(); 42 | } 43 | } 44 | 45 | void DiscoverConnection::discover() 46 | { 47 | m_accessPoints.clear(); 48 | writeDatagram("[DISCOVER]", QHostAddress::Broadcast, 43801); 49 | } 50 | 51 | void DiscoverConnection::connectToName(const QString &name) 52 | { 53 | writeDatagram("[CONNECT]##" + m_name.toLocal8Bit(), getAddress(name), 43801); 54 | } 55 | 56 | void DiscoverConnection::processDatagram() 57 | { 58 | while (hasPendingDatagrams()) { 59 | QNetworkDatagram datagram = receiveDatagram(); 60 | if (!datagram.senderAddress().isNull() && datagram.senderPort() != -1) { 61 | if (datagram.data() == "[DISCOVER]") { 62 | writeDatagram("[NAME]##" + m_name.toLocal8Bit(), datagram.senderAddress(), 63 | quint16(datagram.senderPort())); 64 | } else if (datagram.data().left(8) == "[NAME]##") { 65 | QString name = QString::fromLocal8Bit(datagram.data().mid(8)); 66 | m_accessPoints[name] = datagram.senderAddress(); 67 | qDebug().nospace() << "AccessPoint: [" << name << "]===[" << datagram.senderAddress() << "]"; 68 | emit newAccessPoint(name); 69 | } else if (datagram.data().left(11) == "[CONNECT]##") { 70 | QString name = QString::fromLocal8Bit(datagram.data().mid(11)); 71 | emit newConnection(name); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/discoverconnection.h: -------------------------------------------------------------------------------- 1 | #ifndef DISCOVERCONNECTION_H 2 | #define DISCOVERCONNECTION_H 3 | 4 | #include 5 | 6 | class DiscoverConnection : public QUdpSocket 7 | { 8 | Q_OBJECT 9 | 10 | Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) 11 | 12 | public: 13 | static DiscoverConnection* instance(); 14 | ~DiscoverConnection(); 15 | 16 | QString getName(const QHostAddress &address) const; 17 | QHostAddress getAddress(const QString &name) const; 18 | 19 | QString name() const; 20 | void setName(const QString &name); 21 | 22 | Q_INVOKABLE void discover(); 23 | Q_INVOKABLE void connectToName(const QString &name); 24 | 25 | signals: 26 | void nameChanged(); 27 | void newConnection(const QString &name); 28 | void newAccessPoint(const QString &name); 29 | 30 | private: 31 | DiscoverConnection(QObject *parent = nullptr); 32 | void processDatagram(); 33 | 34 | QString m_name = "未命名"; 35 | QMap m_accessPoints; 36 | }; 37 | 38 | #endif // DISCOVERCONNECTION_H 39 | -------------------------------------------------------------------------------- /src/fileapi.cpp: -------------------------------------------------------------------------------- 1 | #include "fileapi.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | FileApi::FileApi(QObject *parent) 10 | : QObject(parent) 11 | { 12 | 13 | } 14 | 15 | QString FileApi::fileToIcon(const QUrl &url) 16 | { 17 | QString fileName = QQmlFile::urlToLocalFileOrQrc(url); 18 | QFileIconProvider provider; 19 | QFileInfo info = QFileInfo(fileName); 20 | QIcon icon = provider.icon(info); 21 | QPixmap image = QPixmap(icon.pixmap(32, 32)); 22 | QDir dir; 23 | QString iconDir = qApp->applicationDirPath() + "/FileIcon/"; 24 | if (!dir.exists(iconDir)) 25 | dir.mkdir(iconDir); 26 | image.save(iconDir + info.fileName() + "_icon.png", "PNG"); 27 | 28 | return "file:///" + iconDir + info.fileName() + "_icon.png"; 29 | } 30 | 31 | QString FileApi::fileName(const QUrl &url) 32 | { 33 | QString fileName = QQmlFile::urlToLocalFileOrQrc(url); 34 | return QFileInfo(fileName).fileName(); 35 | } 36 | 37 | QString FileApi::convertByte(int byte) 38 | { 39 | if (byte > (1024 * 1024 * 1024)) 40 | return QString::asprintf("%.2fGB", byte / (1024.0 * 1024.0 * 1024.0)); 41 | else if (byte > (1024 * 1024)) 42 | return QString::asprintf("%.2fMB", byte / (1024.0 * 1024.0)); 43 | else if (byte > (1024)) 44 | return QString::asprintf("%.2fKB", byte / (1024.0)); 45 | else return QString::asprintf("%d bytes", int(byte)); 46 | } 47 | -------------------------------------------------------------------------------- /src/fileapi.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEAPI_H 2 | #define FILEAPI_H 3 | 4 | #include 5 | #include 6 | 7 | class FileApi : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | FileApi(QObject *parent = nullptr); 13 | 14 | Q_INVOKABLE QString fileToIcon(const QUrl &url); 15 | Q_INVOKABLE QString fileName(const QUrl &url); 16 | Q_INVOKABLE QString convertByte(int byte); 17 | }; 18 | 19 | #endif // FILEAPI_H 20 | -------------------------------------------------------------------------------- /src/fileblock.cpp: -------------------------------------------------------------------------------- 1 | #include "fileblock.h" 2 | 3 | QDataStream& operator>>(QDataStream &in, FileBlock &block) 4 | { 5 | in >> block.blockSize 6 | >> block.offset 7 | >> block.fileSize 8 | >> block.fileName 9 | >> block.dataBlock; 10 | 11 | return in; 12 | } 13 | 14 | QDataStream& operator<<(QDataStream &out, FileBlock &block) 15 | { 16 | out << block.blockSize 17 | << block.offset 18 | << block.fileSize 19 | << block.fileName 20 | << block.dataBlock; 21 | 22 | return out; 23 | } 24 | 25 | QDebug operator<<(QDebug debug, const FileBlock &block) 26 | { 27 | debug << "[blockSize]: " << block.blockSize << endl 28 | << "[offset]: " << block.offset << endl 29 | << "[fileSize]: " << block.fileSize << endl 30 | << "[fileName]: " << block.fileName << endl; 31 | 32 | return debug; 33 | } 34 | -------------------------------------------------------------------------------- /src/fileblock.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEBLOCK_H 2 | #define FILEBLOCK_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct FileBlock 9 | { 10 | qint16 blockSize; 11 | qint32 offset; 12 | qint32 fileSize; 13 | QByteArray fileName; 14 | QByteArray dataBlock; 15 | 16 | bool isEmpty() const { 17 | return fileName.isEmpty() || dataBlock.isEmpty(); 18 | } 19 | 20 | int size() const { 21 | return int(sizeof(blockSize)) + 22 | int(sizeof(offset)) + 23 | int(sizeof(fileSize)) + 24 | fileName.size() + 25 | dataBlock.size() + 26 | 2 * int(sizeof(int)); //有两个QByteArray,每个会在前面加int大小 27 | } 28 | }; 29 | 30 | extern QDataStream& operator>>(QDataStream &in, FileBlock &block); 31 | extern QDataStream& operator<<(QDataStream &out, FileBlock &block); 32 | extern QDebug operator<<(QDebug debug, const FileBlock &block); 33 | 34 | #endif // FILEBLOCK_H 35 | -------------------------------------------------------------------------------- /src/filemanager.cpp: -------------------------------------------------------------------------------- 1 | #include "filemanager.h" 2 | #include 3 | 4 | FileInfo::FileInfo(QObject *parent) 5 | : QObject (parent) 6 | { 7 | 8 | } 9 | 10 | FileInfo::FileInfo(const FileInfo &other, QObject *parent) 11 | : QObject (parent), 12 | m_fileName(other.m_fileName), 13 | m_offset(other.m_offset), 14 | m_fileSize(other.m_fileSize) 15 | { 16 | 17 | } 18 | 19 | FileInfo::FileInfo(const QString &fileName, int offset, int fileSize, QObject *parent) 20 | : QObject (parent), 21 | m_fileName(fileName), 22 | m_offset(offset), 23 | m_fileSize(fileSize) 24 | { 25 | 26 | } 27 | 28 | FileInfo::~FileInfo() 29 | { 30 | 31 | } 32 | 33 | QString FileInfo::fileName() const 34 | { 35 | return m_fileName; 36 | } 37 | 38 | void FileInfo::setFileName(const QString &fileName) 39 | { 40 | if (fileName != m_fileName) { 41 | m_fileName = fileName; 42 | emit fileNameChanged(); 43 | } 44 | } 45 | 46 | int FileInfo::offset() const 47 | { 48 | return m_offset; 49 | } 50 | 51 | void FileInfo::setOffset(int offset) 52 | { 53 | if (offset != m_offset) { 54 | m_offset = offset; 55 | emit offsetChanged(); 56 | } 57 | } 58 | 59 | int FileInfo::fileSize() const 60 | { 61 | return m_fileSize; 62 | } 63 | 64 | void FileInfo::setFileSize(int fileSize) 65 | { 66 | if (fileSize != m_fileSize) { 67 | m_fileSize = fileSize; 68 | emit fileSizeChanged(); 69 | } 70 | } 71 | 72 | 73 | FileManager* FileManager::instance() 74 | { 75 | static FileManager fileManager; 76 | return &fileManager; 77 | } 78 | 79 | FileManager::FileManager(QObject *parent) 80 | : QObject (parent) 81 | { 82 | m_readFilesProxy = new QQmlListProperty(this, m_readFiles); 83 | m_writeFilesProxy = new QQmlListProperty(this, m_writeFiles); 84 | } 85 | 86 | FileManager::~FileManager() 87 | { 88 | 89 | } 90 | 91 | QQmlListProperty FileManager::readFiles() 92 | { 93 | return *m_readFilesProxy; 94 | } 95 | 96 | QQmlListProperty FileManager::writeFiles() 97 | { 98 | return *m_writeFilesProxy; 99 | } 100 | 101 | void FileManager::addReadFile(const QString &fileName, qint32 totalSize) 102 | { 103 | FileInfo *info = new FileInfo(fileName, 0, totalSize, this); 104 | m_readFiles.append(info); 105 | m_filesTable[fileName] = info; 106 | emit readFilesChanged(); 107 | } 108 | 109 | void FileManager::updateReadFile(const QString &fileName, qint32 offset) 110 | { 111 | FileInfo *info = m_filesTable[fileName]; 112 | if (info) { 113 | info->setOffset(offset); 114 | if (offset == info->fileSize()) { 115 | m_filesTable.remove(fileName); 116 | emit writeFileComplete(fileName); 117 | } 118 | } 119 | } 120 | 121 | void FileManager::addWriteFile(const QString &fileName, qint32 totalSize) 122 | { 123 | FileInfo *info = new FileInfo(fileName, 0, totalSize, this); 124 | m_writeFiles.append(info); 125 | m_filesTable[fileName] = info; 126 | emit writeFilesChanged(); 127 | } 128 | 129 | void FileManager::updateWriteFile(const QString &fileName, qint32 offset) 130 | { 131 | FileInfo *info = m_filesTable[fileName]; 132 | if (info) { 133 | info->setOffset(offset); 134 | if (offset == info->fileSize()) { 135 | m_filesTable.remove(fileName); 136 | emit writeFileComplete(fileName); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/filemanager.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEMANAGER_H 2 | #define FILEMANAGER_H 3 | 4 | #include 5 | #include 6 | 7 | class FileInfo : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged) 12 | Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) 13 | Q_PROPERTY(int fileSize READ fileSize WRITE setFileSize NOTIFY fileSizeChanged) 14 | 15 | public: 16 | FileInfo(QObject *parent = nullptr); 17 | FileInfo(const FileInfo &other, QObject *parent = nullptr); 18 | FileInfo(const QString &fileName, int offset, int fileSize, QObject *parent = nullptr); 19 | ~FileInfo(); 20 | 21 | QString fileName() const; 22 | void setFileName(const QString &fileName); 23 | 24 | int offset() const; 25 | void setOffset(int offset); 26 | 27 | int fileSize() const; 28 | void setFileSize(int fileSize); 29 | 30 | signals: 31 | void fileNameChanged(); 32 | void offsetChanged(); 33 | void fileSizeChanged(); 34 | 35 | private: 36 | QString m_fileName; 37 | int m_offset; 38 | int m_fileSize; 39 | }; 40 | 41 | class FileManager : public QObject 42 | { 43 | Q_OBJECT 44 | 45 | Q_PROPERTY(QQmlListProperty readFiles READ readFiles NOTIFY readFilesChanged) 46 | Q_PROPERTY(QQmlListProperty writeFiles READ writeFiles NOTIFY writeFilesChanged) 47 | 48 | public: 49 | static FileManager* instance(); 50 | ~FileManager(); 51 | 52 | QQmlListProperty readFiles(); 53 | QQmlListProperty writeFiles(); 54 | 55 | public slots: 56 | void addReadFile(const QString &fileName, qint32 totalSize); 57 | void updateReadFile(const QString &fileName, qint32 offset); 58 | 59 | void addWriteFile(const QString &fileName, qint32 totalSize); 60 | void updateWriteFile(const QString &fileName, qint32 offset); 61 | 62 | signals: 63 | void readFilesChanged(); 64 | void writeFilesChanged(); 65 | void readFileComplete(const QString &fileName); 66 | void writeFileComplete(const QString &fileName); 67 | 68 | private: 69 | FileManager(QObject *parent = nullptr); 70 | 71 | QHash m_filesTable; 72 | QList m_readFiles; 73 | QQmlListProperty *m_readFilesProxy; 74 | QList m_writeFiles; 75 | QQmlListProperty *m_writeFilesProxy; 76 | }; 77 | 78 | #endif // FILEMANAGER_H 79 | -------------------------------------------------------------------------------- /src/filetransfer.cpp: -------------------------------------------------------------------------------- 1 | #include "discoverconnection.h" 2 | #include "connectionmanager.h" 3 | #include "filemanager.h" 4 | #include "filetransfer.h" 5 | #include "transfersocket.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | FileTransfer::FileTransfer(QObject *parent) 12 | : QObject (parent) 13 | { 14 | m_connection = new ConnectionManager(this); 15 | connect(m_connection, &ConnectionManager::hasNewConnection, this, [this](QTcpSocket *socket) { 16 | if (m_socket) 17 | m_socket->deleteLater(); 18 | m_socket = qobject_cast(socket); 19 | }); 20 | } 21 | 22 | FileTransfer *FileTransfer::instance() 23 | { 24 | static FileTransfer fileTransfer; 25 | return &fileTransfer; 26 | } 27 | 28 | FileTransfer::~FileTransfer() 29 | { 30 | 31 | } 32 | 33 | void FileTransfer::setAccessPoint(const QString &name) 34 | { 35 | DiscoverConnection *dc = DiscoverConnection::instance(); 36 | QHostAddress address = dc->getAddress(name); 37 | 38 | QThread *thread = new QThread; 39 | connect(thread, &QThread::finished, thread, &QThread::deleteLater); 40 | TransferSocket *socket = new TransferSocket; 41 | socket->moveToThread(thread); 42 | thread->start(); 43 | m_socket = socket; 44 | QMetaObject::invokeMethod(m_socket, "setDestAddress", Q_ARG(QHostAddress, address)); 45 | } 46 | 47 | void FileTransfer::sendFile(const QUrl &url) 48 | { 49 | QFileInfo info(QQmlFile::urlToLocalFileOrQrc(url)); 50 | FileManager::instance()->addWriteFile(info.fileName(), int(info.size())); 51 | QMetaObject::invokeMethod(m_socket, "sendFile", Q_ARG(QUrl, url)); 52 | } 53 | -------------------------------------------------------------------------------- /src/filetransfer.h: -------------------------------------------------------------------------------- 1 | #ifndef FILETRANSFER_H 2 | #define FILETRANSFER_H 3 | 4 | #include 5 | 6 | class ConnectionManager; 7 | class TransferSocket; 8 | class FileTransfer : public QObject 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | static FileTransfer* instance(); 14 | ~FileTransfer(); 15 | 16 | Q_INVOKABLE void setAccessPoint(const QString &name); 17 | Q_INVOKABLE void sendFile(const QUrl &url); 18 | 19 | signals: 20 | void error(const QString &error); 21 | 22 | private: 23 | FileTransfer(QObject *parent = nullptr); 24 | 25 | TransferSocket *m_socket; 26 | ConnectionManager *m_connection; 27 | }; 28 | 29 | #endif // FILETRANSFER_H 30 | -------------------------------------------------------------------------------- /src/framelesswindow.cpp: -------------------------------------------------------------------------------- 1 | #include "framelesswindow.h" 2 | 3 | static QRect MoveArea; 4 | 5 | FramelessWindow::FramelessWindow(QWindow *parent) 6 | : QQuickWindow (parent) 7 | 8 | { 9 | setFlags(flags() | Qt::Window | Qt::FramelessWindowHint); 10 | 11 | //在这里改变默认移动区域 12 | //只有鼠标在移动区域内,才能移动窗口 13 | MoveArea = {8, 8, width() - 16, 40}; 14 | connect(this, &QQuickWindow::widthChanged, this, [](int arg){ 15 | MoveArea.setWidth(arg - 16); 16 | }); 17 | } 18 | 19 | bool FramelessWindow::movable() const 20 | { 21 | return m_movable; 22 | } 23 | 24 | void FramelessWindow::setMovable(bool arg) 25 | { 26 | if (m_movable != arg) { 27 | m_movable = arg; 28 | emit movableChanged(); 29 | } 30 | } 31 | 32 | bool FramelessWindow::resizable() const 33 | { 34 | return m_resizable; 35 | } 36 | 37 | void FramelessWindow::setResizable(bool arg) 38 | { 39 | if (m_resizable != arg) { 40 | m_resizable = arg; 41 | emit resizableChanged(); 42 | } 43 | } 44 | 45 | void FramelessWindow::mousePressEvent(QMouseEvent *event) 46 | { 47 | m_startPos = event->globalPos(); 48 | m_oldPos = position(); 49 | m_oldSize = size(); 50 | event->ignore(); 51 | 52 | QQuickWindow::mousePressEvent(event); 53 | } 54 | 55 | void FramelessWindow::mouseReleaseEvent(QMouseEvent *event) 56 | { 57 | m_oldPos = position(); 58 | 59 | QQuickWindow::mouseReleaseEvent(event); 60 | } 61 | 62 | void FramelessWindow::mouseDoubleClickEvent(QMouseEvent *event) 63 | { 64 | if (m_currentArea == Move) { 65 | if (windowState() == Qt::WindowMaximized) { 66 | showNormal(); 67 | m_currentArea = Client; 68 | } else if (windowState() == Qt::WindowNoState) { 69 | showMaximized(); 70 | m_currentArea = Client; 71 | } 72 | } 73 | 74 | QQuickWindow::mouseDoubleClickEvent(event); 75 | } 76 | 77 | void FramelessWindow::mouseMoveEvent(QMouseEvent *event) 78 | { 79 | if (event->buttons() & Qt::LeftButton) { 80 | if (m_movable && m_currentArea == Move) { 81 | //单独处理移动区域,这样可以更快 82 | //但是需要注意,z序更高的MouseArea仍会触发 83 | setPosition(m_oldPos - m_startPos + event->globalPos()); 84 | } else if (m_resizable && m_currentArea != Move){ 85 | setWindowGeometry(event->globalPos()); 86 | } 87 | } else { 88 | QPoint pos = event->pos(); 89 | m_currentArea = getArea(pos); 90 | if (m_resizable) setCursorIcon(); 91 | } 92 | 93 | QQuickWindow::mouseMoveEvent(event); 94 | } 95 | 96 | FramelessWindow::MouseArea FramelessWindow::getArea(const QPoint &pos) 97 | { 98 | int x = pos.x(); 99 | int y = pos.y(); 100 | int w = width(); 101 | int h = height(); 102 | MouseArea area; 103 | 104 | if (x >= 0 && x <= 8 && y >= 0 && y <= 8) { 105 | area = TopLeft; 106 | } else if (x > 8 && x < (w - 8) && y >= 0 && y <= 8) { 107 | area = Top; 108 | } else if (x >=(w - 8) && x <= w && y >= 0 && y <= 8) { 109 | area = TopRight; 110 | } else if (x >= 0 && x <= 8 && y > 8 && y < (h - 8)) { 111 | area = Left; 112 | } else if (x >=(w - 8) && x <= w && y > 8 && y < (h - 8)) { 113 | area = Right; 114 | } else if (x >= 0 && x <= 8 && y >= (h - 8) && y <= h) { 115 | area = BottomLeft; 116 | } else if (x > 8 && x < (w - 8) && y >= (h - 8) && y <= h) { 117 | area = Bottom; 118 | } else if (x >=(w - 8) && x <= w && y >= (h - 8) && y <= h) { 119 | area = BottomRight; 120 | } else if (MoveArea.contains(x, y)) { 121 | area = Move; 122 | } else { 123 | area = Client; 124 | } 125 | 126 | return area; 127 | } 128 | 129 | void FramelessWindow::setWindowGeometry(const QPoint &pos) 130 | { 131 | QPoint offset = m_startPos - pos; 132 | 133 | if (offset.x() == 0 && offset.y() == 0) 134 | return; 135 | 136 | static auto set_geometry_func = [this](const QSize &size, const QPoint &pos) { 137 | QPoint temp_pos = m_oldPos; 138 | QSize temp_size = minimumSize(); 139 | if (size.width() > minimumWidth()) { 140 | temp_pos.setX(pos.x()); 141 | temp_size.setWidth(size.width()); 142 | } else { 143 | //防止瞬间拉过头,会导致错误的计算位置,这里纠正 144 | if (pos.x() != temp_pos.x()) 145 | temp_pos.setX(m_oldPos.x() + m_oldSize.width() - minimumWidth()); 146 | } 147 | if (size.height() > minimumHeight()) { 148 | temp_pos.setY(pos.y()); 149 | temp_size.setHeight(size.height()); 150 | } else { 151 | //防止瞬间拉过头,会导致错误的计算位置,这里纠正 152 | if (pos.y() != temp_pos.y()) 153 | temp_pos.setY(m_oldPos.y() + m_oldSize.height() - minimumHeight()); 154 | } 155 | setGeometry(QRect(temp_pos, temp_size)); 156 | update(); 157 | }; 158 | 159 | switch (m_currentArea) { 160 | case TopLeft: 161 | set_geometry_func(m_oldSize + QSize(offset.x(), offset.y()), m_oldPos - offset); 162 | break; 163 | case Top: 164 | set_geometry_func(m_oldSize + QSize(0, offset.y()), m_oldPos - QPoint(0, offset.y())); 165 | break; 166 | case TopRight: 167 | set_geometry_func(m_oldSize - QSize(offset.x(), -offset.y()), m_oldPos - QPoint(0, offset.y())); 168 | break; 169 | case Left: 170 | set_geometry_func(m_oldSize + QSize(offset.x(), 0), m_oldPos - QPoint(offset.x(), 0));; 171 | break; 172 | case Right: 173 | set_geometry_func(m_oldSize - QSize(offset.x(), 0), position()); 174 | break; 175 | case BottomLeft: 176 | set_geometry_func(m_oldSize + QSize(offset.x(), -offset.y()), m_oldPos - QPoint(offset.x(), 0)); 177 | break; 178 | case Bottom: 179 | set_geometry_func(m_oldSize + QSize(0, -offset.y()), position()); 180 | break; 181 | case BottomRight: 182 | set_geometry_func(m_oldSize - QSize(offset.x(), offset.y()), position()); 183 | break; 184 | default: 185 | break; 186 | } 187 | } 188 | 189 | void FramelessWindow::setCursorIcon() 190 | { 191 | static bool unset = false; 192 | 193 | switch (m_currentArea) { 194 | case TopLeft: 195 | case BottomRight: 196 | unset = true; 197 | setCursor(Qt::SizeFDiagCursor); 198 | break; 199 | case Top: 200 | case Bottom: 201 | unset = true; 202 | setCursor(Qt::SizeVerCursor); 203 | break; 204 | case TopRight: 205 | case BottomLeft: 206 | unset = true; 207 | setCursor(Qt::SizeBDiagCursor); 208 | break; 209 | case Left: 210 | case Right: 211 | unset = true; 212 | setCursor(Qt::SizeHorCursor); 213 | break; 214 | case Move: 215 | unset = true; 216 | setCursor(Qt::ArrowCursor); 217 | break; 218 | default: 219 | if (unset) { 220 | unset = false; 221 | unsetCursor(); 222 | } 223 | break; 224 | } 225 | } 226 | 227 | -------------------------------------------------------------------------------- /src/framelesswindow.h: -------------------------------------------------------------------------------- 1 | #ifndef FRAMELESSWINDOW_H 2 | #define FRAMELESSWINDOW_H 3 | 4 | #include 5 | 6 | class FramelessWindow : public QQuickWindow 7 | { 8 | Q_OBJECT 9 | 10 | Q_PROPERTY(bool movable READ movable WRITE setMovable NOTIFY movableChanged) 11 | Q_PROPERTY(bool resizable READ resizable WRITE setResizable NOTIFY resizableChanged) 12 | 13 | enum MouseArea { 14 | TopLeft = 1, 15 | Top, 16 | TopRight, 17 | Left, 18 | Move, 19 | Right, 20 | BottomLeft, 21 | Bottom, 22 | BottomRight, 23 | Client 24 | }; 25 | 26 | public: 27 | explicit FramelessWindow(QWindow *parent = nullptr); 28 | 29 | bool movable() const; 30 | void setMovable(bool arg); 31 | 32 | bool resizable() const; 33 | void setResizable(bool arg); 34 | 35 | protected: 36 | void mousePressEvent(QMouseEvent *event) override; 37 | void mouseReleaseEvent(QMouseEvent *event) override; 38 | void mouseDoubleClickEvent(QMouseEvent *event) override; 39 | void mouseMoveEvent(QMouseEvent *event) override; 40 | 41 | signals: 42 | void movableChanged(); 43 | void resizableChanged(); 44 | 45 | private: 46 | MouseArea getArea(const QPoint &pos); 47 | void setWindowGeometry(const QPoint &pos); 48 | void setCursorIcon(); 49 | 50 | bool m_movable = true; 51 | bool m_resizable = true; 52 | MouseArea m_currentArea = Move; 53 | QPoint m_startPos; 54 | QPoint m_oldPos; 55 | QSize m_oldSize; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "discoverconnection.h" 2 | #include "framelesswindow.h" 3 | #include "fileapi.h" 4 | #include "filemanager.h" 5 | #include "filetransfer.h" 6 | #include "scanneritem.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 15 | QCoreApplication::setOrganizationName("FileTransfer"); 16 | QCoreApplication::setOrganizationDomain("mps.filetransfer.app"); 17 | 18 | qRegisterMetaType("FileInfo"); 19 | qRegisterMetaType("QHostAddress"); 20 | qmlRegisterType("an.window", 1, 0, "FramelessWindow"); 21 | qmlRegisterType("an.file", 1, 0, "FileInfo"); 22 | qmlRegisterType("an.item", 1, 0, "ScannerItem"); 23 | 24 | QApplication app(argc, argv); 25 | QQmlApplicationEngine engine; 26 | engine.rootContext()->setContextProperty("fileApi", new FileApi); 27 | engine.rootContext()->setContextProperty("fileManager", FileManager::instance()); 28 | engine.rootContext()->setContextProperty("fileTransfer", FileTransfer::instance()); 29 | engine.rootContext()->setContextProperty("discoverCon", DiscoverConnection::instance()); 30 | engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); 31 | if (engine.rootObjects().isEmpty()) 32 | return -1; 33 | 34 | return app.exec(); 35 | } 36 | -------------------------------------------------------------------------------- /src/scanneritem.cpp: -------------------------------------------------------------------------------- 1 | #include "scanneritem.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | ScannerItem::ScannerItem(QQuickItem *parent) 9 | : QQuickPaintedItem (parent) 10 | { 11 | qsrand(uint (time(nullptr))); 12 | m_updateTimer = new QTimer(this); 13 | m_updateTimer->setInterval(16); 14 | connect(m_updateTimer, &QTimer::timeout, this, [this](){ update(); }); 15 | 16 | QTimer *timer = new QTimer(this); 17 | connect(timer, &QTimer::timeout, this, [this] { 18 | m_points.clear(); 19 | for(int i = 0; i < 5; ++i) { 20 | int alpha = qrand() % 100 + 40; 21 | int px = qrand() % int(width()); 22 | int py = qrand() % int(height()); 23 | m_points.append(Point(QPoint(px, py), alpha)); 24 | } 25 | }); 26 | timer->start(1400); 27 | } 28 | 29 | ScannerItem::~ScannerItem() 30 | { 31 | 32 | } 33 | 34 | void ScannerItem::start() 35 | { 36 | m_drawable = true; 37 | m_angle = 0; 38 | m_updateTimer->start(); 39 | } 40 | 41 | void ScannerItem::stop() 42 | { 43 | m_drawable = false; 44 | m_angle = 0; 45 | m_updateTimer->stop(); 46 | update(); //清除 47 | } 48 | 49 | void ScannerItem::paint(QPainter *painter) 50 | { 51 | painter->setRenderHint(QPainter::Antialiasing); 52 | painter->setPen(QPen(qRgba(120, 110, 250, 250))); 53 | 54 | //格子 55 | for(int i = 0; i < width(); i += 20) 56 | painter->drawLine(QPointF(i + 0.5, 0), QPointF(i + 0.5, height())); 57 | for(int j = 0; j < height(); j += 20) 58 | painter->drawLine(QPointF(0, j + 0.5), QPointF(width(), j + 0.5)); 59 | 60 | int min = int(qMin(width(), height())); 61 | QPoint center(int(width() / 2), int(height() / 2)); 62 | painter->drawEllipse(center, min / 2, min / 2); 63 | painter->drawEllipse(center, min / 3, min / 3); 64 | painter->drawEllipse(center, min / 6, min / 6); 65 | 66 | if (m_drawable) { 67 | int diff = int(qAbs(width() - height()) / 2); 68 | QConicalGradient gradient(width() / 2, height() / 2, m_angle + 180); 69 | gradient.setColorAt(0.1, QColor(15, 45, 188, 200)); 70 | gradient.setColorAt(0.7, QColor(15, 45, 188, 0)); 71 | painter->setBrush(gradient); 72 | painter->setPen(QPen(Qt::NoPen)); 73 | if (width() > height()) 74 | painter->drawPie(diff, 0, min, min, m_angle * 16, 60 * 16); 75 | else painter->drawPie(0, diff, min, min, m_angle * 16, 60 * 16); 76 | 77 | for(int i = 0; i < 5; ++i) { 78 | painter->setBrush(QBrush(QColor(15, 45, 188, m_points.at(i).alpha))); 79 | painter->drawEllipse(m_points.at(i).point, 7, 7); 80 | } 81 | m_angle -= 2; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/scanneritem.h: -------------------------------------------------------------------------------- 1 | #ifndef SCANNERITEM_H 2 | #define SCANNERITEM_H 3 | 4 | #include 5 | 6 | struct Point 7 | { 8 | QPoint point; 9 | int alpha; 10 | 11 | Point(const QPoint &p, int a) : point(p) , alpha(a) { } 12 | }; 13 | 14 | class ScannerItem : public QQuickPaintedItem 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | ScannerItem(QQuickItem *parent = nullptr); 20 | ~ScannerItem(); 21 | 22 | Q_INVOKABLE void start(); 23 | Q_INVOKABLE void stop(); 24 | 25 | protected: 26 | virtual void paint(QPainter *painter); 27 | 28 | private: 29 | bool m_drawable = false; 30 | int m_angle = 0; 31 | QList m_points; 32 | QTimer *m_updateTimer; 33 | }; 34 | 35 | #endif // SCANNERITEM_H 36 | -------------------------------------------------------------------------------- /src/transfersocket.cpp: -------------------------------------------------------------------------------- 1 | #include "fileblock.h" 2 | #include "filemanager.h" 3 | #include "transfersocket.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static const int maxBlockSize = 1024; 12 | 13 | struct File 14 | { 15 | QFile *file; 16 | qint32 size; 17 | }; 18 | 19 | class TransferSocketPrivate 20 | { 21 | public: 22 | int m_maxTaskNum = 8; 23 | QString m_cachePath; 24 | QByteArray m_recvData; 25 | QMap m_recvFiles; 26 | QHostAddress m_destAddress; 27 | }; 28 | 29 | TransferSocket::TransferSocket() 30 | { 31 | d = new TransferSocketPrivate; 32 | d->m_cachePath = qApp->applicationDirPath() + "/FileRecv/"; 33 | QDir dir; 34 | if (!dir.exists(d->m_cachePath)) { 35 | dir.mkpath(d->m_cachePath); 36 | } 37 | 38 | connect(this, &QTcpSocket::readyRead, this, [this]() { 39 | d->m_recvData += readAll(); 40 | processRecvBlock(); 41 | }); 42 | } 43 | 44 | TransferSocket::~TransferSocket() 45 | { 46 | delete d; 47 | } 48 | 49 | void TransferSocket::requestNewConnection() 50 | { 51 | abort(); 52 | connectToHost(d->m_destAddress, 43800); 53 | waitForConnected(5000); 54 | } 55 | 56 | void TransferSocket::setDestAddress(const QHostAddress &address) 57 | { 58 | if (d->m_destAddress != address) 59 | d->m_destAddress = address; 60 | requestNewConnection(); 61 | } 62 | 63 | void TransferSocket::sendFile(const QUrl &url) 64 | { 65 | if (state() != SocketState::ConnectedState) 66 | requestNewConnection(); 67 | 68 | QtConcurrent::run([this, url]() { 69 | QTime time; 70 | time.start(); 71 | QFile file(QQmlFile::urlToLocalFileOrQrc(url)); 72 | file.open(QIODevice::ReadOnly); 73 | 74 | qint32 offset = 0; 75 | qint32 totalSize = qint32(file.size()); 76 | QString fileName = QFileInfo(QQmlFile::urlToLocalFileOrQrc(url)).fileName(); 77 | while (offset < totalSize) { 78 | file.seek(offset); 79 | QByteArray dataBlock = file.read(maxBlockSize); 80 | FileBlock block = { qint16(dataBlock.size()), offset, totalSize, 81 | fileName.toLocal8Bit(), dataBlock}; 82 | QByteArray data; 83 | QDataStream out(&data, QIODevice::WriteOnly); 84 | out.setVersion(QDataStream::Qt_5_12); 85 | out << block; 86 | QMetaObject::invokeMethod(this, "writeToSocket", Q_ARG(QByteArray, data)); 87 | 88 | offset += dataBlock.size(); 89 | if (time.elapsed() >= 1000 || offset >= totalSize) { 90 | time.restart(); 91 | QMetaObject::invokeMethod(FileManager::instance(), "updateWriteFile", 92 | Q_ARG(QString, fileName), Q_ARG(int, offset)); 93 | } 94 | } 95 | 96 | file.close(); 97 | }); 98 | } 99 | 100 | void TransferSocket::processRecvBlock() 101 | { 102 | static QTime time = QTime::currentTime(); 103 | if (d->m_recvData.size() > 0) { 104 | FileBlock block; 105 | QDataStream in(&d->m_recvData, QIODevice::ReadOnly); 106 | in.setVersion(QDataStream::Qt_5_12); 107 | in >> block; 108 | 109 | if (block.isEmpty()) 110 | return; 111 | 112 | QString fileName = QString::fromLocal8Bit(block.fileName); 113 | 114 | if (!d->m_recvFiles[fileName].file) { 115 | QFile *file = new QFile(d->m_cachePath + fileName); 116 | file->open(QIODevice::WriteOnly); 117 | d->m_recvFiles[fileName].file = file; 118 | d->m_recvFiles[fileName].size = 0; 119 | QMetaObject::invokeMethod(FileManager::instance(), "addReadFile", 120 | Q_ARG(QString, fileName), Q_ARG(int, block.fileSize)); 121 | QThread::msleep(100); 122 | } 123 | 124 | if (d->m_recvFiles[fileName].size < block.fileSize) { 125 | d->m_recvFiles[fileName].size += block.blockSize; 126 | d->m_recvFiles[fileName].file->write(block.dataBlock); 127 | qDebug() << block; 128 | } 129 | 130 | if (d->m_recvFiles[fileName].size == block.fileSize) { 131 | d->m_recvFiles[fileName].file->close(); 132 | d->m_recvFiles[fileName].file->deleteLater(); 133 | d->m_recvFiles.remove(fileName); 134 | QMetaObject::invokeMethod(FileManager::instance(), "updateReadFile", 135 | Q_ARG(QString, fileName), Q_ARG(int, block.fileSize)); 136 | } 137 | 138 | if (time.elapsed() >= 1000) { 139 | time.restart(); 140 | QMetaObject::invokeMethod(FileManager::instance(), "updateReadFile", 141 | Q_ARG(QString, fileName), Q_ARG(int, d->m_recvFiles[fileName].size)); 142 | } 143 | 144 | d->m_recvData.remove(0, block.size()); 145 | if (d->m_recvData.size() > 0) //如果还有则继续处理 146 | processRecvBlock(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/transfersocket.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSFERSOCKET_H 2 | #define TRANSFERSOCKET_H 3 | 4 | #include 5 | 6 | class TransferSocketPrivate; 7 | class TransferSocket : public QTcpSocket 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | TransferSocket(); 13 | ~TransferSocket(); 14 | 15 | void requestNewConnection(); 16 | 17 | Q_INVOKABLE void setDestAddress(const QHostAddress &address); 18 | Q_INVOKABLE void sendFile(const QUrl &url); 19 | Q_INVOKABLE void writeToSocket(const QByteArray &data) { QTcpSocket::write(data); } 20 | 21 | signals: 22 | void hasError(const QString &error); 23 | 24 | public slots: 25 | void processRecvBlock(); 26 | 27 | private: 28 | TransferSocketPrivate *d; 29 | }; 30 | 31 | #endif // TRANSFERSOCKET_H 32 | --------------------------------------------------------------------------------