├── .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 | 
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 |
--------------------------------------------------------------------------------