Browse Source

创建git

tags/v1.0
chenyong 9 years ago
commit
1f87aeb59f
100 changed files with 7056 additions and 0 deletions
  1. +38
    -0
      .gitignore
  2. +674
    -0
      LICENSE
  3. +41
    -0
      addfellowdialog.cpp
  4. +29
    -0
      addfellowdialog.h
  5. +38
    -0
      addfellowdialog.ui
  6. +57
    -0
      chooseemojidlg.cpp
  7. +31
    -0
      chooseemojidlg.h
  8. +77
    -0
      chooseemojidlg.ui
  9. +49
    -0
      chooseemojiwidget.cpp
  10. +24
    -0
      chooseemojiwidget.h
  11. +102
    -0
      default.qrc
  12. +130
    -0
      downloadfiledlg.ui
  13. +209
    -0
      emoji.cpp
  14. +11
    -0
      emoji.h
  15. +100
    -0
      feiq.pro
  16. +75
    -0
      feiqlib/asynwait.cpp
  17. +42
    -0
      feiqlib/asynwait.h
  18. +170
    -0
      feiqlib/content.h
  19. +13
    -0
      feiqlib/defer.cpp
  20. +16
    -0
      feiqlib/defer.h
  21. +73
    -0
      feiqlib/encoding.cpp
  22. +34
    -0
      feiqlib/encoding.h
  23. +289
    -0
      feiqlib/feiqcommu.cpp
  24. +86
    -0
      feiqlib/feiqcommu.h
  25. +934
    -0
      feiqlib/feiqengine.cpp
  26. +92
    -0
      feiqlib/feiqengine.h
  27. +119
    -0
      feiqlib/feiqmodel.cpp
  28. +38
    -0
      feiqlib/feiqmodel.h
  29. +104
    -0
      feiqlib/fellow.h
  30. +82
    -0
      feiqlib/filetask.cpp
  31. +64
    -0
      feiqlib/filetask.h
  32. +199
    -0
      feiqlib/history.cpp
  33. +50
    -0
      feiqlib/history.h
  34. +66
    -0
      feiqlib/ifeiqview.h
  35. +139
    -0
      feiqlib/ipmsg.h
  36. +29
    -0
      feiqlib/msgqueuethread.cpp
  37. +82
    -0
      feiqlib/msgqueuethread.h
  38. +145
    -0
      feiqlib/parcelable.h
  39. +47
    -0
      feiqlib/post.h
  40. +27
    -0
      feiqlib/protocol.h
  41. +84
    -0
      feiqlib/tcpserver.cpp
  42. +23
    -0
      feiqlib/tcpserver.h
  43. +113
    -0
      feiqlib/tcpsocket.cpp
  44. +34
    -0
      feiqlib/tcpsocket.h
  45. +189
    -0
      feiqlib/udpcommu.cpp
  46. +68
    -0
      feiqlib/udpcommu.h
  47. +16
    -0
      feiqlib/uniqueid.cpp
  48. +21
    -0
      feiqlib/uniqueid.h
  49. +84
    -0
      feiqlib/utils.cpp
  50. +13
    -0
      feiqlib/utils.h
  51. +106
    -0
      fellowlistwidget.cpp
  52. +37
    -0
      fellowlistwidget.h
  53. +230
    -0
      filemanagerdlg.cpp
  54. +55
    -0
      filemanagerdlg.h
  55. BIN
      icon.icns
  56. +21
    -0
      main.cpp
  57. +526
    -0
      mainwindow.cpp
  58. +100
    -0
      mainwindow.h
  59. +209
    -0
      mainwindow.ui
  60. +14
    -0
      osx/notification.h
  61. +22
    -0
      osx/notification.mm
  62. +25
    -0
      osx/osxplatform.cpp
  63. +22
    -0
      osx/osxplatform.h
  64. +59
    -0
      platformdepend.cpp
  65. +35
    -0
      platformdepend.h
  66. +57
    -0
      readme.md
  67. +216
    -0
      recvtextedit.cpp
  68. +52
    -0
      recvtextedit.h
  69. BIN
      res/face/1.gif
  70. BIN
      res/face/10.gif
  71. BIN
      res/face/11.gif
  72. BIN
      res/face/12.gif
  73. BIN
      res/face/13.gif
  74. BIN
      res/face/14.gif
  75. BIN
      res/face/15.gif
  76. BIN
      res/face/16.gif
  77. BIN
      res/face/17.gif
  78. BIN
      res/face/18.gif
  79. BIN
      res/face/19.gif
  80. BIN
      res/face/2.gif
  81. BIN
      res/face/20.gif
  82. BIN
      res/face/21.gif
  83. BIN
      res/face/22.gif
  84. BIN
      res/face/23.gif
  85. BIN
      res/face/24.gif
  86. BIN
      res/face/25.gif
  87. BIN
      res/face/26.gif
  88. BIN
      res/face/27.gif
  89. BIN
      res/face/28.gif
  90. BIN
      res/face/29.gif
  91. BIN
      res/face/3.gif
  92. BIN
      res/face/30.gif
  93. BIN
      res/face/31.gif
  94. BIN
      res/face/32.gif
  95. BIN
      res/face/33.gif
  96. BIN
      res/face/34.gif
  97. BIN
      res/face/35.gif
  98. BIN
      res/face/36.gif
  99. BIN
      res/face/37.gif
  100. BIN
      res/face/38.gif

+ 38
- 0
.gitignore View File

@@ -0,0 +1,38 @@
# C++ objects and libs

*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.dll
*.dylib

# Qt-es

/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
qrc_*.cpp
ui_*.h
Makefile*
*build-*

# QtCreator

*.autosave

# QtCtreator Qml
*.qmlproject.user
*.qmlproject.user.*

# QtCtreator CMake
CMakeLists.txt.user


+ 674
- 0
LICENSE View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The GNU General Public License is a free, copyleft license for
software and other kinds of works.

The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.

When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.

Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

The precise terms and conditions for copying, distribution and
modification follow.

TERMS AND CONDITIONS

0. Definitions.

"This License" refers to version 3 of the GNU General Public License.

"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.

To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

A "covered work" means either the unmodified Program or a work based
on the Program.

To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

1. Source Code.

The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.

A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

The Corresponding Source for a work in source code form is that
same work.

2. Basic Permissions.

All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

4. Conveying Verbatim Copies.

You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

5. Conveying Modified Source Versions.

You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.

b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".

c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.

d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.

A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

6. Conveying Non-Source Forms.

You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.

b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.

c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.

d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.

e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.

A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

7. Additional Terms.

"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or

b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or

c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or

d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or

e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or

f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.

All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

8. Termination.

You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

9. Acceptance Not Required for Having Copies.

You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

10. Automatic Licensing of Downstream Recipients.

Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.

An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

11. Patents.

A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".

A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

12. No Surrender of Others' Freedom.

If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

13. Use with the GNU Affero General Public License.

Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

14. Revised Versions of this License.

The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

15. Disclaimer of Warranty.

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

16. Limitation of Liability.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

17. Interpretation of Sections 15 and 16.

If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.

The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ 41
- 0
addfellowdialog.cpp View File

@@ -0,0 +1,41 @@
#include "addfellowdialog.h"
#include "ui_addfellowdialog.h"
#include <QHostAddress>
#include <QMessageBox>

AddFellowDialog::AddFellowDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::AddFellowDialog)
{
ui->setupUi(this);
connect(ui->okBtn, SIGNAL(clicked(bool)), this, SLOT(onOkClicked()));
}

AddFellowDialog::~AddFellowDialog()
{
delete ui;
}

QString AddFellowDialog::getIp()
{
return ui->ipEdit->text();
}

void AddFellowDialog::onOkClicked()
{
auto ip = ui->ipEdit->text();
if (isValidIp(ip))
{
accept();
}
else
{
QMessageBox::warning(this, "ip地址无效", "要添加的ip地址无效");
}
}

bool AddFellowDialog::isValidIp(const QString &ip)
{
QHostAddress address;
return address.setAddress(ip);
}

+ 29
- 0
addfellowdialog.h View File

@@ -0,0 +1,29 @@
#ifndef ADDFELLOWDIALOG_H
#define ADDFELLOWDIALOG_H

#include <QDialog>

namespace Ui {
class AddFellowDialog;
}

class AddFellowDialog : public QDialog
{
Q_OBJECT

public:
explicit AddFellowDialog(QWidget *parent = 0);
~AddFellowDialog();

public:
QString getIp();
private slots:
void onOkClicked();
private:
bool isValidIp(const QString& ip);

private:
Ui::AddFellowDialog *ui;
};

#endif // ADDFELLOWDIALOG_H

+ 38
- 0
addfellowdialog.ui View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddFellowDialog</class>
<widget class="QDialog" name="AddFellowDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>45</height>
</rect>
</property>
<property name="windowTitle">
<string>手动添加好友</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="ipEdit">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>请输入要添加的好友ip</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="okBtn">
<property name="text">
<string>确定</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

+ 57
- 0
chooseemojidlg.cpp View File

@@ -0,0 +1,57 @@
#include "chooseemojidlg.h"
#include "ui_chooseemojidlg.h"
#include <QMouseEvent>
#include <QMovie>
#include "emoji.h"

ChooseEmojiDlg::ChooseEmojiDlg(QWidget *parent) :
QDialog(parent),
ui(new Ui::ChooseEmojiDlg)
{
ui->setupUi(this);

setWindowFlags(Qt::Tool);
setFixedSize(size());

mMovie = new QMovie(this);
ui->gifLabel->setMovie(mMovie);

connect(ui->chooseWidget, SIGNAL(choose(int)), this, SLOT(choose(int)));
connect(ui->chooseWidget, SIGNAL(choose(int)), this, SLOT(hide()));
connect(ui->chooseWidget, SIGNAL(hesitate(int)), this, SLOT(hesitate(int)));
}

ChooseEmojiDlg::~ChooseEmojiDlg()
{
delete ui;
}

void ChooseEmojiDlg::hideEvent(QHideEvent *event)
{
mMovie->stop();
}

void ChooseEmojiDlg::hesitate(int index)
{
if (index == -1)
{
ui->hintLabel->setText("");
mMovie->stop();
ui->gifLabel->setText("");
}
else
{
ui->hintLabel->setText(QString(g_emojiText[index])+" "+QString(g_emojis[index]));

auto gif = ":/default/res/face/"+QString::number(index+1)+".gif";

mMovie->stop();
mMovie->setFileName(gif);
mMovie->start();
}
}

void ChooseEmojiDlg::choose(int index)
{
emit choose(g_emojis[index]);
}

+ 31
- 0
chooseemojidlg.h View File

@@ -0,0 +1,31 @@
#ifndef CHOOSEEMOJIDLG_H
#define CHOOSEEMOJIDLG_H

#include <QDialog>

namespace Ui {
class ChooseEmojiDlg;
}

class ChooseEmojiDlg : public QDialog
{
Q_OBJECT

public:
explicit ChooseEmojiDlg(QWidget *parent = 0);
~ChooseEmojiDlg();
protected:
void hideEvent(QHideEvent *event);
signals:
void choose(const QString& emojiText);

private slots:
void hesitate(int index);
void choose(int index);

private:
Ui::ChooseEmojiDlg *ui;
QMovie* mMovie;
};

#endif // CHOOSEEMOJIDLG_H

+ 77
- 0
chooseemojidlg.ui View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ChooseEmojiDlg</class>
<widget class="QDialog" name="ChooseEmojiDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>401</width>
<height>181</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>选择要插入的表情</string>
</property>
<widget class="QLabel" name="hintLabel">
<property name="geometry">
<rect>
<x>0</x>
<y>150</y>
<width>351</width>
<height>31</height>
</rect>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
<widget class="ChooseEmojiWidget" name="chooseWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>401</width>
<height>151</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background-image: url(:/default/res/face/page.bmp);</string>
</property>
</widget>
<widget class="QLabel" name="gifLabel">
<property name="geometry">
<rect>
<x>360</x>
<y>150</y>
<width>41</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>ChooseEmojiWidget</class>
<extends>QLabel</extends>
<header>chooseemojiwidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

+ 49
- 0
chooseemojiwidget.cpp View File

@@ -0,0 +1,49 @@
#include "chooseemojiwidget.h"
#include <QMouseEvent>
#include "emoji.h"

ChooseEmojiWidget::ChooseEmojiWidget(QWidget* parent)
:QLabel(parent), mIndex(-1)
{
setMouseTracking(true);
}

void ChooseEmojiWidget::mousePressEvent(QMouseEvent *event)
{
mIndex = getIndex(event->x(), event->y());
}

void ChooseEmojiWidget::mouseMoveEvent(QMouseEvent *event)
{
auto index = getIndex(event->x(), event->y());
if (index >= 0 && index < EMOJI_LEN)
{
emit hesitate(index);
}
else
{
emit hesitate(-1);
}
}

void ChooseEmojiWidget::mouseReleaseEvent(QMouseEvent *event)
{
auto index = getIndex(event->x(), event->y());
if (mIndex == index && mIndex >= 0 && mIndex < EMOJI_LEN)
{
emit choose(mIndex);
}

mIndex = -1;
}

int ChooseEmojiWidget::getIndex(int x, int y)
{
int col = x/25;
int row = y/25;
auto index = row * EMOJI_BMP_COL + col;
if (index >= 0 && index < EMOJI_LEN)
return index;

return -1;
}

+ 24
- 0
chooseemojiwidget.h View File

@@ -0,0 +1,24 @@
#ifndef CHOOSEEMOJIWIDGET_H
#define CHOOSEEMOJIWIDGET_H

#include <QLabel>

class ChooseEmojiWidget : public QLabel
{
Q_OBJECT
public:
ChooseEmojiWidget(QWidget* parent=0);
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
private:
int getIndex(int x, int y);
signals:
void choose(int emojiIndex);
void hesitate(int emojiIndex);
private:
int mIndex;
};

#endif // CHOOSEEMOJIWIDGET_H

+ 102
- 0
default.qrc View File

@@ -0,0 +1,102 @@
<RCC>
<qresource prefix="/default">
<file>res/icon.png</file>
<file>res/face/1.gif</file>
<file>res/face/2.gif</file>
<file>res/face/3.gif</file>
<file>res/face/4.gif</file>
<file>res/face/5.gif</file>
<file>res/face/6.gif</file>
<file>res/face/7.gif</file>
<file>res/face/8.gif</file>
<file>res/face/9.gif</file>
<file>res/face/10.gif</file>
<file>res/face/11.gif</file>
<file>res/face/12.gif</file>
<file>res/face/13.gif</file>
<file>res/face/14.gif</file>
<file>res/face/15.gif</file>
<file>res/face/16.gif</file>
<file>res/face/17.gif</file>
<file>res/face/18.gif</file>
<file>res/face/19.gif</file>
<file>res/face/20.gif</file>
<file>res/face/21.gif</file>
<file>res/face/22.gif</file>
<file>res/face/23.gif</file>
<file>res/face/24.gif</file>
<file>res/face/25.gif</file>
<file>res/face/26.gif</file>
<file>res/face/27.gif</file>
<file>res/face/28.gif</file>
<file>res/face/29.gif</file>
<file>res/face/30.gif</file>
<file>res/face/31.gif</file>
<file>res/face/32.gif</file>
<file>res/face/33.gif</file>
<file>res/face/34.gif</file>
<file>res/face/35.gif</file>
<file>res/face/36.gif</file>
<file>res/face/37.gif</file>
<file>res/face/38.gif</file>
<file>res/face/39.gif</file>
<file>res/face/40.gif</file>
<file>res/face/41.gif</file>
<file>res/face/42.gif</file>
<file>res/face/43.gif</file>
<file>res/face/44.gif</file>
<file>res/face/45.gif</file>
<file>res/face/46.gif</file>
<file>res/face/47.gif</file>
<file>res/face/48.gif</file>
<file>res/face/49.gif</file>
<file>res/face/50.gif</file>
<file>res/face/51.gif</file>
<file>res/face/52.gif</file>
<file>res/face/53.gif</file>
<file>res/face/54.gif</file>
<file>res/face/55.gif</file>
<file>res/face/56.gif</file>
<file>res/face/57.gif</file>
<file>res/face/58.gif</file>
<file>res/face/59.gif</file>
<file>res/face/60.gif</file>
<file>res/face/61.gif</file>
<file>res/face/62.gif</file>
<file>res/face/63.gif</file>
<file>res/face/64.gif</file>
<file>res/face/65.gif</file>
<file>res/face/66.gif</file>
<file>res/face/67.gif</file>
<file>res/face/68.gif</file>
<file>res/face/69.gif</file>
<file>res/face/70.gif</file>
<file>res/face/71.gif</file>
<file>res/face/72.gif</file>
<file>res/face/73.gif</file>
<file>res/face/74.gif</file>
<file>res/face/75.gif</file>
<file>res/face/76.gif</file>
<file>res/face/77.gif</file>
<file>res/face/78.gif</file>
<file>res/face/79.gif</file>
<file>res/face/80.gif</file>
<file>res/face/81.gif</file>
<file>res/face/82.gif</file>
<file>res/face/83.gif</file>
<file>res/face/84.gif</file>
<file>res/face/85.gif</file>
<file>res/face/86.gif</file>
<file>res/face/87.gif</file>
<file>res/face/88.gif</file>
<file>res/face/89.gif</file>
<file>res/face/90.gif</file>
<file>res/face/91.gif</file>
<file>res/face/92.gif</file>
<file>res/face/93.gif</file>
<file>res/face/94.gif</file>
<file>res/face/95.gif</file>
<file>res/face/96.gif</file>
<file>res/face/page.bmp</file>
</qresource>
</RCC>

+ 130
- 0
downloadfiledlg.ui View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DownloadFileDlg</class>
<widget class="QDialog" name="DownloadFileDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>324</height>
</rect>
</property>
<property name="windowTitle">
<string>文件管理</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>4</number>
</property>
<property name="leftMargin">
<number>8</number>
</property>
<property name="topMargin">
<number>8</number>
</property>
<property name="rightMargin">
<number>8</number>
</property>
<property name="bottomMargin">
<number>8</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="clearBtn">
<property name="text">
<string>清除</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="delBtn">
<property name="text">
<string>删除</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="saveBtn">
<property name="text">
<string>保存</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="taskTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>5</number>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>70</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>类型</string>
</property>
</column>
<column>
<property name="text">
<string>状态</string>
</property>
</column>
<column>
<property name="text">
<string>好友</string>
</property>
</column>
<column>
<property name="text">
<string>文件名</string>
</property>
</column>
<column>
<property name="text">
<string>进度</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLineEdit" name="searchEdit"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

+ 209
- 0
emoji.cpp View File

@@ -0,0 +1,209 @@
#include "emoji.h"

const char* g_emojis[EMOJI_LEN]={
"/:)",
"/:~",
"/:*",
"/:|",
"/8-)",
"/:<",
"/:$",
"/:X",
"/:Z",
"/:'(",
"/:-|",
"/:@",
"/:P",
"/:D",
"/:O",
"/<rotate>",

"/:(",
"/:+",
"/:lenhan",
"/:Q",
"/:T",
"/;P",
"/;-D",
"/;d",
"/;o",
"/:g",
"/|-)",
"/:!",
"/:L",
"/:>",
"/;bin",
"/:fw",

"/;fd",
"/:-S",
"/;?",
"/;x",
"/;@",
"/:8",
"/;!",
"/!!!",
"/:xx",
"/:bye",
"/:csweat",
"/:knose",
"/:applause",
"/:cdale",
"/:huaixiao",
"/:shake",

"/:lhenhen",
"/:rhenhen",
"/:yawn",
"/:snooty",
"/:chagrin",
"/:kcry",
"/:yinxian",
"/:qinqin",
"/:xiaren",
"/:kelin",
"/:caidao",
"/:xig",
"/:bj",
"/:basketball",
"/:pingpong",
"/:jump",

"/:coffee",
"/:eat",
"/:pig",
"/:rose",
"/:fade",
"/:kiss",
"/:heart",
"/:break",
"/:cake",
"/:shd",
"/:bomb",
"/:dao",
"/:footb",
"/:piaocon",
"/:shit",
"/:oh",

"/:moon",
"/:sun",
"/;gift",
"/:hug",
"/:strong",
"/;weak",
"/:share",
"/:shl",
"/:baoquan",
"/:cajole",
"/:quantou",
"/:chajin",
"/:aini",
"/:sayno",
"/:sayok",
"/:love"
};

const char* g_emojiText[EMOJI_LEN]={
"微笑",
"撇嘴",
"色",
"发呆",
"得意",
"流泪",
"害羞",
"闭嘴",
"困",
"大哭",
"尴尬",
"发怒",
"调皮",
"龇牙",
"惊讶",
"转圈",

"难过",
"酷",
"冷汗",
"抓狂",
"吐",
"偷笑",
"可爱",
"白眼",
"傲慢",
"饥饿",
"困 ",
"惊恐",
"冷汗",
"憨笑",
"大兵",
"飞吻",

"奋斗",
"咒骂",
"疑问",
"嘘……",
"晕",
"折磨",
"衰",
"骷髅",
"敲打",
"再见",
"擦汗",
"抠鼻",
"鼓掌",
"糗大了",
"坏笑",
"发抖",

"左哼哼",
"右哼哼",
"哈欠",
"鄙视",
"委屈",
"快哭了",
"阴险",
"亲亲",
"吓",
"可怜",
"菜刀",
"西瓜",
"啤酒",
"篮球",
"乒乓",
"跳",

"咖啡",
"吃饭",
"猪头",
"玫瑰",
"枯萎",
"示爱",
"爱心",
"心碎",
"蛋糕",
"闪电",
"炸弹",
"匕首",
"足球",
"瓢虫",
"大便",
"怄火",

"月亮",
"太阳",
"礼物",
"拥抱",
"点赞",
"弱",
"握手",
"胜利",
"抱拳",
"勾引",
"拳头",
"差劲",
"爱你",
"no",
"ok",
"爱情"
};

+ 11
- 0
emoji.h View File

@@ -0,0 +1,11 @@
#ifndef EMOJI_H
#define EMOJI_H

#define EMOJI_LEN 96
#define EMOJI_BMP_ROW 6
#define EMOJI_BMP_COL 16

extern const char* g_emojis[EMOJI_LEN];
extern const char* g_emojiText[EMOJI_LEN];

#endif // EMOJI_H

+ 100
- 0
feiq.pro View File

@@ -0,0 +1,100 @@
#-------------------------------------------------
#
# Project created by QtCreator 2016-07-30T10:47:11
#
#-------------------------------------------------

QT += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = feiq
TEMPLATE = app



LIBS += -liconv -lsqlite3

mac{
QT += macextras

ICON = icon.icns

LIBS += -framework Foundation

OBJECTIVE_SOURCES += osx/notification.mm

SOURCES += osx/osxplatform.cpp

HEADERS += osx/notification.h\
osx/osxplatform.h
}

SOURCES += main.cpp\
mainwindow.cpp \
feiqlib/udpcommu.cpp \
feiqlib/feiqcommu.cpp \
feiqlib/feiqengine.cpp \
feiqlib/feiqmodel.cpp \
feiqlib/encoding.cpp \
feiqlib/tcpserver.cpp \
feiqlib/tcpsocket.cpp \
feiqlib/utils.cpp \
feiqlib/uniqueid.cpp \
feiqlib/filetask.cpp \
feiqlib/defer.cpp \
feiqlib/asynwait.cpp \
feiqlib/history.cpp \
fellowlistwidget.cpp \
searchfellowdlg.cpp \
recvtextedit.cpp \
filemanagerdlg.cpp \
addfellowdialog.cpp \
emoji.cpp \
chooseemojidlg.cpp \
platformdepend.cpp \
chooseemojiwidget.cpp \
sendtextedit.cpp


HEADERS += mainwindow.h \
feiqlib/ipmsg.h \
feiqlib/udpcommu.h \
feiqlib/feiqcommu.h \
feiqlib/protocol.h \
feiqlib/post.h \
feiqlib/content.h \
feiqlib/feiqengine.h \
feiqlib/feiqmodel.h \
feiqlib/fellow.h \
feiqlib/ifeiqview.h \
feiqlib/msgqueuethread.h \
feiqlib/encoding.h \
feiqlib/tcpserver.h \
feiqlib/tcpsocket.h \
feiqlib/utils.h \
feiqlib/uniqueid.h \
feiqlib/filetask.h \
feiqlib/defer.h \
feiqlib/asynwait.h \
feiqlib/history.h \
feiqlib/parcelable.h \
fellowlistwidget.h \
searchfellowdlg.h \
recvtextedit.h \
filemanagerdlg.h \
addfellowdialog.h \
emoji.h \
chooseemojidlg.h \
platformdepend.h \
chooseemojiwidget.h \
sendtextedit.h

FORMS += mainwindow.ui \
searchfellowdlg.ui \
downloadfiledlg.ui \
addfellowdialog.ui \
chooseemojidlg.ui

RESOURCES += \
default.qrc

+ 75
- 0
feiqlib/asynwait.cpp View File

@@ -0,0 +1,75 @@
#include "asynwait.h"
#include <thread>
#include <unistd.h>

AsynWait::AsynWait()
{

}

void AsynWait::start(int precision)
{
if (mStarted == true)
return;

mStarted=true;
mPrecision = precision;

thread thd(&AsynWait::run, this);
mThd.swap(thd);
}

void AsynWait::stop()
{
mStarted=false;
mThd.join();
}

void AsynWait::addWaitPack(IdType packetId, AsynWait::OnWaitTimeout onTimeout, int msTimeo)
{
WaitPack pack;
pack.id = packetId;
pack.handler = onTimeout;
pack.timeo = time_point_cast<milliseconds>(system_clock::now()) + milliseconds(msTimeo);

mPacksMutex.lock();
mWaitPacks.push_back(pack);
mPacksMutex.unlock();
}

void AsynWait::clearWaitPack(IdType packetId)
{
mPacksMutex.lock();
mWaitPacks.remove_if([packetId](WaitPack pack){
return pack.id = packetId;
});
mPacksMutex.unlock();
}

void AsynWait::run()
{
list<WaitPack> timeos;
while (mStarted) {
usleep(mPrecision*1000);
timeos.clear();

auto cur = system_clock::now();
mPacksMutex.lock();
mWaitPacks.remove_if([&cur, &timeos](const WaitPack& pack){
if (cur > pack.timeo)
{
timeos.push_back(pack);
return true;
}
return false;
});
mPacksMutex.unlock();

if (!timeos.empty())
{
for (auto& pack : timeos)
pack.handler(pack.id);
}
}
}


+ 42
- 0
feiqlib/asynwait.h View File

@@ -0,0 +1,42 @@
#ifndef ASYNWAIT_H
#define ASYNWAIT_H

#include "uniqueid.h"
#include <functional>
#include <list>
#include <mutex>
#include <chrono>
#include <thread>

using namespace std;
using namespace std::chrono;

class AsynWait
{
public:
typedef function<void (IdType)> OnWaitTimeout;
AsynWait();
void start(int precision=200);
void stop();

public:
void addWaitPack(IdType packetId, OnWaitTimeout onTimeout, int msTimeo);
void clearWaitPack(IdType packetId);

private:
void run();
private:
struct WaitPack{
IdType id;
OnWaitTimeout handler;
time_point<system_clock, milliseconds> timeo;
};

list<WaitPack> mWaitPacks;
mutex mPacksMutex;
bool mStarted=false;
int mPrecision;
thread mThd;
};

#endif // ASYNWAIT_H

+ 170
- 0
feiqlib/content.h View File

@@ -0,0 +1,170 @@
#ifndef CONTENT_H
#define CONTENT_H

#include <string>
#include "protocol.h"
#include "uniqueid.h"
#include <sys/stat.h>
#include "ipmsg.h"
#include "utils.h"
#include "parcelable.h"
using namespace std;

enum class ContentType{Text, Knock, File, Image, Id};



/**
* @brief 消息内容
*/
class Content : public Parcelable
{
public:
IdType packetNo;
void setPacketNo(string val){
packetNo = stoul(val);
}
void setPacketNo(IdType val){
packetNo = val;
}

virtual ~Content(){}
ContentType type() const {return mType;}

protected:
ContentType mType;

public:
virtual void writeTo(Parcel& out) const override
{
out.write(mType);
out.write(packetNo);
}
virtual void readFrom(Parcel& in) override
{
in.read(mType);
in.read(packetNo);
}
};

class IdContent : public Content
{
public:
IdContent(){mType = ContentType::Id;}
IdType id;
};

class TextContent : public Content
{
public:
TextContent(){mType = ContentType::Text;}
string text;
string format;

public:
virtual void writeTo(Parcel& out) const override
{
Content::writeTo(out);
out.writeString(text);
out.writeString(format);
}

virtual void readFrom(Parcel& in) override
{
Content::readFrom(in);
in.readString(text);
in.readString(format);
}
};

class FileContent : public Content
{
public:
FileContent(){mType = ContentType::File;}
IdType fileId;
string filename;
string path;//保存路径或要发送的文件的路径
int size = 0;
int modifyTime = 0;
int fileType = 0;

public:
static unique_ptr<FileContent> createFileContentToSend(const string& filePath)
{
static UniqueId mFileId;
struct stat fInfo;
auto ret = stat(filePath.c_str(), &fInfo);
if (ret != 0)
return nullptr;

unique_ptr<FileContent> file(new FileContent());
file->fileId = mFileId.get();
file->path = filePath;
file->filename = getFileNameFromPath(filePath);
if (S_ISREG(fInfo.st_mode))
file->fileType = IPMSG_FILE_REGULAR;
else if (S_ISREG(fInfo.st_mode))
file->fileType = IPMSG_FILE_DIR;
else
return nullptr;//先不支持其他类型
file->size = fInfo.st_size;
file->modifyTime = fInfo.st_mtimespec.tv_sec;

return file;
}
};

class KnockContent: public Content
{
public:
KnockContent(){mType = ContentType::Knock;}
};

class ImageContent: public Content
{
public:
ImageContent(){mType = ContentType::Image;}
string id;
};

class ContentParcelFactory
{
public:
static unique_ptr<Content> createFromParcel(Parcel& in)
{
//先读取父类信息
Content content;
auto pos = in.mark();
content.readFrom(in);
in.unmark(pos);

//根据类型读取剩余数据
Content* ptr = nullptr;
switch (content.type()) {
case ContentType::Text:
ptr = new TextContent;
break;
case ContentType::Knock:
ptr = new KnockContent;
break;
case ContentType::File:
ptr = new FileContent;
break;
case ContentType::Image:
ptr = new ImageContent;
break;
case ContentType::Id:
ptr = new IdContent;
break;
default:
break;
}

if (ptr)
ptr->readFrom(in);

return unique_ptr<Content>(ptr);
}
};

#endif // CONTENT_H

+ 13
- 0
feiqlib/defer.cpp View File

@@ -0,0 +1,13 @@
#include "defer.h"

Defer::Defer(function<void ()> deleter)
:mDeleter(deleter)
{

}

Defer::~Defer()
{
if (mDeleter)
mDeleter();
}

+ 16
- 0
feiqlib/defer.h View File

@@ -0,0 +1,16 @@
#ifndef DEFER_H
#define DEFER_H

#include <functional>
using namespace std;

class Defer
{
public:
Defer(function<void ()> deleter);
~Defer();
private:
function<void ()> mDeleter;
};

#endif // DEFER_H

+ 73
- 0
feiqlib/encoding.cpp View File

@@ -0,0 +1,73 @@
#include "encoding.h"
#include <iconv.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

Encoding* encIn = new Encoding("GBK", "UTF-8");
Encoding* encOut = new Encoding("UTF-8", "GBK");

Encoding::Encoding(const string &fromCharset, const string &toCharset)
{
//iconv_open is a freek(to goes first)
mIconv = iconv_open(toCharset.c_str(), fromCharset.c_str());
}

Encoding::~Encoding()
{
if (mIconv != (iconv_t)-1)
iconv_close(mIconv);
}

vector<char> Encoding::convert(const vector<char> &str)
{
vector<char> result(str.size()*3);
auto len = result.size();
if (convert(str.data(), str.size(), result.data(), &len))
{
result.resize(len);
result.shrink_to_fit();
return result;
}

return str;
}

string Encoding::convert(const string &str)
{
auto len = str.length()*3;
unique_ptr<char[]> buf(new char[len]);
if (convert(str.data(), str.size(), buf.get(), &len))
{
string result = buf.get();
return result;
}

return str;
}

bool Encoding::convert(const char *input, size_t len, char *output, size_t *outLen)
{
if (mIconv == (iconv_t)-1)
return false;

//copy in str
size_t inLen = len;
auto inBuf = unique_ptr<char[]>(new char[inLen+1]);
char* pIn = inBuf.get();
memcpy(pIn, input, inLen);
pIn[inLen]=0;

//do convert
int ret = iconv(mIconv, &pIn, &inLen, &output, outLen);
if (ret == -1)
{
perror("convert failed");
return false;
}

*output = 0;

return true;
}

+ 34
- 0
feiqlib/encoding.h View File

@@ -0,0 +1,34 @@
#ifndef ENCODING_H
#define ENCODING_H

#include <vector>
#include <string>
using namespace std;
#include <iconv.h>

class Encoding
{
public:
Encoding(const string& fromCharset, const string& toCharset);
~Encoding();
vector<char> convert(const vector<char>& str);
string convert(const string& str);

/**
* @brief convert 编码转换
* @param input 源内存首地址
* @param len 源长度
* @param output 目标内存首地址
* @param outLen 输入输出参数,输入目标缓冲区大小,输出实际使用大小
* @return
*/
bool convert(const char* input, size_t len, char* output, size_t* outLen);
private:
iconv_t mIconv;
};

extern Encoding* encOut;
extern Encoding* encIn;


#endif // ENCODING_H

+ 289
- 0
feiqlib/feiqcommu.cpp View File

@@ -0,0 +1,289 @@
#include "feiqcommu.h"
#include "udpcommu.h"
#include "ipmsg.h"
#include <arpa/inet.h>
#include "fellow.h"
#include <sstream>
#include <QDebug>
#include <limits.h>
#include "utils.h"

FeiqCommu::FeiqCommu()
{
}

void FeiqCommu::setMyHost(string host){mHost=host;}

void FeiqCommu::setMyName(string name){
mName=name;
std::replace(mName.begin(), mName.end(), HLIST_ENTRY_SEPARATOR, (char)HOSTLIST_DUMMY);
}

void FeiqCommu::addRecvProtocol(RecvProtocol *protocol){
mRecvPrtocols.push_back(protocol);
}

pair<bool, string> FeiqCommu::start()
{
if (!mUdp.bindTo(IPMSG_PORT))
{
return {false, "bind failed:"+mUdp.getErrMsg()};
}

if (!mUdp.startAsyncRecv(
std::bind(&FeiqCommu::onRecv, this, placeholders::_1,placeholders::_2)
)
)
{
mUdp.close();
return {false, "start aysnc recv failed:"+mUdp.getErrMsg()};
}

if (!mTcpServer.start(IPMSG_PORT)){
mUdp.close();
return {false, "无法启动文件服务"};
}
mTcpServer.whenNewClient(std::bind(&FeiqCommu::onTcpClientConnected, this, placeholders::_1));

//其他字段是什么意思呢?
mMac = mUdp.getBoundMac();
mVersion = "1_lbt6_0#128#"+mMac+"#0#0#0#4001#9";
return {true, ""};
}

void FeiqCommu::stop()
{
mUdp.close();
}

pair<IdType, string> FeiqCommu::send(const string &ip, SendProtocol &sender)
{
//打包
IdType packetNo=0;
auto out = pack(sender, &packetNo);

//发送
auto ret = mUdp.sentTo(ip, IPMSG_PORT, out.data(), out.size());
if (ret < 0)
return {0, mUdp.getErrMsg()};

return {packetNo, ""};
}

class SendRequestFile : public SendProtocol
{
public:
int filetype=IPMSG_FILE_REGULAR;
int fileid;
int offset=0;
int packetNo;

int cmdId() override {return filetype == IPMSG_FILE_DIR ? IPMSG_GETDIRFILES : IPMSG_GETFILEDATA;}
void write(ostream& os) override
{
char sep = HLIST_ENTRY_SEPARATOR;
os<<std::hex<<packetNo<<sep
<<fileid<<sep
<<offset<<sep;
}
};

unique_ptr<TcpSocket> FeiqCommu::requestFileData(const string &ip,
const FileContent& file, int offset)
{
unique_ptr<TcpSocket> client(new TcpSocket());
if (!client->connect(ip, IPMSG_PORT))
return nullptr;

SendRequestFile requestSender;
requestSender.packetNo = file.packetNo;
requestSender.fileid = file.fileId;
requestSender.offset = offset;
auto request = pack(requestSender);

int ret = client->send(request.data(), request.size());
if (ret < 0)
return nullptr;

return client;
}

void FeiqCommu::setFileServerHandler(FileServerHandler fileServerHandler)
{
mFileServerHandler = fileServerHandler;
}

void FeiqCommu::onRecv(const string &ip, vector<char> &data)
{
auto post = make_shared<Post>();
post->from->setIp(ip);

//解析
if (!dumpRaw(data, *post))
return;

//尝试获取mac
auto info = dumpVersionInfo(post->from->version());
post->from->setMac(info.mac);

//屏蔽自己的包
if (mMac == post->from->getMac()//匹配mac
&& mName == post->from->getName())//再匹配名字,以防mac获取失败
{
return;
}

//除非收到下线包,否则都认为在线
post->from->setOnLine(true);

//调用协议处理
for (auto& handler : mRecvPrtocols)
{
if (handler->read(post))
break;
}
}

vector<char> FeiqCommu::pack(SendProtocol &sender, IdType* packetId)
{
//搜集数据
char sep = HLIST_ENTRY_SEPARATOR;
auto packetNo = mPacketNo.get();
auto cmdId = sender.cmdId();

//拼接消息头
stringstream os;
os<<mVersion<<sep<<packetNo<<sep<<mName<<sep<<mHost<<sep<<cmdId<<sep;

//组装消息
sender.write(os);
os.put(0);

os.seekg(0, os.end);
auto len = os.tellg();
os.seekg(0, os.beg);

vector<char> buf(len);
os.read(buf.data(), len);

if (packetId != nullptr)
*packetId = packetNo;
return buf;
}

void FeiqCommu::onTcpClientConnected(int socket)
{
if (mFileServerHandler)
{
//接收请求
unique_ptr<TcpSocket> client(new TcpSocket(socket));
std::array<char,MAX_RCV_SIZE> buf;
int ret = client->recv(buf.data(), MAX_RCV_SIZE);
if (ret <= 0)
return;

//解析请求
vector<char> request(ret);
std::copy(buf.begin(), buf.begin()+ret, request.begin());

Post post;
if (!dumpRaw(request, post))
return;

auto values = splitAllowSeperator(post.extra.begin(), post.extra.end(), HLIST_ENTRY_SEPARATOR);
if (values.size() < 3)
return;

int packetNo = stoi(values[0], 0, 16);
int fileId = stoi(values[1], 0, 16);
int offset = stoi(values[2], 0, 16);

//处理请求
mFileServerHandler(std::move(client), packetNo, fileId, offset);
}
}

bool FeiqCommu::dumpRaw(vector<char>& data, Post& post)
{
auto ptr = data.begin();
auto last = data.end();

//取出协议的前5项
array<string, 5> value;
auto count = 0uL;
while (count < value.size()) {
auto found = std::find(ptr, last, HLIST_ENTRY_SEPARATOR);
if (found == last)
break;

auto size = std::distance(ptr, found);
if (size == 0)
{
value[count]="";
}
else
{
vector<char> buf(size+1);
std::copy(ptr, found, buf.begin());
buf.push_back(0);
std::replace(buf.begin(), buf.end(), HOSTLIST_DUMMY, HLIST_ENTRY_SEPARATOR);
value[count]=toString(buf);//TODO:是否有编码问题?
}

ptr=found+1;
++count;
}

//协议错误
if (count < value.size())
return false;

//解析
post.from->setVersion(value[0]);
post.packetNo = value[1];
if (!post.from)
post.from=make_shared<Fellow>();
post.from->setPcName(value[2]);
post.from->setHost(value[3]);
post.cmdId = stoull(value[4]);

//取出extra部分
if (ptr != last)
{
auto size = std::distance(ptr, last);
vector<char> buf(size);
std::copy(ptr, last, buf.begin());
post.extra = buf;
}

return true;
}

VersionInfo FeiqCommu::dumpVersionInfo(const string &version)
{
const char sep = '#';
VersionInfo info;

auto tail = version.end();
auto head = version.begin();
//1
auto begin = std::find(head, tail, sep);
if (begin == tail)
return info;

//2=begin
begin = std::find(begin+1, tail, sep);
if (begin == tail)
return info;
++begin;

//3=end
auto end = std::find(begin, tail, sep);
if (end == tail)
return info;

info.mac = version.substr(distance(head,begin), distance(begin,end));

return info;
}


+ 86
- 0
feiqlib/feiqcommu.h View File

@@ -0,0 +1,86 @@
#ifndef FEIQCOMMU_H
#define FEIQCOMMU_H

#include <string>
#include <tuple>
#include <vector>
#include <memory>
#include <array>
#include "post.h"
#include "protocol.h"
#include "udpcommu.h"
#include <atomic>
#include "encoding.h"
#include "tcpsocket.h"
#include "tcpserver.h"
#include "uniqueid.h"
using namespace std;

struct VersionInfo
{
string mac;
};

/**
* @brief 提供基础的feiq通信功能,对udp、tcp封装,负责消息的打包、解包
*/
class FeiqCommu
{
public:
typedef function<void (unique_ptr<TcpSocket>, int packetNo, int fileId, int offset)> FileServerHandler;
FeiqCommu();

public:
void setMyHost(string host);
void setMyName(string name);
void addRecvProtocol(RecvProtocol* protocol);

public:
/**
* @brief start 启动feiq通信
* @return 是否启动成功,如果失败,返回具体失败原因
*/
pair<bool, string> start();
void stop();

/**
* @brief send 发送sender打包的内容
* @param ip 发给谁
* @param sender 要发送什么
* @return 发送成功,返回发送包ID,否则返回-1,并设置失败原因
*/
pair<IdType, string> send(const string& ip, SendProtocol& sender);

/**
* @brief requestFileData 请求好友开始发送文件数据
* @param ip 向谁请求
* @param file 要请求的文件
* @return 如果请求成功,返回tcp连接,据此获取数据,否则返回nullptr
*/
unique_ptr<TcpSocket> requestFileData(const string& ip, const FileContent &file, int offset);

/**
* @brief setFileServerHandler 设置文件服务的处理
* @param fileServerHandler 参数:客户端socket连接,请求的文件id,请求的数据偏移
*/
void setFileServerHandler(FileServerHandler fileServerHandler);
public:
static bool dumpRaw(vector<char> &data, Post &post);
static VersionInfo dumpVersionInfo(const string& version);
private:
void onRecv(const string& ip, vector<char> &data);
vector<char> pack(SendProtocol& sender, IdType *packetId = nullptr);
void onTcpClientConnected(int socket);
private:
vector<RecvProtocol*> mRecvPrtocols;
UdpCommu mUdp;
string mHost="";
string mName="";
string mVersion="";
UniqueId mPacketNo;
string mMac;
TcpServer mTcpServer;
FileServerHandler mFileServerHandler;
};

#endif // FEIQCOMMU_H

+ 934
- 0
feiqlib/feiqengine.cpp View File

@@ -0,0 +1,934 @@
#include "feiqengine.h"
#include "protocol.h"
#include "ipmsg.h"
#include <memory>
#include "utils.h"
#include <fstream>
#include "defer.h"
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <iomanip>

class ContentSender : public SendProtocol
{
public:
void setContent(const Content* content)
{
mContent = content;
}

protected:
const Content* mContent;
};

class SendTextContent : public ContentSender
{
public:
int cmdId() override{return IPMSG_SENDMSG|IPMSG_SENDCHECKOPT;}
void write(ostream& os) override
{
auto content = static_cast<const TextContent*>(mContent);
if (content->format.empty())
{
os<<encOut->convert(content->text);
}
else
{
os<<encOut->convert(content->text)
<<"{"
<<encOut->convert(content->format)
<<"}";
}
}
};

class SendKnockContent : public ContentSender
{
public:
int cmdId() override{return IPMSG_KNOCK;}
void write(ostream &os) override {(void)os;}
};

class SendFileContent : public ContentSender
{
public:
int cmdId() override {return IPMSG_SENDMSG|IPMSG_FILEATTACHOPT;}
void write(ostream& os) override
{
auto content = static_cast<const FileContent*>(mContent);
char sep = HLIST_ENTRY_SEPARATOR;
auto filename = content->filename;
stringReplace(filename, ":", "::");//估摸着协议不会变,偷懒下
os<<(char)0
<<to_string(content->fileId)
<<sep
<<encOut->convert(filename)
<<sep
<<std::hex<<content->size
<<sep
<<content->modifyTime
<<sep
<<content->fileType;
}
};

class SendImOnLine : public SendProtocol
{
public:
SendImOnLine(const string& name):mName(name){}
int cmdId() override{return IPMSG_BR_ENTRY;}
void write(ostream &os) override
{
os<<encOut->convert(mName);
}

private:
string mName;
};

class SendImOffLine : public SendProtocol
{
public:
SendImOffLine(const string& name):mName(name){}
int cmdId() override {return IPMSG_BR_EXIT;}
void write(ostream &os) override
{
os<<encOut->convert(mName);
}
private:
string mName;
};

/**
* @brief The AnsSendCheck class 发送消息我收到了
*/
class SendSentCheck : public SendProtocol
{
public:
SendSentCheck(const string& packetNo)
:mPacketNo(packetNo){}

int cmdId() override{return IPMSG_RECVMSG;}

void write(ostream& os) override
{
os<<mPacketNo;
}
private:
string mPacketNo;
};

/**
* @brief The SendReadCheck class 发送消息我已经读了
*/
class SendReadCheck : public SendProtocol
{
public:
SendReadCheck(const string& packetNo)
:mPacketNo(packetNo){}
public:
int cmdId() override {return IPMSG_READMSG;}
void write(ostream& os) override
{
os<<mPacketNo;
}
private:
string mPacketNo;
};

/**
* @brief The AnsBrEntry class 回复好友上线包
*/
class AnsBrEntry : public SendProtocol
{
public:
AnsBrEntry(const string& myName):mName(myName){}
public:
int cmdId() override { return IPMSG_ANSENTRY;}
void write(ostream &os) override {
os<<encOut->convert(mName);
}
private:
const string& mName;
};

//定义触发器
typedef std::function<void (shared_ptr<Post> post)> OnPostReady;
#define DECLARE_TRIGGER(name)\
public:\
name(OnPostReady trigger) : mTrigger(trigger){}\
private:\
OnPostReady mTrigger;\
void trigger(shared_ptr<Post> post){mTrigger(post);}

/**
* @brief The RecvAnsEntry class 好友响应我们的上线消息
*/
class RecvAnsEntry : public RecvProtocol
{
DECLARE_TRIGGER(RecvAnsEntry)
public:
bool read(shared_ptr<Post> post)
{
if (IS_CMD_SET(post->cmdId, IPMSG_ANSENTRY))
{
auto converted = toString(encIn->convert(post->extra));
post->from->setName(converted);
trigger(post);
return true;
}
return false;
}
};
/**
* @brief The RecvBrEntry class 好友上线
*/
class RecvBrEntry : public RecvProtocol
{
DECLARE_TRIGGER(RecvBrEntry)
public:
bool read(shared_ptr<Post> post)
{
if (IS_CMD_SET(post->cmdId, IPMSG_BR_ENTRY))
{
post->from->setName(toString(encIn->convert(post->extra)));
trigger(post);
return true;
}
return false;
}
};
/**
* @brief The RecvBrExit class 好友下线
*/
class RecvBrExit : public RecvProtocol
{
DECLARE_TRIGGER(RecvBrExit)
public:
bool read(shared_ptr<Post> post)
{
if (IS_CMD_SET(post->cmdId, IPMSG_BR_EXIT))
{
post->from->setOnLine(false);
trigger(post);
return true;
}
return false;
}
};
/**
* @brief The RecvKnock class 窗口抖动
*/
class RecvKnock : public RecvProtocol
{
public:
bool read(shared_ptr<Post> post)
{
if (IS_CMD_SET(post->cmdId, IPMSG_KNOCK))
{
post->contents.push_back(make_shared<KnockContent>());
}
return false;
}
};
/**
* @brief The AnsSendCheck class
*/
class RecvSendCheck : public RecvProtocol
{
DECLARE_TRIGGER(RecvSendCheck)
public:
bool read(shared_ptr<Post> post)
{
if (IS_OPT_SET(post->cmdId, IPMSG_SENDCHECKOPT))
trigger(post);
return false;
}
};

/**
* @brief The RecvReadCheck class 接收到请求阅后通知
*/
class RecvReadCheck : public RecvProtocol
{
DECLARE_TRIGGER(RecvReadCheck)
public:
bool read(shared_ptr<Post> post)
{
if (IS_OPT_SET(post->cmdId, IPMSG_READCHECKOPT))
trigger(post);
return false;
}
};

/**
* @brief The RecvText class 接收文本消息
*/
class RecvText : public RecvProtocol
{
public:
bool read(shared_ptr<Post> post)
{
if (!IS_CMD_SET(post->cmdId, IPMSG_SENDMSG))
return false;

auto& extra = post->extra;

auto end = extra.end();
auto begin = extra.begin();
auto found = std::find(begin, end, 0);
if (found != begin)//有找到0,且不是第一个字符
{
string rawText;
rawText.assign(begin, found);

auto content = createTextContent(encIn->convert(rawText));
post->contents.push_back(shared_ptr<Content>(std::move(content)));
}

return false;
}
private:
unique_ptr<TextContent> createTextContent(const string& raw)
{
auto content = unique_ptr<TextContent>(new TextContent());
auto begin = raw.find('{');
auto end = raw.find("}", begin+1);

if (begin != raw.npos && end != raw.npos)
{
content->text = raw.substr(0, begin);
content->format = raw.substr(begin+1, end-begin-1);
}
else
{
content->text = raw;
}
return content;
}
};

class RecvFile : public RecvProtocol
{
public:
bool read(shared_ptr<Post> post)
{
if (!IS_OPT_SET(post->cmdId, IPMSG_FILEATTACHOPT) || !IS_CMD_SET(post->cmdId, IPMSG_SENDMSG))
return false;

//文件任务信息紧随文本消息之后,中间相隔一个ascii 0
//一个文件任务信息格式为fileId:filename:fileSize:modifyTime:fileType:其他扩展属性
//多个文件任务以ascii 7分割
//文件名含:,以::表示
auto& extra = post->extra;
auto end = extra.end();
auto found = find(extra.begin(), end, 0)+1;

while (found != end)
{
auto endTask = find(found, end, FILELIST_SEPARATOR);
if (endTask == end)
break;

auto content = createFileContent(found, endTask);
content->packetNo = stoul(post->packetNo);
if (content != nullptr)
post->contents.push_back(shared_ptr<Content>(std::move(content)));

found = ++endTask;
}

return false;
}
private:
unique_ptr<FileContent> createFileContent(vector<char>::iterator from,
vector<char>::iterator to)
{
unique_ptr<FileContent> content(new FileContent());

auto values = splitAllowSeperator(from, to, HLIST_ENTRY_SEPARATOR);
const int fieldCount = 5;
if (values.size() < fieldCount)
return nullptr;

content->fileId = stoi(values[0]);
content->filename = encIn->convert(values[1]);
content->size = stoi(values[2],0,16);
content->modifyTime = stoi(values[3],0,16);
content->fileType = stoi(values[4],0,16);

return content;
}
};

class Debuger : public RecvProtocol
{
public:
bool read(shared_ptr<Post> post)
{
cout<<"==========================="<<endl;
cout<<"cmd id : "<<std::hex<<post->cmdId<<endl;
cout<<"from: "<<post->from->toString()<<endl;
int count = 0;

for (unsigned char ch : post->extra){
cout<<setw(2)<<setfill('0')<<hex<<(unsigned int)ch<<" ";
if (++count >= 8){
cout<<endl;
count=0;
}
}
cout<<endl;
return false;
}
};

class RecvReadMessage : public RecvProtocol
{
DECLARE_TRIGGER(RecvReadMessage)
public:
bool read(shared_ptr<Post> post)
{
if (post->cmdId == IPMSG_RECVMSG)
{
IdType id = static_cast<IdType>(stoll(toString(post->extra)));
auto content = make_shared<IdContent>();
content->id = id;
post->addContent(content);
trigger(post);
return true;
}
return false;
}
};

class RecvImage : public RecvProtocol
{
public:
bool read(shared_ptr<Post> post)
{
if (IS_CMD_SET(post->cmdId, IPMSG_SENDIMAGE)
&& IS_OPT_SET(post->cmdId, IPMSG_FILEATTACHOPT))
{
char id[9]={0};
memcpy(id, post->extra.data(), 8);
auto content = make_shared<ImageContent>();
content->id = id;
post->contents.push_back(content);
}
return false;
}
};

/**
* @brief The EndRecv class 终止解析链
*/
class EndRecv : public RecvProtocol
{
DECLARE_TRIGGER(EndRecv)
public:
bool read(shared_ptr<Post> post)
{
if (!post->contents.empty())
trigger(post);
return true;
}
};

//添加一条接收协议,触发时更新好友信息,并调用func
#define ADD_RECV_PROTOCOL(protocol, func)\
{\
RecvProtocol* p = new protocol([this](shared_ptr<Post> post){\
post->from = this->addOrUpdateFellow(post->from);\
this->func(post);});\
mRecvProtocols.push_back(unique_ptr<RecvProtocol>(p));\
mCommu.addRecvProtocol(p);\
}

//添加一条接收协议,无触发
#define ADD_RECV_PROTOCOL2(protocol)\
{\
RecvProtocol* p = new protocol();\
mRecvProtocols.push_back(unique_ptr<RecvProtocol>(p));\
mCommu.addRecvProtocol(p);\
}

//添加一条接收协议,触发时更新好友信息
#define ADD_RECV_PROTOCOL3(protocol)\
{\
RecvProtocol* p = new protocol([this](shared_ptr<Post> post){\
post->from = this->addOrUpdateFellow(post->from);});\
mRecvProtocols.push_back(unique_ptr<RecvProtocol>(p));\
mCommu.addRecvProtocol(p);\
}

//添加一条发送协议
#define ADD_SEND_PROTOCOL(protocol, sender, args...)\
{\
mContentSender[protocol]=make_shared<sender>(##args);\
}

FeiqEngine::FeiqEngine()
{
ADD_RECV_PROTOCOL2(Debuger);//仅用于开发中的调试

ADD_RECV_PROTOCOL3(RecvAnsEntry);
ADD_RECV_PROTOCOL(RecvBrEntry, onBrEntry);
ADD_RECV_PROTOCOL3(RecvBrExit);
ADD_RECV_PROTOCOL(RecvSendCheck, onSendCheck);
ADD_RECV_PROTOCOL(RecvReadCheck, onReadCheck);
ADD_RECV_PROTOCOL(RecvReadMessage, onReadMessage);//好友回复消息已经阅读
ADD_RECV_PROTOCOL2(RecvText);
ADD_RECV_PROTOCOL2(RecvImage);
ADD_RECV_PROTOCOL2(RecvKnock);
ADD_RECV_PROTOCOL2(RecvFile);
ADD_RECV_PROTOCOL(EndRecv, onMsg);

ADD_SEND_PROTOCOL(ContentType::Text, SendTextContent);
ADD_SEND_PROTOCOL(ContentType::Knock, SendKnockContent);
ADD_SEND_PROTOCOL(ContentType::File, SendFileContent);

mCommu.setFileServerHandler(std::bind(&FeiqEngine::fileServerHandler,
this,
placeholders::_1,
placeholders::_2,
placeholders::_3,
placeholders::_4));
}

pair<bool, string> FeiqEngine::send(shared_ptr<Fellow> fellow, shared_ptr<Content> content)
{
if (content == nullptr)
return {false, "要发送的内容无效"};

auto& sender = mContentSender[content->type()];
if (sender == nullptr)
return {false, "no send protocol can send"};

sender->setContent(content.get());
auto ip = fellow->getIp();
auto ret = mCommu.send(ip, *sender);
if (ret.first == 0)
{
return {false, ret.second};
}

content->setPacketNo(ret.first);

if (content->type() == ContentType::File){
auto ptr = dynamic_pointer_cast<FileContent>(content);
mModel.addUploadTask(fellow, ptr)->setObserver(mView);
}
else if (content->type() == ContentType::Text){
auto handler = std::bind(&FeiqEngine::onSendTimeo, this, placeholders::_1, ip, content);
mAsyncWait.addWaitPack(content->packetNo, handler, 5000);
}
return {true, ""};
}

pair<bool, string> FeiqEngine::sendFiles(shared_ptr<Fellow> fellow, list<shared_ptr<FileContent>> &files)
{
for (auto file : files) {
auto ret = send(fellow, file);
if (!ret.first)
return ret;
}
return {true,""};
}

bool FeiqEngine::downloadFile(FileTask* task)
{
if (task==nullptr)
return false;

task->setObserver(mView);

auto func = [task, this](){
auto fellow = task->fellow();
auto content = task->getContent();

auto client = mCommu.requestFileData(fellow->getIp(), *content, 0);
if (client == nullptr)
{
task->setState(FileTaskState::Error, "请求下载文件失败,可能好友已经取消");
return;
}

FILE* of = fopen(content->path.c_str(), "w+");
if (of == nullptr){
task->setState(FileTaskState::Error, "无法打开文件进行保存");
return;
}

// Defer{//TODO:工作异常
// [of](){
// cout<<"close file now"<<endl;
// fclose(of);
// }
// };

const int unitSize = 2048;//一次请求2k
const int maxTimeoCnt = 3;//最多允许超时3次
const int timeo = 2000;//允许超时2s

int recv = 0;
auto total = content->size;
std::array<char, unitSize> buf;
int timeoCnt = 0;
task->setState(FileTaskState::Running);
while (recv < total)
{
if (task->hasCancelPending())
{
task->setState(FileTaskState::Canceled);
fclose(of);
return;
}

auto left = total - recv;
auto request = unitSize > left ? left : unitSize;
auto got = client->recv(buf.data(), request, timeo);
if (got == -1 && ++timeoCnt >= maxTimeoCnt)
{
task->setState(FileTaskState::Error, "下载文件超时,好友可能掉线");
fclose(of);
return;
}
else if (got < 0)
{
task->setState(FileTaskState::Error, "接收数据出错,可能网络错误");
fclose(of);
return;
}
else
{
fwrite(buf.data(), 1, got, of);
recv+=got;
task->setProcess(recv);
}
}

fclose(of);
task->setState(FileTaskState::Finish);
};

thread thd(func);
thd.detach();

return task;
}

class GetPubKey : public SendProtocol
{
public:
int cmdId() {return IPMSG_GETPUBKEY;}
void write(ostream& os){
(void)os;
}
};

pair<bool, string> FeiqEngine::start()
{
if (mStarted)
{
return {true, "已经启动过"};
}

mCommu.setMyHost(encOut->convert(mHost));
mCommu.setMyName(encOut->convert(mName));
auto result = mCommu.start();

if(result.first)
{
mAsyncWait.start();

mMsgThd.start();
mMsgThd.setHandler(std::bind(&FeiqEngine::dispatchMsg, this, placeholders::_1));

mStarted = true;
sendImOnLine();
}

return result;
}

void FeiqEngine::stop()
{
if (mStarted)
{
mStarted=false;
SendImOffLine imOffLine(mName);
mCommu.send("255.255.255.255", imOffLine);
broadcastToCurstomGroup(imOffLine);
mCommu.stop();
mAsyncWait.stop();
mMsgThd.stop();
}
}

void FeiqEngine::addToBroadcast(const string &ip)
{
mBroadcast.push_back(ip);
}

void FeiqEngine::setMyHost(string host)
{
mHost=host;
if (mName.empty())
mName = mHost;
}

void FeiqEngine::setMyName(string name){
mName=name;
if (mName.empty())
mName = mHost;
}

void FeiqEngine::sendImOnLine(const string &ip)
{
SendImOnLine imOnLine(mName);

if (ip.empty())
{
mCommu.send("255.255.255.255", imOnLine);
broadcastToCurstomGroup(imOnLine);
}
else
{
mCommu.send(ip, imOnLine);
}
}

void FeiqEngine::enableIntervalDetect(int seconds)
{
thread thd([this, seconds](){
while(mStarted)
{
sleep(seconds);
if (!mStarted) break;

SendImOnLine imOnLine(mName);
broadcastToCurstomGroup(imOnLine);
}
});
thd.detach();
}


FeiqModel &FeiqEngine::getModel()
{
return mModel;
}

void FeiqEngine::onBrEntry(shared_ptr<Post> post)
{
AnsBrEntry ans(mName);
mCommu.send(post->from->getIp(), ans);
}

void FeiqEngine::onMsg(shared_ptr<Post> post)
{
static vector<string> rejectedImages;

auto event = make_shared<MessageViewEvent>();
event->when = post->when;
event->fellow = post->from;

auto it = post->contents.begin();
auto end = post->contents.end();

string reply;
while (it != end)//过滤消息内容:删除不支持的包,并回复好友
{
bool rejected = false;
if ((*it)->type() == ContentType::File)
{
auto fc = static_pointer_cast<FileContent>(*it);

if (fc->fileType == IPMSG_FILE_REGULAR)//TODO:与飞秋的文件夹传输协议还没支持
mModel.addDownloadTask(event->fellow, fc);
else if (fc->fileType == IPMSG_FILE_DIR)
{
rejected=true;
reply+="Mac飞秋还不支持接收目录:"+fc->filename+"\n";
}
}
else if ((*it)->type() == ContentType::Text)
{
auto tc = static_cast<TextContent*>((*it).get());
string begin = "/~#>";
string end = "<B~";
if (startsWith(tc->text, begin) && endsWith(tc->text, end))
{
rejected=true;
}
}
else if ((*it)->type() == ContentType::Image)
{
//这个包还没被拒绝过,发送拒绝消息
auto ic = static_cast<ImageContent*>((*it).get());
if (std::find(rejectedImages.begin(), rejectedImages.end(), ic->id)==rejectedImages.end())
{
reply+="Mac飞秋还不支持接收图片,请用文件形式发送图片\n";
rejectedImages.push_back(ic->id);
}
rejected=true;
}

if (!rejected)
{
event->contents.push_back(*it);
}
++it;
}

if (!reply.empty())
{
SendTextContent send;
TextContent content;
content.text = reply;
send.setContent(&content);
mCommu.send(post->from->getIp(), send);
}

if (!event->contents.empty())
mMsgThd.sendMessage(event);
}

void FeiqEngine::onSendCheck(shared_ptr<Post> post)
{
SendSentCheck reply(post->packetNo);
mCommu.send(post->from->getIp(), reply);
}

void FeiqEngine::onReadCheck(shared_ptr<Post> post)
{
SendReadCheck reply(post->packetNo);
mCommu.send(post->from->getIp(), reply);
}

void FeiqEngine::onSendTimeo(IdType packetId, const string& ip, shared_ptr<Content> content)
{
auto event = make_shared<SendTimeoEvent>();
event->fellow = mModel.findFirstFellowOf(ip);
if (event->fellow == nullptr)
return;

event->content = content;
mMsgThd.sendMessage(event);
}

void FeiqEngine::onReadMessage(shared_ptr<Post> post)
{
if (post->contents.empty())
return;
auto content = dynamic_pointer_cast<IdContent>(post->contents[0]);
mAsyncWait.clearWaitPack(content->id);
}

void FeiqEngine::fileServerHandler(unique_ptr<TcpSocket> client, int packetNo, int fileId, int offset)
{
auto task = mModel.findTask(packetNo, fileId);
if (task == nullptr)
return;

auto func = [task, offset](unique_ptr<TcpSocket> client){
FILE* is = fopen(task->getContent()->path.c_str(), "r");
if (is == nullptr)
{
task->setState(FileTaskState::Error, "无法读取文件");
}

// Defer{
// [is](){
// fclose(is);
// }
// };

if (offset > 0)
fseek(is, offset, SEEK_SET);

const int unitSize = 2048;//一次发送2k
std::array<char, unitSize> buf;
auto total = task->getContent()->size;
int sent = 0;

task->setState(FileTaskState::Running);
while (sent < total && !feof(is))
{
auto left = total - sent;
auto request = unitSize > left ? left : unitSize;
int got = fread(buf.data(), 1, request, is);
got = client->send(buf.data(), got);
if (got < 0)
{
task->setState(FileTaskState::Error, "无法发送数据,可能是网络问题");
fclose(is);
return;
}

sent+=got;
task->setProcess(sent);
}

if (sent != total)
{
task->setState(FileTaskState::Error, "文件未完整发送,可能是发送期间文件被改动");
}
else
{
task->setProcess(total);
task->setState(FileTaskState::Finish);
}

fclose(is);
};

thread thd(func, std::move(client));
thd.detach();
}

shared_ptr<Fellow> FeiqEngine::addOrUpdateFellow(shared_ptr<Fellow> fellow)
{
auto f = mModel.getFullInfoOf(fellow);
bool shouldApdate = false;

if (f == nullptr)
{
mModel.addFellow(fellow);
f = fellow;
shouldApdate = true;
}
else
{
if (f->update(*fellow))
shouldApdate = true;
}

if (shouldApdate){
auto event = make_shared<FellowViewEvent>();
event->what = ViewEventType::FELLOW_UPDATE;
event->fellow = f;
event->when = Post::now();
mMsgThd.sendMessage(event);
}

return f;
}

void FeiqEngine::dispatchMsg(shared_ptr<ViewEvent> msg)
{
mView->onEvent(msg);
}

void FeiqEngine::broadcastToCurstomGroup(SendProtocol &protocol)
{
for (auto ip : mBroadcast)
{
if (!mStarted)
break;//发送过程是一个耗时网络操作,如果已经stop,则中断

mCommu.send(ip, protocol);
}
}

+ 92
- 0
feiqlib/feiqengine.h View File

@@ -0,0 +1,92 @@
#ifndef FEIQENGINE_H
#define FEIQENGINE_H

#include "content.h"
#include "feiqcommu.h"
#include <string>
#include <tuple>
#include <list>
#include <unordered_map>
#include "feiqmodel.h"
#include "msgqueuethread.h"
#include "ifeiqview.h"
#include "asynwait.h"
using namespace std;

class Post;
class ContentSender;

/**
* @brief The FeiqEngine class
* feiq以mvc模式架构,FeiqEngine是control部分,负责逻辑控制(以及具体协议来往)
*/
class FeiqEngine
{
public:
FeiqEngine();

public:
pair<bool, string> send(shared_ptr<Fellow> fellow, shared_ptr<Content> content);
pair<bool, string> sendFiles(shared_ptr<Fellow> fellow, list<shared_ptr<FileContent> > &files);
bool downloadFile(FileTask* task);

public:
pair<bool, string> start();
void stop();
void addToBroadcast(const string& ip);
void setMyHost(string host);
void setMyName(string name);
void setView(IFeiqView* view){mView = view;}
void sendImOnLine(const string& ip = "");
/**
* @brief enableIntervalDetect 当接入路由,被禁止发送广播包时,
* 启用间隔检测可每隔一段时间发送一次上线通知到指定网段,以实现检测。
*/
void enableIntervalDetect(int seconds);

public:
FeiqModel &getModel();

private://trigers
void onAnsEntry(shared_ptr<Post> post);
void onBrEntry(shared_ptr<Post> post);
void onBrExit(shared_ptr<Post> post);
void onMsg(shared_ptr<Post> post);
void onSendCheck(shared_ptr<Post> post);
void onReadCheck(shared_ptr<Post> post);
void onSendTimeo(IdType packetId, const string &ip, shared_ptr<Content> content);
void onReadMessage(shared_ptr<Post> post);

private:
void fileServerHandler(unique_ptr<TcpSocket> client, int packetNo, int fileId, int offset);

private:
shared_ptr<Fellow> addOrUpdateFellow(shared_ptr<Fellow> fellow);
void dispatchMsg(shared_ptr<ViewEvent> msg);
void broadcastToCurstomGroup(SendProtocol& protocol);

private:
FeiqCommu mCommu;
vector<unique_ptr<RecvProtocol>> mRecvProtocols;
FeiqModel mModel;
string mHost;
string mName;
MsgQueueThread<ViewEvent> mMsgThd;
IFeiqView* mView;
vector<string> mBroadcast;
bool mStarted=false;
AsynWait mAsyncWait;//异步等待对方回包

struct EnumClassHash
{
template <typename T>
std::size_t operator()(T t) const
{
return static_cast<std::size_t>(t);
}
};
//可以用unique_ptr,但是unique_ptr要求知道具体定义
unordered_map<ContentType, shared_ptr<ContentSender>, EnumClassHash> mContentSender;
};

#endif // FEIQENGINE_H

+ 119
- 0
feiqlib/feiqmodel.cpp View File

@@ -0,0 +1,119 @@
#include "feiqmodel.h"
#include <functional>

FeiqModel::FeiqModel()
{

}

void FeiqModel::addFellow(shared_ptr<Fellow> fellow)
{
lock_guard<mutex> guard(mFellowLock);
mFellows.push_back(fellow);
}

shared_ptr<Fellow> FeiqModel::getFullInfoOf(shared_ptr<Fellow> fellow)
{
lock_guard<mutex> guard(mFellowLock);
auto predict = [&fellow](shared_ptr<Fellow> tmp){return fellow->isSame(*tmp);};
auto found = std::find_if(mFellows.begin(), mFellows.end(), predict);
return found == mFellows.end() ? nullptr : *found;
}

shared_ptr<Fellow> FeiqModel::findFirstFellowOf(const string &ip)
{
lock_guard<mutex> guard(mFellowLock);
auto predict = [&ip](shared_ptr<Fellow> tmp){return tmp->getIp() == ip;};
auto found = std::find_if(mFellows.begin(), mFellows.end(), predict);
return found == mFellows.end() ? nullptr : *found;
}

list<shared_ptr<Fellow> > FeiqModel::searchFellow(const string &text)
{
lock_guard<mutex> guard(mFellowLock);
list<shared_ptr<Fellow>> fellows;
if (text.empty())
{
fellows = mFellows;
}
else
{
for (shared_ptr<Fellow> fellow : mFellows)
{
if (fellow->getName().find(text) != string::npos
|| fellow->getHost().find(text) != string::npos
|| fellow->getIp().find(text) != string::npos)
fellows.push_back(fellow);
}
}

return fellows;
}

shared_ptr<Fellow> FeiqModel::getShared(const Fellow *fellow)
{
if (fellow == nullptr)
return nullptr;

lock_guard<mutex> guard(mFellowLock);
for (shared_ptr<Fellow> f : mFellows)
{
if (f.get() == fellow)
return f;
}

return nullptr;
}

shared_ptr<FileTask> FeiqModel::addDownloadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent)
{
lock_guard<mutex> guard(mFileTaskLock);
auto task = make_shared<FileTask>(fileContent, FileTaskType::Download);
task->setFellow(fellow);
mFileTasks.push_back(task);
return task;
}

shared_ptr<FileTask> FeiqModel::addUploadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent)
{
lock_guard<mutex> guard(mFileTaskLock);
auto task = make_shared<FileTask>(fileContent, FileTaskType::Upload);
task->setFellow(fellow);
mFileTasks.push_back(task);
return task;
}

void FeiqModel::removeFileTask(function<bool (const FileTask&)> predict)
{
lock_guard<mutex> g(mFileTaskLock);
mFileTasks.remove_if([predict](shared_ptr<FileTask> t){
return predict(*t);
});
}

shared_ptr<FileTask> FeiqModel::findTask(IdType packetNo, IdType fileId, FileTaskType type)
{
lock_guard<mutex> g(mFileTaskLock);
for (auto task : mFileTasks) {
if (task->type() != type)
continue;

auto content = task->getContent();
if (content->fileId == fileId && content->packetNo == packetNo)
return task;
}

return nullptr;
}

list<shared_ptr<FileTask> > FeiqModel::searchTask(function<bool (const FileTask &)> predict)
{
lock_guard<mutex> g(mFileTaskLock);
list<shared_ptr<FileTask>> allTask;

for (auto task : mFileTasks)
if (predict(*(task.get())))
allTask.push_back(task);

return allTask;
}

+ 38
- 0
feiqlib/feiqmodel.h View File

@@ -0,0 +1,38 @@
#ifndef FEIQMODEL_H
#define FEIQMODEL_H

#include "fellow.h"
#include <memory>
#include <list>
#include <mutex>
#include "filetask.h"
#include "uniqueid.h"
using namespace std;

class FeiqModel
{
public:
FeiqModel();

public:
void addFellow(shared_ptr<Fellow> fellow);
shared_ptr<Fellow> getFullInfoOf(shared_ptr<Fellow> fellow);
shared_ptr<Fellow> findFirstFellowOf(const string& ip);
list<shared_ptr<Fellow>> searchFellow(const string& text);
shared_ptr<Fellow> getShared(const Fellow* fellow);

public:
shared_ptr<FileTask> addDownloadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent);
shared_ptr<FileTask> addUploadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent);
shared_ptr<FileTask> findTask(IdType packetNo, IdType fileId, FileTaskType type = FileTaskType::Upload);
list<shared_ptr<FileTask>> searchTask(function<bool(const FileTask&)> predict);
void removeFileTask(function<bool (const FileTask&)> predict);

private:
list<shared_ptr<Fellow>> mFellows;
list<shared_ptr<FileTask>> mFileTasks;
mutex mFellowLock;
mutex mFileTaskLock;
};

#endif // FEIQMODEL_H

+ 104
- 0
feiqlib/fellow.h View File

@@ -0,0 +1,104 @@
#ifndef FELLOW_H
#define FELLOW_H

#include <string>
#include <memory>
#include <sstream>
using namespace std;

class Fellow
{
public:
string getIp() const{return mIp;}
string getName() const{return mName.empty() ? mPcName : mName;}
string getHost() const{return mHost;}
string getMac() const{return mMac;}
bool isOnLine() const{return mOnLine;}
string version() const{return mVersion;}

void setIp(const string& value){
mIp = value;
}

void setName(const string& value){
mName = value;
}

void setHost(const string& value){
mHost = value;
}

void setMac(const string& value){
mMac = value;
}

void setOnLine(bool value){
mOnLine = value;
}

void setVersion(const string& value){
mVersion = value;
}

void setPcName(const string& value){
mPcName = value;
}

bool update(const Fellow& fellow)
{
bool changed = false;

if (!fellow.mName.empty() && mName != fellow.mName){
mName = fellow.mName;
changed=true;
}

if (!fellow.mMac.empty() && mMac != fellow.mMac){
mMac = fellow.mMac;
changed=true;
}

if (mOnLine != fellow.mOnLine){
mOnLine = fellow.mOnLine;
changed=true;
}

return changed;
}

bool operator == (const Fellow& fellow)
{
return isSame(fellow);
}

bool isSame(const Fellow& fellow)
{
return mIp == fellow.mIp || (!mMac.empty() && mMac == fellow.mMac);
}

string toString() const
{
ostringstream os;
os<<"["
<<"ip="<<mIp
<<",name="<<mName
<<",host="<<mHost
<<",pcname="<<mPcName
<<",mac="<<mMac
<<",online="<<mOnLine
<<",version="<<mVersion
<<"]";
return os.str();
}

private:
string mIp;
string mPcName;
string mName;
string mHost;
string mMac;
bool mOnLine;
string mVersion;
};

#endif // FELLOW_H

+ 82
- 0
feiqlib/filetask.cpp View File

@@ -0,0 +1,82 @@
#include "filetask.h"

FileTask::FileTask()
{

}

FileTask::FileTask(shared_ptr<FileContent> fileContent, FileTaskType type)
:mContent(fileContent), mType(type)
{
mNotifySize = fileContent->size/100;//每1%通知一次
const int minNotifySize = 102400;//至少变化了100k才通知
if (mNotifySize < minNotifySize)
mNotifySize = minNotifySize;
}

void FileTask::setObserver(IFileTaskObserver *observer)
{
mObserver = observer;
}

void FileTask::setProcess(int val)
{
mProcess = val;
if (mProcess - mLastProcess >= mNotifySize)
{
mLastProcess = mProcess;
mObserver->onProgress(this);
}
}

void FileTask::setState(FileTaskState val, const string &msg)
{
mState = val;
mMsg = msg;
mObserver->onStateChanged(this);
}

void FileTask::setFellow(shared_ptr<Fellow> fellow)
{
mFellow = fellow;
}

void FileTask::cancel()
{
mCancelPending=true;
}

bool FileTask::hasCancelPending()
{
return mCancelPending;
}

shared_ptr<Fellow> FileTask::fellow() const
{
return mFellow;
}

int FileTask::getProcess() const
{
return mProcess;
}

FileTaskState FileTask::getState() const
{
return mState;
}

string FileTask::getDetailInfo() const
{
return mMsg;
}

shared_ptr<FileContent> FileTask::getContent() const
{
return mContent;
}

FileTaskType FileTask::type() const
{
return mType;
}

+ 64
- 0
feiqlib/filetask.h View File

@@ -0,0 +1,64 @@
#ifndef FILETASK_H
#define FILETASK_H


#include "content.h"
#include <memory>
#include <functional>
#include "fellow.h"
using namespace std;

enum class FileTaskType{
Download,
Upload
};

enum class FileTaskState{
NotStart,
Running,
Finish,
Error,
Canceled
};

class FileTask;
class IFileTaskObserver
{
public:
virtual void onStateChanged(FileTask* fileTask) = 0;
virtual void onProgress(FileTask* fileTask) = 0;
};

class FileTask
{
public:
FileTask();
FileTask(shared_ptr<FileContent> fileContent, FileTaskType type);
void setObserver(IFileTaskObserver* observer);
public:
void setProcess(int val);
void setState(FileTaskState val, const string& msg="");
void setFellow(shared_ptr<Fellow> fellow);
void cancel();
bool hasCancelPending();
public:
shared_ptr<Fellow> fellow() const;
int getProcess() const;
FileTaskState getState() const;
string getDetailInfo() const;
shared_ptr<FileContent> getContent() const;
FileTaskType type() const;
private:
shared_ptr<Fellow> mFellow;//要发送给的用户,或文件来自该用户
int mProcess=0;
FileTaskState mState = FileTaskState::NotStart;
shared_ptr<FileContent> mContent;
IFileTaskObserver* mObserver;
FileTaskType mType = FileTaskType::Upload;
string mMsg;
bool mCancelPending=false;
int mNotifySize;
int mLastProcess=0;
};

#endif // FILETASK_H

+ 199
- 0
feiqlib/history.cpp View File

@@ -0,0 +1,199 @@
#include "history.h"
#include <iostream>
#include "defer.h"
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "defer.h"

#define FELLOW_TABLE "fellow"
#define MESSAGE_TABLE "message"

#define CHECK_SQLITE_RET(ret, action, result)\
if (ret != SQLITE_OK)\
{\
cout<<"failed to"#action":"<<ret<<endl;\
return result;\
}

#define CHECK_SQLITE_RET2(ret, action)\
if (ret != SQLITE_OK)\
{\
cout<<"failed to"#action":"<<ret<<endl;\
return;\
}

History::History()
{

}

bool History::init(const string &dbPath)
{
unInit();

//检查数据库文件是否存在,若不存在,需要创建表
bool needCreateTable=false;
auto ret = access(dbPath.c_str(), F_OK);
if (ret == -1)
{
cout<<"db may be new created:"<<strerror(errno)<<endl;
needCreateTable=true;
}

//打开数据库
ret = sqlite3_open(dbPath.c_str(), &mDb);
bool success = false;
Defer closeDbIfErr{
[this, success]()
{
if (!success)
{
cerr<<"init failed, close db now"<<endl;
sqlite3_close(mDb);//除非内存不够,否则open总是会分配mDb的内存,总是需要close
mDb = nullptr;
}
}
};

CHECK_SQLITE_RET(ret, "open sqlite", false);

//创建表
if (needCreateTable)
{
string createFellowTable = "create table " FELLOW_TABLE "(id integer,ip text, name text, mac text, primary key(id));";
string createMessageTable = "create table " MESSAGE_TABLE " (id integer, fellow integer, when integer, content blob, primary key(id))";

ret = sqlite3_exec(mDb, createFellowTable.c_str(), nullptr, nullptr, nullptr);
CHECK_SQLITE_RET(ret, "create fellow table", false);

ret = sqlite3_exec(mDb, createMessageTable.c_str(), nullptr, nullptr, nullptr);
CHECK_SQLITE_RET(ret, "create message table", false);
}

success=true;
return true;
}

void History::unInit()
{
if (mDb != nullptr)
{
sqlite3_close(mDb);
mDb = nullptr;
}
}

void History::add(const HistoryRecord& record)
{
int ret;

//先更新好友信息
auto fellowId = findFellowId(record.who->getIp());
if (fellowId < 0)
{
string sql = "insert into " FELLOW_TABLE " values(null,"
+record.who->getIp()
+","+record.who->getName()
+","+record.who->getMac()
+");";
ret = sqlite3_exec(mDb, sql.c_str(), nullptr, nullptr, nullptr);
CHECK_SQLITE_RET2(ret, "insert fellow to db");

fellowId = findFellowId(record.who->getIp());
}

//再增加记录
Parcel parcel;
record.what->writeTo(parcel);
string sql = "insert into " MESSAGE_TABLE "values(null,"
+to_string(fellowId)
+","+ to_string(record.when.time_since_epoch().count())
+",?);";

sqlite3_stmt* stmt = nullptr;
ret = sqlite3_prepare_v2(mDb, sql.c_str(), sql.length(), &stmt, nullptr);
CHECK_SQLITE_RET2(ret, "prepare to insert message");

Defer finalizeStmt{
[stmt](){
sqlite3_finalize(stmt);
}
};

auto buf = parcel.raw();
ret = sqlite3_bind_blob(stmt, 1, buf.data(), buf.size(), nullptr);
CHECK_SQLITE_RET2(ret, "bind content to blob");

ret = sqlite3_step(stmt);
CHECK_SQLITE_RET2(ret, "insert message");
}

vector<HistoryRecord> History::query(const string& selection, const vector<string>& args)
{
vector<HistoryRecord> result;

sqlite3_stmt* stmt;
string sql = "select * from " MESSAGE_TABLE " where "+selection;
auto ret = sqlite3_prepare_v2(mDb, sql.c_str(), sql.length(), &stmt, nullptr);
CHECK_SQLITE_RET(ret, "prepare to query", result);

Defer finalizeStmt{
[stmt](){
sqlite3_finalize(stmt);
}
};

int len = args.size();
for (auto i = 0; i < len; i++)
{
ret = sqlite3_bind_blob(stmt, i+1, args[i].c_str(), args[i].length(), nullptr);
CHECK_SQLITE_RET(ret, "bind args", result);
}

while(true)
{
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE)
{
return result;
}
else if (ret != SQLITE_ROW)
{
cerr<<"error occur while step query:"<<ret<<endl;
return result;
}
else
{
auto fellowId = sqlite3_column_int(stmt, 1);
auto when = sqlite3_column_int(stmt, 2);
auto contentData = sqlite3_column_blob(stmt, 3);
auto contentLen = sqlite3_column_bytes(stmt, 3);

HistoryRecord record;
auto fellow = getFellow(fellowId);
record.who = shared_ptr<Fellow>(std::move(fellow));
record.when = time_point<steady_clock, milliseconds>(milliseconds(when));

Parcel parcel;
parcel.fillWith(contentData, contentLen);
parcel.resetForRead();
record.what = ContentParcelFactory::createFromParcel(parcel);

result.push_back(record);
}
}

return result;
}

unique_ptr<Fellow> History::getFellow(int id)
{

}

int History::findFellowId(const string &ip)
{

}


+ 50
- 0
feiqlib/history.h View File

@@ -0,0 +1,50 @@
#ifndef HISTORY_H
#define HISTORY_H

#include "fellow.h"
#include <memory>
#include <vector>
#include "content.h"
#include "post.h"
#include <sqlite3.h>
#include <unordered_map>

using namespace std;

struct HistoryRecord{
time_point<steady_clock, milliseconds> when;
shared_ptr<Fellow> who;
shared_ptr<Content> what;
};

//日志记录
//完成代码
//调试代码
//加入model,合并model功能
//加入engine自动记录
//按好友、日期查询最近记录
//更新文件path
/**
* @brief The History class 以Content为单位,记录和查询聊天记录
* 还只是个半成品~
*/
class History
{
public:
History();

public:
bool init(const string& dbPath);
void unInit();

public:
void add(const HistoryRecord &record);
vector<HistoryRecord> query(const string& selection, const vector<string> &args);
private:
unique_ptr<Fellow> getFellow(int id);
int findFellowId(const string& ip);
private:
sqlite3* mDb = nullptr;
};

#endif // HISTORY_H

+ 66
- 0
feiqlib/ifeiqview.h View File

@@ -0,0 +1,66 @@
#ifndef IFEIQVIEW_H
#define IFEIQVIEW_H

#include <memory>
#include <vector>
#include <chrono>

using namespace std;
using namespace std::chrono;

#include "fellow.h"
#include "content.h"
#include "filetask.h"
#include "post.h"

enum class ViewEventType {
FELLOW_UPDATE,
MESSAGE,
SEND_TIMEO,
};

class ViewEvent
{
public:
virtual ~ViewEvent(){}
public:
decltype(Post::now()) when = Post::now();
ViewEventType what;
};

class FellowViewEvent : public ViewEvent
{
public:
FellowViewEvent(){
what = ViewEventType::FELLOW_UPDATE;
}
shared_ptr<Fellow> fellow;
};

class SendTimeoEvent : public FellowViewEvent
{
public:
SendTimeoEvent(){
what = ViewEventType::SEND_TIMEO;
}

shared_ptr<Content> content;
};

class MessageViewEvent : public FellowViewEvent
{
public:
MessageViewEvent(){
what = ViewEventType::MESSAGE;
}
vector<shared_ptr<Content>> contents;
};

class IFeiqView : public IFileTaskObserver
{
public:
virtual ~IFeiqView(){}
virtual void onEvent(shared_ptr<ViewEvent> event) = 0;
};

#endif // IFEIQVIEW_H

+ 139
- 0
feiqlib/ipmsg.h View File

@@ -0,0 +1,139 @@
#ifndef IPMSG_H
#define IPMSG_H

#define IPMSG_PORT 2425

/* command */
#define IPMSG_NOOPERATION 0x00000000

#define IPMSG_BR_ENTRY 0x00000001 //用户上线
#define IPMSG_BR_EXIT 0x00000002//用户下线
#define IPMSG_ANSENTRY 0x00000003//通报在线,回复【用户上线】
#define IPMSG_BR_ABSENCE 0x00000004//通报离线模式取消或用户名变更

//下面这组不知道干嘛的
#define IPMSG_BR_ISGETLIST 0x00000010
#define IPMSG_OKGETLIST 0x00000011
#define IPMSG_GETLIST 0x00000012
#define IPMSG_ANSLIST 0x00000013
#define IPMSG_BR_ISGETLIST2 0x00000018

#define IPMSG_SENDMSG 0x00000020//发送一条消息,如果发送者不认识,发送IPMSG_BR_ENTRY,但如果标记了IPMSG_NOADDLISTOPT,放过他
#define IPMSG_RECVMSG 0x00000021//通知消息已经接收,仅当设置IPMSG_SENDCHECKOPT,附加区写入原始包序号
#define IPMSG_READMSG 0x00000030//响应IPMSG_SECRETOPT+SENDMSG,附加区写入原始包序号
#define IPMSG_DELMSG 0x00000031//?
#define IPMSG_ANSREADMSG 0x00000032//响应READMSG+READCHECKOPT

#define IPMSG_GETINFO 0x00000040//请求ipmsg协议版本
#define IPMSG_SENDINFO 0x00000041//发送ipmsg协议版本

#define IPMSG_GETABSENCEINFO 0x00000050//请问你离线了吗?
#define IPMSG_SENDABSENCEINFO 0x00000051//对啊,离线;Not absence mode没离线

#define IPMSG_GETFILEDATA 0x00000060
#define IPMSG_RELEASEFILES 0x00000061
#define IPMSG_GETDIRFILES 0x00000062

#define IPMSG_GETPUBKEY 0x00000072
#define IPMSG_ANSPUBKEY 0x00000073

//飞秋的扩展协议
#define IPMSG_OPEN_YOU 0x00000077//no extra
#define IPMSG_INPUTING 0x00000079//no extra。发送完消息也会跟一条,并且没有7a,why?
#define IPMSG_INPUT_END 0x0000007a//no extra
#define IPMSG_KNOCK 0x000000d1//窗口抖动
#define IPMSG_SENDIMAGE 0x000000c0//发送图片

/* option for all command */
#define IPMSG_ABSENCEOPT 0x00000100
#define IPMSG_SERVEROPT 0x00000200
#define IPMSG_DIALUPOPT 0x00010000
#define IPMSG_FILEATTACHOPT 0x00200000
#define IPMSG_ENCRYPTOPT 0x00400000
#define IPMSG_UTF8OPT 0x00800000

/* option for send command */
#define IPMSG_SENDCHECKOPT 0x00000100
#define IPMSG_SECRETOPT 0x00000200
#define IPMSG_BROADCASTOPT 0x00000400
#define IPMSG_MULTICASTOPT 0x00000800
#define IPMSG_NOPOPUPOPT 0x00001000
#define IPMSG_AUTORETOPT 0x00002000
#define IPMSG_RETRYOPT 0x00004000
#define IPMSG_PASSWORDOPT 0x00008000
#define IPMSG_NOLOGOPT 0x00020000
#define IPMSG_NEWMUTIOPT 0x00040000
#define IPMSG_NOADDLISTOPT 0x00080000
#define IPMSG_READCHECKOPT 0x00100000
#define IPMSG_SECRETEXOPT (IPMSG_READCHECKOPT | IPMSG_SECRETOPT)

#define IPMSG_NO_REPLY_OPTS (IPMSG_BROADCASTOPT | IPMSG_AUTORETOPT)

/* encryption flags for encrypt command */
#define IPMSG_RSA_512 0x00000001
#define IPMSG_RSA_1024 0x00000002
#define IPMSG_RSA_2048 0x00000004
#define IPMSG_RC2_40 0x00001000
#define IPMSG_RC2_128 0x00004000
#define IPMSG_RC2_256 0x00008000
#define IPMSG_BLOWFISH_128 0x00020000
#define IPMSG_BLOWFISH_256 0x00040000
#define IPMSG_AES_128 0x00100000
#define IPMSG_AES_192 0x00200000
#define IPMSG_AES_256 0x00400000
#define IPMSG_SIGN_STAMPOPT 0x01000000
#define IPMSG_SIGN_MD5 0x10000000
#define IPMSG_SIGN_SHA1 0x20000000

/* compatibilty for Win beta version */
#define IPMSG_RC2_40OLD 0x00000010 // for beta1-4 only
#define IPMSG_RC2_128OLD 0x00000040 // for beta1-4 only
#define IPMSG_BLOWFISH_128OLD 0x00000400 // for beta1-4 only
#define IPMSG_RC2_40ALL (IPMSG_RC2_40 | IPMSG_RC2_40OLD)
#define IPMSG_RC2_128ALL (IPMSG_RC2_128 | IPMSG_RC2_128OLD)
#define IPMSG_BLOWFISH_128ALL (IPMSG_BLOWFISH_128 | IPMSG_BLOWFISH_128OLD)

/* file types for fileattach command */
#define IPMSG_FILE_REGULAR 0x00000001
#define IPMSG_FILE_DIR 0x00000002
#define IPMSG_FILE_RETPARENT 0x00000003 // return parent directory
#define IPMSG_FILE_SYMLINK 0x00000004
#define IPMSG_FILE_CDEV 0x00000005 // for UNIX
#define IPMSG_FILE_BDEV 0x00000006 // for UNIX
#define IPMSG_FILE_FIFO 0x00000007 // for UNIX
#define IPMSG_FILE_RESFORK 0x00000010 // for Mac

/* file attribute options for fileattach command */
#define IPMSG_FILE_RONLYOPT 0x00000100
#define IPMSG_FILE_HIDDENOPT 0x00001000
#define IPMSG_FILE_EXHIDDENOPT 0x00002000 // for MacOS X
#define IPMSG_FILE_ARCHIVEOPT 0x00004000
#define IPMSG_FILE_SYSTEMOPT 0x00008000

/* extend attribute types for fileattach command */
#define IPMSG_FILE_UID 0x00000001
#define IPMSG_FILE_USERNAME 0x00000002 // uid by string
#define IPMSG_FILE_GID 0x00000003
#define IPMSG_FILE_GROUPNAME 0x00000004 // gid by string
#define IPMSG_FILE_PERM 0x00000010 // for UNIX
#define IPMSG_FILE_MAJORNO 0x00000011 // for UNIX devfile
#define IPMSG_FILE_MINORNO 0x00000012 // for UNIX devfile
#define IPMSG_FILE_CTIME 0x00000013 // for UNIX
#define IPMSG_FILE_MTIME 0x00000014
#define IPMSG_FILE_ATIME 0x00000015
#define IPMSG_FILE_CREATETIME 0x00000016
#define IPMSG_FILE_CREATOR 0x00000020 // for Mac
#define IPMSG_FILE_FILETYPE 0x00000021 // for Mac
#define IPMSG_FILE_FINDERINFO 0x00000022 // for Mac
#define IPMSG_FILE_ACL 0x00000030
#define IPMSG_FILE_ALIASFNAME 0x00000040 // alias fname
#define IPMSG_FILE_UNICODEFNAME 0x00000041 // UNICODE fname

#define FILELIST_SEPARATOR 0x7
#define HOSTLIST_DUMMY (char)0x8
#define HLIST_ENTRY_SEPARATOR (char)0x3a

#define IS_CMD_SET(cmd, test) (((cmd) & 0xFF) == test)
#define IS_OPT_SET(cmd, opt) (((cmd) & opt) == opt)

#endif // IPMSG_H

+ 29
- 0
feiqlib/msgqueuethread.cpp View File

@@ -0,0 +1,29 @@
#include "msgqueuethread.h"
#include <thread>

MsgQueueThread::MsgQueueThread()
{

}

void MsgQueueThread::setHandler(MsgQueueThread::Handler handler)
{

}

void MsgQueueThread::start()
{
if (mRun)
return;

mRun=true;
thread thd(&MsgQueueThread::loop, this);
thd.detach();
}

void MsgQueueThread::stop()
{
mRun=false;
unique_lock<mutex> lock(mQueueMutex);
mQueueCnd.notify_all();
}

+ 82
- 0
feiqlib/msgqueuethread.h View File

@@ -0,0 +1,82 @@
#ifndef MSGQUEUETHREAD_H
#define MSGQUEUETHREAD_H

#include <mutex>
#include <queue>
#include <functional>
#include <condition_variable>
#include <thread>
using namespace std;

//TODO:实现移到cpp中
template<class Msg>
class MsgQueueThread
{
typedef function<void (shared_ptr<Msg>)> Handler;
public:
void setHandler(Handler handler)
{
mHandler = handler;
}

void start()
{
if (mRun)
return;

mRun=true;
thread thd(&MsgQueueThread::loop, this);
mThread.swap(thd);
}

void stop()
{
if (!mRun)
return;

mRun=false;
unique_lock<mutex> lock(mQueueMutex);
while(!mQueue.empty())
mQueue.pop();
mQueueCnd.notify_all();
lock.unlock();
mThread.join();
}

void sendMessage(shared_ptr<Msg> msg)
{
unique_lock<mutex> lock(mQueueMutex);
mQueue.push(msg);
mQueueCnd.notify_one();
}

private:
void loop()
{
while (mRun) {
unique_lock<mutex> lock(mQueueMutex);
if (mQueue.empty())
mQueueCnd.wait(lock);
if (!mRun)
return;

auto msg = mQueue.front();
mQueue.pop();
lock.unlock();//队列已经没有利用价值了,放了它

if (mHandler)
mHandler(msg);
}

}

private:
condition_variable mQueueCnd;
mutex mQueueMutex;
queue<shared_ptr<Msg>> mQueue;
bool mRun=false;
Handler mHandler;
thread mThread;
};

#endif // MSGQUEUETHREAD_H

+ 145
- 0
feiqlib/parcelable.h View File

@@ -0,0 +1,145 @@
#ifndef PARCELABLE_H
#define PARCELABLE_H

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

class Parcel
{
private:
class Head
{
public:
Head(int size)
{
this->size = size;
}

Head(istream& ss)
{
read(ss);
}

int size;

void myWriteInt(ostream& os, int val)
{
char buf[9] = {0};
snprintf(buf, sizeof(buf), "%08d", val);
os<<buf;
}

int myReadInt(istream& is)
{
char buf[9] = {0};
is.read(buf, sizeof(buf)-1);
return stoi(buf);
}

void write(ostream& os){
myWriteInt(os, size);
}

void read(istream& is){
size = myReadInt(is);
}
};

public:
template<typename T>
void write(const T& val){
writePtr(&val, 1);
}

template<typename T>
void read(T& val){
readPtr(&val);
}

template<typename T>
void writePtr(const T* ptr, int n){
Head head{(int)(n * sizeof(T))};
head.write(ss);
ss.write((const char*)ptr, head.size);
}

template<typename T>
void readPtr(T* ptr){
Head head(ss);
unique_ptr<char[]> buf(new char[head.size]);
ss.read(buf.get(), head.size);
memcpy(ptr, buf.get(), head.size);
}

void writeString(const string& val)
{
writePtr(val.c_str(), val.length());
}

void readString(string& val)
{
auto size = nextSize();
unique_ptr<char[]> buf(new char[size+1]);
readPtr(buf.get());
buf[size]=0;
val=buf.get();
}

void resetForRead()
{
ss.seekg(0, ss.beg);
}

void fillWith(const void* data, int len)
{
ss.clear();
ss.write(static_cast<const char*>(data), len);
}

public:
streampos mark()
{
return ss.tellg();
}

void unmark(streampos markPos)
{
ss.seekg(markPos);
}

public:
vector<char> raw()
{
auto size = ss.tellp();
resetForRead();

vector<char> buf(size);
ss.read(buf.data(), size);

return buf;
}

private:
int nextSize()
{
auto pos = mark();
Head head(ss);
unmark(pos);
return head.size;
}

private:
stringstream ss;
};

class Parcelable
{
public:
virtual void writeTo(Parcel& out) const =0;
virtual void readFrom(Parcel& in) =0;
};

#endif // PARCELABLE_H

+ 47
- 0
feiqlib/post.h View File

@@ -0,0 +1,47 @@
#ifndef POST_H
#define POST_H

#include <memory>
#include <vector>
#include <list>
#include <string>
#include "content.h"
#include <chrono>

using namespace std;
using namespace std::chrono;
class Fellow;

/**
* @brief The Post struct
* 定义一次好友发来的请求/数据包
*/
class Post
{
public:
static time_point<system_clock, milliseconds> now(){
return time_point_cast<milliseconds>(system_clock::now());
}

decltype(now()) when = Post::now();
vector<char> extra;

string packetNo;
IdType cmdId;

shared_ptr<Fellow> from;
vector<shared_ptr<Content>> contents;

Post()
{
from = make_shared<Fellow>();
}

void addContent(shared_ptr<Content> content)
{
content->setPacketNo(packetNo);
contents.push_back(content);
}
};

#endif // POST_H

+ 27
- 0
feiqlib/protocol.h View File

@@ -0,0 +1,27 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H

#include <ostream>
using namespace std;

class Post;

class SendProtocol
{
public:
virtual int cmdId() = 0;
virtual void write(ostream& os) = 0;
};

class RecvProtocol
{
public:
/**
* @brief read 解析收到的数据
* @param post 当前好友请求,整个解析链中被不断读写完善,在解析链最后得到一个完整的Post
* @return true 已完成解析,无需后续的解析链,false 继续解析
*/
virtual bool read(shared_ptr<Post> post) = 0;
};

#endif // PROTOCOL_H

+ 84
- 0
feiqlib/tcpserver.cpp View File

@@ -0,0 +1,84 @@
#include "tcpserver.h"
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <thread>
#include <unistd.h>

using namespace std;

TcpServer::TcpServer()
{

}

bool TcpServer::start(int port)
{
if (mStarted)
return true;

mSocket = socket(AF_INET, SOCK_STREAM, 0);
if (mSocket == -1)
{
perror("create tcp socket failed: %s");
return false;
}

int val = 1;
setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
int ret = ::bind(mSocket, (sockaddr*)&addr, sizeof(addr));
if (ret == -1)
{
perror("bind failed");
return false;
}

ret = listen(mSocket, 5);
if (ret == -1)
{
perror("listen failed");
return false;
}

mStarted=true;
thread thd(&TcpServer::keepAccept, this);
thd.detach();

return true;
}

void TcpServer::whenNewClient(TcpServer::ClientHandler onClientConnected)
{
mClientHandler = onClientConnected;
}

void TcpServer::stop()
{
mStarted = false;
close(mSocket);
}

void TcpServer::keepAccept()
{
while (mStarted) {
sockaddr_in addr;
socklen_t len = sizeof(addr);
int ret = ::accept(mSocket, (sockaddr*)&addr, &len);
if (ret < 0)
{
perror("failed to accept");
break;
}

if (mClientHandler)
{
mClientHandler(ret);
}
}
}

+ 23
- 0
feiqlib/tcpserver.h View File

@@ -0,0 +1,23 @@
#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <functional>

class TcpServer
{
public:
TcpServer();
typedef std::function<void (int socket)> ClientHandler;
public:
bool start(int port);
void whenNewClient(ClientHandler onClientConnected);
void stop();
private:
void keepAccept();
private:
ClientHandler mClientHandler;
bool mStarted=false;
int mSocket;
};

#endif // TCPSERVER_H

+ 113
- 0
feiqlib/tcpsocket.cpp View File

@@ -0,0 +1,113 @@
#include "tcpsocket.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>

TcpSocket::TcpSocket()
{

}

TcpSocket::TcpSocket(int socket)
{
mSocket = socket;
if (mSocket != -1)
{
sockaddr_in addr;
socklen_t len = sizeof(addr);
auto ret = getpeername(mSocket, (sockaddr*)&addr, &len);
if (ret == 0)
{
mPeerIp = inet_ntoa(addr.sin_addr);
}
}
}

TcpSocket::~TcpSocket()
{
disconnect();
}

bool TcpSocket::connect(const string &ip, int port)
{
if (mSocket != -1)
return true;

mSocket = socket(AF_INET, SOCK_STREAM, 0);
if (mSocket == -1)
{
perror("faield to create socket");
return false;
}

sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
int ret = ::connect(mSocket, (sockaddr*)&addr, sizeof(addr));
if (ret == -1)
{
perror("failed to connect");
close(mSocket);
return false;
}

mPeerIp = ip;
return true;
}

void TcpSocket::disconnect()
{
if (mSocket != -1)
{
close(mSocket);
mPeerIp="";
}
}

int TcpSocket::send(const void *data, int size)
{
int sent = 0;
auto pdata = static_cast<const char*>(data);

while (sent < size)
{
int ret = ::send(mSocket, pdata+sent, size-sent, 0);
if (ret == -1 )
{
if (errno != EAGAIN)
{
perror("tcp send failed");
return -1;
}
continue;
}

sent+=ret;
}

return sent;
}

int TcpSocket::recv(void *data, int size, int msTimeout)
{
timeval tv = {msTimeout/1000, (msTimeout%1000)*1000};
setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

int ret = ::recv(mSocket, data, size, 0);
if (ret == -1)
{
if (errno == ETIMEDOUT || errno == EAGAIN)
return -1;
else
{
perror("recv failed");
return -2;
}
}

return ret;
}

+ 34
- 0
feiqlib/tcpsocket.h View File

@@ -0,0 +1,34 @@
#ifndef TCPSOCKET_H
#define TCPSOCKET_H

#include <string>
using namespace std;

class TcpSocket
{
public:
TcpSocket();
TcpSocket(int socket);
~TcpSocket();

public:
bool connect(const string& ip, int port);
void disconnect();

public:
int send(const void* data, int size);
/**
* @brief recv 阻塞等待数据
* @param data 接收缓冲区
* @param size 最大接收字节数
* @param msTimeout 接收超时(毫秒)
* @return 接收到的字节数;超时返回-1,其他错误返回-2
*/
int recv(void* data, int size, int msTimeout = 1000);

private:
int mSocket=-1;
string mPeerIp;
};

#endif // TCPSOCKET_H

+ 189
- 0
feiqlib/udpcommu.cpp View File

@@ -0,0 +1,189 @@
#include "udpcommu.h"
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <thread>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if_dl.h>
#include <sys/sysctl.h>
#include <array>

#define setFailedMsgAndReturnFalse(msg) \
{mErrMsg = msg;\
return false;}

#define setErrnoMsgAndReturnFalse()\
{mErrMsg = strerror(errno);\
return false;}

#define setErrnoMsg() mErrMsg = strerror(errno);

UdpCommu::UdpCommu(){}

bool UdpCommu::bindTo(int port)
{
if (mSocket != -1)
setFailedMsgAndReturnFalse("已经初始化");

//创建socket
mSocket = socket(PF_INET, SOCK_DGRAM, 0);
if (mSocket == -1)
setErrnoMsgAndReturnFalse();

auto ret = -1;
//允许广播
auto broadcast = 1;
ret = setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(int));
if (ret == -1)
setErrnoMsgAndReturnFalse();

//地址复用
auto reuse = 1;
ret = setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
if (ret == -1)
setErrnoMsgAndReturnFalse();

//绑定
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);

ret = ::bind(mSocket, (sockaddr*)&addr, sizeof(addr));
if (ret == -1)
setErrnoMsgAndReturnFalse();

return true;
}

int UdpCommu::sentTo(const string& ip, int port, const void *data, int size)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);

auto ret = ::sendto(mSocket, data, size, 0, (sockaddr*)&addr, sizeof(addr));
if (ret == -1)
setErrnoMsg();

return ret;
}

bool UdpCommu::startAsyncRecv(UdpRecvHandler handler)
{
if (handler == nullptr)
setFailedMsgAndReturnFalse("handler不能为空")

if (mSocket == -1)
setFailedMsgAndReturnFalse("请先初始化socket");

mRecvHandler = handler;
if (!mAsyncMode)
{
mAsyncMode = true;
std::thread t(&UdpCommu::recvThread, this);
t.detach();
}

return true;
}

void UdpCommu::close()
{
if (mSocket == -1)
return;

::close(mSocket);
mSocket = -1;
mAsyncMode=false;
}

string UdpCommu::getBoundMac()
{
//osx未定义SIOCGIFHWADDR,写死获取en0
int mib[6];
size_t len=0;
unsigned char *ptr;
struct if_msghdr *ifm;
struct sockaddr_dl *sdl;

mib[0] = CTL_NET;
mib[1] = AF_ROUTE;
mib[2] = 0;
mib[3] = AF_LINK;
mib[4] = NET_RT_IFLIST;
if ((mib[5] = if_nametoindex("en0")) == 0) {
perror("if_nametoindex error");
return "";
}

if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
perror("sysctl 1 error");
return "";
}

unique_ptr<char[]> buf(new char[len]);
if (sysctl(mib, 6, buf.get(), &len, NULL, 0) < 0) {
perror("sysctl 2 error");
return "";
}

ifm = (struct if_msghdr *)buf.get();
sdl = (struct sockaddr_dl *)(ifm + 1);
ptr = (unsigned char *)LLADDR(sdl);

char macStr[20]={0};
snprintf(macStr, sizeof(macStr), "%02x%02x%02x%02x%02x%02x", *ptr, *(ptr+1), *(ptr+2),
*(ptr+3), *(ptr+4), *(ptr+5));

return macStr;
}

string UdpCommu::getErrMsg()
{
return mErrMsg;
}

void UdpCommu::recvThread()
{
timeval timeo = {3,0};
auto ret = setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeval));
if (ret != 0){
printf("faield to set recv timeo\n");
mAsyncMode=false;
return;
}

std::array<char,MAX_RCV_SIZE> buf;
sockaddr_in addr;
socklen_t len = sizeof(addr);

while (mSocket != -1) {
buf.fill(0);
memset(&addr, 0, len);

auto size = recvfrom(mSocket, buf.data(), MAX_RCV_SIZE, 0, (sockaddr*)&addr, &len);
if (size < 0)
{
if (errno == EAGAIN || errno == ETIMEDOUT)
continue;

printf("error occur:%s\n", strerror(errno));
break;
}

auto ip = inet_ntoa(addr.sin_addr);
vector<char> data(std::begin(buf), std::begin(buf)+size);
mRecvHandler(ip, data);
}

printf("end recv thread\n");
mAsyncMode=false;
}

+ 68
- 0
feiqlib/udpcommu.h View File

@@ -0,0 +1,68 @@
#ifndef UDPCOMMU_H
#define UDPCOMMU_H

#include <string>
#include <functional>
#include <vector>
using namespace std;

#define MAX_RCV_SIZE 4096
typedef function<void (const string& ip, vector<char> &data)> UdpRecvHandler;

class UdpCommu
{
public:
UdpCommu();
public:
/**
* @brief bindTo 绑定到本地端口
* @param port 端口号
* @return 是否绑定成功
*/
bool bindTo(int port);

/**
* @brief sentTo 往目标地址发送数据
* @param ip 目标ip
* @param port 目标端口
* @param data 数据指针
* @param size 数据大小
* @return >=0发送成功的字节数,-1发送失败
*/
int sentTo(const string &ip, int port, const void *data, int size);

/**
* @brief startAsyncRecv 开始异步接收数据
* @param handler 处理函数
* @return 是否启动成功
*/
bool startAsyncRecv(UdpRecvHandler handler);

/**
* @brief close 关闭udp通信
*/
void close();

/**
* @brief getBoundMac 获取udp通信绑定的网卡
* @return 绑定的网卡地址
*/
string getBoundMac();
public:
/**
* @brief getErrMsg 获取最近一次错误的错误信息
* @return 最近一次的错误信息
*/
string getErrMsg();

private:
void recvThread();
bool mAsyncMode=false;

private:
string mErrMsg="";
int mSocket=-1;
UdpRecvHandler mRecvHandler=nullptr;
};

#endif // UDPCOMMU_H

+ 16
- 0
feiqlib/uniqueid.cpp View File

@@ -0,0 +1,16 @@
#include "uniqueid.h"
#include <limits.h>

UniqueId::UniqueId()
{
mId = 0;
}

IdType UniqueId::get()
{
auto id = ++mId;
if (id >= INT_MAX || id < 0)
mId=1;

return mId;
}

+ 21
- 0
feiqlib/uniqueid.h View File

@@ -0,0 +1,21 @@
#ifndef UNIQUEID_H
#define UNIQUEID_H

#include <atomic>
using namespace std;

typedef unsigned long long IdType;
/**
* @brief The UniqueId class 返回大于0小于MAX_ULONG的数,多线程安全,自动归0
*/
class UniqueId
{
public:
UniqueId();
public:
IdType get();
private:
atomic_ullong mId;
};

#endif // UNIQUEID_H

+ 84
- 0
feiqlib/utils.cpp View File

@@ -0,0 +1,84 @@
#include "utils.h"

vector<string> splitAllowSeperator(vector<char>::iterator from, vector<char>::iterator to, char sep)
{
vector<string> values;
vector<char> buf;
while(from < to)
{
if (*from == sep)
{
if (from+1 != to && *(from+1) == sep)
{
++from;
}
else
{
char* value = new char[buf.size()+1];
memcpy(value, buf.data(), buf.size());
value[buf.size()]=0;
values.push_back(value);
delete[] value;
buf.clear();
++from;
continue;
}
}

buf.push_back(*from);
++from;
}

return values;
}

void stringReplace(string& target,const string& pattern,const string& candidate)
{
auto pos=0;
auto ps=pattern.size();
auto cs=candidate.size();
while((pos=target.find(pattern,pos)) != string::npos)
{
target.replace(pos,ps,candidate);
pos+=cs;
}
}

string getFileNameFromPath(const string &path)
{
auto sep = '/';
auto result = path;
if (result.at(result.length()-1) == sep)
result = result.substr(0, result.length()-1);

auto pos = result.find_last_of('/');
if (pos == string::npos)
return result;

return result.substr(pos+1);
}

bool startsWith(const string &str, const string &patten)
{
if (str.length() < patten.length())
return false;
return std::equal(patten.begin(), patten.end(), str.begin());
}

bool endsWith(const string &str, const string &patten)
{
if (str.length() < patten.length())
return false;
return std::equal(patten.rbegin(), patten.rend(), str.rbegin());
}

string toString(const vector<char> &buf)
{
auto len = buf.size();
char* value = new char[len+1];
memcpy(value, buf.data(), len);
value[len]=0;
string result = value;
delete[] value;
return result;
}

+ 13
- 0
feiqlib/utils.h View File

@@ -0,0 +1,13 @@
#ifndef UTILS_H
#define UTILS_H

#include <vector>
#include <string>
using namespace std;
vector<string> splitAllowSeperator(vector<char>::iterator from, vector<char>::iterator to, char sep);
void stringReplace(string& target,const string& pattern,const string& candidate);
string getFileNameFromPath(const string& path);
bool startsWith(const string& str, const string& patten);
bool endsWith(const string& str, const string& patten);
string toString(const vector<char>& buf);
#endif // UTILS_H

+ 106
- 0
fellowlistwidget.cpp View File

@@ -0,0 +1,106 @@
#include "fellowlistwidget.h"
#include <QDebug>

FellowListWidget::FellowListWidget()
{

}

void FellowListWidget::bindTo(QListWidget *widget)
{
mWidget = widget;
connect(mWidget, &QListWidget::itemClicked, this, &FellowListWidget::itemChosen);
}

void FellowListWidget::update(const Fellow &fellow)
{
auto item = findFirstItem(fellow);
if (item == nullptr)
{
mWidget->addItem(fellowText(fellow));
item = mWidget->item(mWidget->count()-1);
}
else
{
item->setText(fellowText(fellow));
}

item->setData(Qt::UserRole, QVariant::fromValue((void*)&fellow));
}

void FellowListWidget::top(const Fellow &fellow)
{
auto item = findFirstItem(fellow);
if (item != nullptr)
{
mWidget->takeItem(mWidget->row(item));
mWidget->insertItem(0, item);
mWidget->scrollToItem(item);
mWidget->setCurrentItem(item);
}
}

//TODO:take->insert的方式或导致如果item是当前焦点,则移动后焦点丢失
void FellowListWidget::topSecond(const Fellow &fellow)
{
auto item = findFirstItem(fellow);
if (item != nullptr)
{
mWidget->takeItem(mWidget->row(item));
mWidget->insertItem(1, item);
}
}

void FellowListWidget::mark(const Fellow &fellow, const QString &info)
{
auto item = findFirstItem(fellow);
if (item != nullptr)
{
if (info.isEmpty())
item->setText(fellowText(fellow));
else
{
item->setText("("+info+")"+fellowText(fellow));
if (mWidget->row(item)!=0)
{
if (mWidget->currentRow() == 0)
topSecond(fellow);
else
top(fellow);
}
}
}
}

void FellowListWidget::itemChosen(QListWidgetItem *item)
{
if (item == nullptr)
return;

auto fellow = static_cast<const Fellow*>(item->data(Qt::UserRole).value<void*>());
emit select(fellow);
}

QString FellowListWidget::fellowText(const Fellow &fellow)
{
auto text = fellow.getName()+","+fellow.getIp();
if (!fellow.isOnLine())
{
text = "[离线]"+text;
}
return QString(text.c_str());
}

QListWidgetItem *FellowListWidget::findFirstItem(const Fellow &fellow)
{
auto count = mWidget->count();
for (int i = 0; i < count; i++)
{
auto widget = mWidget->item(i);
auto f = static_cast<const Fellow*>(widget->data(Qt::UserRole).value<void*>());
if (f->getIp() == fellow.getIp())
return widget;
}

return nullptr;
}

+ 37
- 0
fellowlistwidget.h View File

@@ -0,0 +1,37 @@
#ifndef FELLOWLISTWIDGET_H
#define FELLOWLISTWIDGET_H

#include "feiqlib/feiqmodel.h"
#include <QListWidget>

//1.按“会话中”,“有新消息”、“在线”、“离线”优先级罗列好友信息
//2.支持查询好友
class FellowListWidget : public QObject
{
Q_OBJECT

public:
FellowListWidget();
void bindTo(QListWidget* widget);

public:
void update(const Fellow& fellow);
void top(const Fellow& fellow);
void topSecond(const Fellow& fellow);
void mark(const Fellow& fellow, const QString &info);

signals:
void select(const Fellow* fellow);

private slots:
void itemChosen(QListWidgetItem *item);

private:
QString fellowText(const Fellow& fellow);
QListWidgetItem* findFirstItem(const Fellow& fellow);

private:
QListWidget* mWidget;
};

#endif // FELLOWLISTWIDGET_H

+ 230
- 0
filemanagerdlg.cpp View File

@@ -0,0 +1,230 @@
#include "filemanagerdlg.h"
#include "ui_downloadfiledlg.h"
#include <sstream>
#include <QMessageBox>
#include <QFileDialog>

#define TYPE_COL 0
#define STATE_COL 1
#define FELLOW_COL 2
#define FILENAME_COL 3
#define PROGRESS_COL 4

FileManagerDlg::FileManagerDlg(QWidget *parent) :
QDialog(parent),
ui(new Ui::DownloadFileDlg)
{
ui->setupUi(this);

connect(ui->clearBtn, SIGNAL(clicked(bool)), this, SLOT(clear()));
connect(ui->delBtn, SIGNAL(clicked(bool)), this, SLOT(delSelTask()));
connect(ui->saveBtn, SIGNAL(clicked(bool)), this, SLOT(saveSelTask()));
connect(ui->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(refresh()));
}

FileManagerDlg::~FileManagerDlg()
{
delete ui;
}

void FileManagerDlg::setEngine(FeiqEngine *engine)
{
mEngine = engine;
}

void FileManagerDlg::select(FileTask *task)
{
refresh();
auto row = findRowByTask(task);
if (row == -1)
return;

ui->taskTable->setCurrentCell(row, TYPE_COL);
}

void FileManagerDlg::statChanged(FileTask *task)
{
auto row = findRowByTask(task);
if (row == -1)
return;

auto str = stateString(task);
auto item = ui->taskTable->item(row, STATE_COL);
item->setText(str);
}

void FileManagerDlg::progressChanged(FileTask *task)
{
auto row = findRowByTask(task);
if (row == -1)
return;

auto str = progressString(task);
auto item = ui->taskTable->item(row, PROGRESS_COL);
item->setText(str);
}

void FileManagerDlg::delSelTask()
{
auto task = getTaskOfCurrentRow();
if (task)
{
if (task->getState() == FileTaskState::Running)
{
QMessageBox::warning(this, "无法删除", "不能删除正上传或下载中的任务", QMessageBox::Ok);
}
else
{
mEngine->getModel().removeFileTask([task](const FileTask& t){
return &t == task;
});
refresh();
}
}
}

void FileManagerDlg::saveSelTask()
{
auto task = getTaskOfCurrentRow();
if (task && task->type() == FileTaskType::Download)
{
if (task->getContent()->path.empty())
{
QString path = QFileDialog::getExistingDirectory(this, "选择保存到……");
if (!path.isEmpty())
{
task->getContent()->path = path.toStdString()+"/"+task->getContent()->filename;
}
}

if (!task->getContent()->path.empty())
mEngine->downloadFile(task);
}
}

void FileManagerDlg::clear()
{
mEngine->getModel().removeFileTask([](const FileTask& task){
return task.getState() != FileTaskState::Running && task.getState() != FileTaskState::NotStart;
});
refresh();
}

void FileManagerDlg::refresh()
{
auto filter = ui->searchEdit->text().toStdString();
reloadWith([filter](const FileTask& task){
if (!filter.empty())
{
return task.getContent()->filename.find(filter)!=string::npos
||task.fellow()->getName().find(filter)!=string::npos;
}
return true;
});
}

void FileManagerDlg::reloadWith(FileManagerDlg::SearchPredict predict)
{
if (!mEngine)
return;

auto table = ui->taskTable;
// table->clearContents();
auto rowCount = table->rowCount();
for (int i = 0; i < rowCount; i++)
table->removeRow(0);
auto tasks = mEngine->getModel().searchTask(predict);
for (shared_ptr<FileTask> task : tasks)
{
auto row = table->rowCount();
table->insertRow(row);
auto item = new QTableWidgetItem(typeString(task->type()));
item->setData(Qt::UserRole, QVariant::fromValue((void*)(task.get())));//把task保存到UserRole中
table->setItem(row, TYPE_COL, item);
table->setItem(row, STATE_COL, new QTableWidgetItem(stateString(task.get())));
table->setItem(row, FELLOW_COL, new QTableWidgetItem(task->fellow()->getName().c_str()));
table->setItem(row, FILENAME_COL, new QTableWidgetItem(task->getContent()->filename.c_str()));
table->setItem(row, PROGRESS_COL, new QTableWidgetItem(progressString(task.get())));
}
table->resizeColumnToContents(TYPE_COL);
table->resizeColumnToContents(TYPE_COL);
table->resizeColumnToContents(FELLOW_COL);
table->resizeColumnToContents(FILENAME_COL);

table->setCurrentCell(0,0);
}

QString FileManagerDlg::typeString(FileTaskType type)
{
switch (type)
{
case FileTaskType::Download:
return "下载";
case FileTaskType::Upload:
return "上传";
default:
break;
}
return "";
}

QString FileManagerDlg::stateString(const FileTask* task)
{
switch (task->getState()) {
case FileTaskState::Canceled:
return "已取消";
case FileTaskState::Error:
return task->getDetailInfo().c_str();
case FileTaskState::Finish:
return "已完成";
case FileTaskState::NotStart:
return "还未开始";
case FileTaskState::Running:
if (task->type()==FileTaskType::Upload)
return "正在上传";
else
return "正在下载";
default:
break;
}

return "";
}

QString FileManagerDlg::progressString(const FileTask *task)
{
stringstream ss;
float percent = task->getProcess()*100.0f/task->getContent()->size;
ss<<task->getProcess()<<"/"<<task->getContent()->size<<"("<<percent<<"%)";
return ss.str().c_str();
}

FileTask *FileManagerDlg::getTaskOfCurrentRow()
{
auto row = ui->taskTable->currentRow();
auto widget = ui->taskTable->item(row, TYPE_COL);
if (widget == nullptr)
return nullptr;

auto task = widget->data(Qt::UserRole);
return static_cast<FileTask*>(task.value<void*>());
}

int FileManagerDlg::findRowByTask(const FileTask *task)
{
int rowCount = ui->taskTable->rowCount();
for (int i = 0; i < rowCount; i++)
{
auto widget = ui->taskTable->item(i, TYPE_COL);
auto itemTask = static_cast<FileTask*>(widget->data(Qt::UserRole).value<void*>());
if (itemTask == task)
return i;
}

return -1;
}

void FileManagerDlg::showEvent(QShowEvent *)
{
refresh();
}

+ 55
- 0
filemanagerdlg.h View File

@@ -0,0 +1,55 @@
#ifndef DOWNLOADFILEDLG_H
#define DOWNLOADFILEDLG_H

#include <QDialog>
#include <memory>
#include "feiqlib/filetask.h"
#include "feiqlib/feiqengine.h"

using namespace std;

namespace Ui {
class DownloadFileDlg;
}


class FileManagerDlg : public QDialog
{
Q_OBJECT

typedef function<bool (const FileTask &)> SearchPredict;
public:
explicit FileManagerDlg(QWidget *parent = 0);
~FileManagerDlg();

public:
void setEngine(FeiqEngine* engine);
void select(FileTask* task);

public slots:
void statChanged(FileTask* task);
void progressChanged(FileTask* task);

private slots:
void delSelTask();
void saveSelTask();
void clear();
void refresh();

private:
void reloadWith(SearchPredict predict);
QString typeString(FileTaskType type);
QString stateString(const FileTask *task);
QString progressString(const FileTask* task);
FileTask* getTaskOfCurrentRow();
int findRowByTask(const FileTask* task);

protected:
virtual void showEvent(QShowEvent *) override;

private:
Ui::DownloadFileDlg *ui;
FeiqEngine* mEngine;
};

#endif // DOWNLOADFILEDLG_H

BIN
icon.icns View File


+ 21
- 0
main.cpp View File

@@ -0,0 +1,21 @@
#include "mainwindow.h"
#include <QApplication>
#include <QWindow>

//待测:
//*无当前交谈用户,且有未读消息时,未读消息能否正确置顶?

//应用图标
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setWindowIcon(QIcon(":/default/res/icon.png"));
MainWindow w;
w.show();
return a.exec();
}

//表情->html
//查通讯录功能
//查ip占用功能
//美化用户列表

+ 526
- 0
mainwindow.cpp View File

@@ -0,0 +1,526 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <thread>
#include <QDir>
#include <QDebug>
#include <QMessageBox>
#include <QTextCodec>
#include <QFileDialog>
#include <QDateTime>
#include <QtMac>
#include "addfellowdialog.h"
#include <QProcess>
#include "platformdepend.h"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
qRegisterMetaType<shared_ptr<ViewEvent>>("ViewEventSharedPtr");

connect(this, SIGNAL(showErrorAndQuit(QString)), this, SLOT(onShowErrorAndQuit(QString)));
//初始化搜索对话框
mSearchFellowDlg = new SearchFellowDlg(this);
connect(mSearchFellowDlg, SIGNAL(onFellowSelected(const Fellow*)),
this, SLOT(finishSearch(const Fellow*)));

connect(ui->actionRefreshFellows, SIGNAL(triggered(bool)), this, SLOT(refreshFellowList()));
connect(ui->actionAddFellow, SIGNAL(triggered(bool)), this, SLOT(addFellow()));

mSearchFellowDlg->setSearchDriver(std::bind(&MainWindow::fellowSearchDriver, this, placeholders::_1));

//初始化文件管理对话框
mDownloadFileDlg = new FileManagerDlg(this);
mDownloadFileDlg->setEngine(&mFeiq);
connect(this, SIGNAL(statChanged(FileTask*)), mDownloadFileDlg, SLOT(statChanged(FileTask*)));
connect(this, SIGNAL(progressChanged(FileTask*)), mDownloadFileDlg, SLOT(progressChanged(FileTask*)));

//初始化好友列表
mFellowList.bindTo(ui->fellowListWidget);
connect(&mFellowList, SIGNAL(select(const Fellow*)), this, SLOT(openChartTo(const Fellow*)));

//初始化接收文本框
mRecvTextEdit = ui->recvEdit;
connect(mRecvTextEdit, SIGNAL(navigateToFileTask(IdType,IdType,bool)), this, SLOT(navigateToFileTask(IdType,IdType,bool)));

//初始化发送文本框
mSendTextEdit = ui->sendEdit;
connect(mSendTextEdit, SIGNAL(acceptDropFiles(QList<QFileInfo>)), this, SLOT(sendFiles(QList<QFileInfo>)));

//初始化Emoji对话框
mChooseEmojiDlg = new ChooseEmojiDlg(this);
connect(ui->actionInsertEmoji, SIGNAL(triggered(bool)), this, SLOT(openChooseEmojiDlg()));
connect(mChooseEmojiDlg, SIGNAL(choose(QString)),mSendTextEdit, SLOT(insertPlainText(QString)));

//初始化菜单
connect(ui->actionSearchFellow, SIGNAL(triggered(bool)), this, SLOT(openSearchDlg()));
connect(ui->actionSettings, SIGNAL(triggered(bool)), this, SLOT(openSettings()));
connect(ui->actionOpendl, SIGNAL(triggered(bool)), this, SLOT(openDownloadDlg()));
connect(ui->actionSendText, SIGNAL(triggered(bool)), this, SLOT(sendText()));
connect(ui->actionSendKnock, SIGNAL(triggered(bool)), this, SLOT(sendKnock()));
connect(ui->actionSendFile, SIGNAL(triggered(bool)), this, SLOT(sendFile()));

//加载配置
auto settingFilePath = QDir::home().filePath(".feiq_setting.ini");
mSettings = new QSettings(settingFilePath, QSettings::IniFormat);
mSettings->setIniCodec(QTextCodec::codecForName("UTF-8"));
mTitle = mSettings->value("app/title", "mac飞秋").toString();
setWindowTitle(mTitle);

mUnreadTimerInterval = mSettings->value("app/unread_timer", "0").toInt();
if (mUnreadTimerInterval > 0)
mUnreadTimerId = startTimer(mUnreadTimerInterval*1000, Qt::VeryCoarseTimer);

//初始化飞秋引擎
connect(this, SIGNAL(feiqViewEvent(shared_ptr<ViewEvent>)), this, SLOT(handleFeiqViewEvent(shared_ptr<ViewEvent>)));

//后台初始化通信
std::thread thd(&MainWindow::initFeiq, this);
thd.detach();
}

MainWindow::~MainWindow()
{
mFeiq.stop();
delete mSettings;
delete mSearchFellowDlg;
delete mDownloadFileDlg;
delete mChooseEmojiDlg;
delete ui;
}

void MainWindow::enterEvent(QEvent *event)
{
auto fellow = mRecvTextEdit->curFellow();
if (fellow)
{
flushUnread(fellow);
updateUnread(fellow);
}

PlatformDepend::instance().hideAllNotify();
}

void MainWindow::timerEvent(QTimerEvent *event)
{
if (event->timerId() == mUnreadTimerId)
{
auto count = getUnreadCount();
if (count > 0)
PlatformDepend::instance().showNotify("未读提醒", QString("还有%1条未读消息").arg(count));
}
}

void MainWindow::openChartTo(const Fellow *fellow)
{
mFellowList.top(*fellow);
mRecvTextEdit->setCurFellow(fellow);
setWindowTitle(mTitle + " - 与"+fellow->getName().c_str()+"会话中");
flushUnread(fellow);
updateUnread(fellow);
}

shared_ptr<Fellow> MainWindow::checkCurFellow()
{
auto fellow = mFeiq.getModel().getShared(mRecvTextEdit->curFellow());
if (fellow == nullptr)
{
mRecvTextEdit->addWarning("这是要发给谁?");
}

return fellow;
}

void MainWindow::showResult(pair<bool, string> ret, const Content* content)
{
if (ret.first)
mRecvTextEdit->addMyContent(content, QDateTime::currentDateTime().currentMSecsSinceEpoch());
else
mRecvTextEdit->addWarning(ret.second.c_str());
}

void MainWindow::onStateChanged(FileTask *fileTask)
{
if (mDownloadFileDlg->isVisible())
{
emit statChanged(fileTask);
}
}

void MainWindow::onProgress(FileTask *fileTask)
{
if (mDownloadFileDlg->isVisible())
{
emit progressChanged(fileTask);
}
}

void MainWindow::onEvent(shared_ptr<ViewEvent> event)
{
emit feiqViewEvent(event);
}

void MainWindow::onShowErrorAndQuit(const QString &text)
{
QMessageBox::warning(this, "出错了,为什么?你猜!", text, "退出应用");

QApplication::exit(-1);
}

void MainWindow::handleFeiqViewEvent(shared_ptr<ViewEvent> event)
{
if (event->what == ViewEventType::FELLOW_UPDATE)
{
auto e = static_cast<FellowViewEvent*>(event.get());
mFellowList.update(*(e->fellow.get()));
}
else if (event->what == ViewEventType::SEND_TIMEO || event->what == ViewEventType::MESSAGE)
{
//地球人都知道这个分支中的ViewEvent集成自FellowViewEvent
auto e = static_cast<FellowViewEvent*>(event.get());
auto fellow = e->fellow.get();

if (isActiveWindow())
{//窗口可见,处理当前用户消息,其他用户消息则放入通知队列
if (fellow == mRecvTextEdit->curFellow())
{
readEvent(event.get());
}
else
{
mUnreadEvents[fellow].push_back(event);
updateUnread(fellow);
}
}
else
{//窗口不可见,放入未读队列并通知
mUnreadEvents[fellow].push_back(event);
updateUnread(fellow);
notifyUnread(event.get());
}
}
}

void MainWindow::refreshFellowList()
{
mFeiq.sendImOnLine();
}

void MainWindow::addFellow()
{
AddFellowDialog dlg(this);
if (dlg.exec() == QDialog::Accepted)
{
auto ip = dlg.getIp();
userAddFellow(ip);
}
}

void MainWindow::openChooseEmojiDlg()
{
mChooseEmojiDlg->exec();
}

void MainWindow::sendFiles(QList<QFileInfo> files)
{
auto fellow = checkCurFellow();
if (!fellow)
return;

for (auto file : files)
{
if (file.isFile())
{
sendFile(file.absoluteFilePath().toStdString());
}
else
{
mRecvTextEdit->addWarning("不支持发送:"+file.absoluteFilePath());
}
}
}

void MainWindow::userAddFellow(QString ip)
{
//创建好友
auto fellow = make_shared<Fellow>();
fellow->setIp(ip.toStdString());
fellow->setOnLine(true);
mFeiq.getModel().addFellow(fellow);

//添加到列表
auto& ref = *(fellow.get());
mFellowList.update(ref);
mFellowList.top(ref);

//发送在线
mFeiq.sendImOnLine(fellow->getIp());
}

void MainWindow::notifyUnread(const ViewEvent *event)
{
if (event->what == ViewEventType::SEND_TIMEO)
{
auto e = static_cast<const SendTimeoEvent*>(event);
auto fellow = e->fellow.get();
showNotification(fellow, "发送超时:"+simpleTextOf(e->content.get()));
}
else if (event->what == ViewEventType::MESSAGE)
{
auto e = static_cast<const MessageViewEvent*>(event);
auto fellow = e->fellow.get();
for (auto content : e->contents)
{
showNotification(fellow, simpleTextOf(content.get()));
}
}
}

void MainWindow::showNotification(const Fellow *fellow, const QString &text)
{
QString content(text);
if (content.length()>20)
content = content.left(20)+"...";
PlatformDepend::instance().showNotify(QString(fellow->getName().c_str())+":", content);
}

void MainWindow::navigateToFileTask(IdType packetNo, IdType fileId, bool upload)
{
auto task = mFeiq.getModel().findTask(packetNo, fileId, upload ? FileTaskType::Upload : FileTaskType::Download);
openDownloadDlg();
mDownloadFileDlg->select(task.get());
}

void MainWindow::sendFile(std::string filepath)
{
auto content = FileContent::createFileContentToSend(filepath);
auto fellow = checkCurFellow();
if (!fellow)
return;

if (content == nullptr)
{
mRecvTextEdit->addWarning("获取文件"+QString(filepath.c_str())+"的信息失败,不发送");
}
else
{
auto fileContent = shared_ptr<FileContent>(std::move(content));
auto ret = mFeiq.send(fellow, fileContent);
showResult(ret, fileContent.get());
}
}

void MainWindow::sendFile()
{
auto fellow = checkCurFellow();
if (!fellow)
return;

//文件多选
QFileDialog fdlg(this, "选择要发送的文件");
fdlg.setFileMode(QFileDialog::ExistingFiles);
if (fdlg.exec() == QDialog::Accepted)
{
auto list = fdlg.selectedFiles();
auto count = list.count();
for (int i = 0; i < count; i++)
{
auto path = list.at(i);
sendFile(path.toStdString());
}
}
}

void MainWindow::sendKnock()
{
auto fellow = checkCurFellow();
if (fellow)
{
auto content = make_shared<KnockContent>();
auto ret = mFeiq.send(fellow, content);
showResult(ret, content.get());
}
}

void MainWindow::sendText()
{
auto text = mSendTextEdit->toPlainText();
if (text.isEmpty())
{
mRecvTextEdit->addWarning("发送空文本是不科学的,驳回");
return;
}


auto fellow = checkCurFellow();
if (fellow)
{
auto content = make_shared<TextContent>();
content->text = text.toStdString();
auto ret = mFeiq.send(fellow, content);
showResult(ret, content.get());
mSendTextEdit->clear();
}
}

void MainWindow::finishSearch(const Fellow *fellow)
{
mFellowList.top(*fellow);
openChartTo(fellow);
}

void MainWindow::openSettings()
{
QMessageBox::information(this, "设置", "设置文件在:"+mSettings->fileName()+"\n重启后生效",
QMessageBox::Ok);
}

void MainWindow::openSearchDlg()
{
mSearchFellowDlg->exec();
}

void MainWindow::openDownloadDlg()
{
mDownloadFileDlg->show();
mDownloadFileDlg->raise();
}

vector<const Fellow *> MainWindow::fellowSearchDriver(const QString &text)
{
auto fellows = mFeiq.getModel().searchFellow(text.toStdString());
vector<const Fellow*> result;
for (auto fellow : fellows)
{
result.push_back(fellow.get());
}
return result;
}

void MainWindow::initFeiq()
{
//配置飞秋
mFeiq.setMyName(mSettings->value("user/name").toString().toStdString());
mFeiq.setMyHost(mSettings->value("user/host","feiq by cy").toString().toStdString());

auto customGrroup = mSettings->value("network/custom_group", "").toString();
if (!customGrroup.isEmpty())
{
auto list = customGrroup.split("|");
for (int i = 0; i < list.size(); i++)
{
QString ipPrefix = list[i];
if (ipPrefix.endsWith("."))
{
for (int j = 2; j < 254; j++)
{
auto ip = ipPrefix+QString::number(j);
mFeiq.addToBroadcast(ip.toStdString());
}
}
}
}

mFeiq.setView(this);

mFeiq.enableIntervalDetect(60);

//启动飞秋
auto ret = mFeiq.start();
if (!ret.first)
{
emit showErrorAndQuit(ret.second.c_str());
}

qDebug()<<"feiq started";
}

void MainWindow::updateUnread(const Fellow *fellow)
{
auto it = mUnreadEvents.find(fellow);
if (it != mUnreadEvents.end())
{
auto count = (*it).second.size();
if (count == 0)
{
mFellowList.mark(*fellow, "");
}
else
{
mFellowList.mark(*fellow, QString::number(count));
}
}

setBadgeNumber(getUnreadCount());
}

int MainWindow::getUnreadCount()
{
auto begin = mUnreadEvents.begin();
auto end = mUnreadEvents.end();
auto count = 0;
for (auto it = begin; it != end; it++)
{
count += it->second.size();
}
return count;
}

void MainWindow::flushUnread(const Fellow *fellow)
{
auto it = mUnreadEvents.find(fellow);
if (it != mUnreadEvents.end())
{
auto& list = (*it).second;
while (!list.empty())
{
auto event = list.front();
readEvent(event.get());
list.pop_front();
}
}
}

void MainWindow::readEvent(const ViewEvent *event)
{
if (event->what == ViewEventType::SEND_TIMEO)
{
auto e = static_cast<const SendTimeoEvent*>(event);
auto simpleText = simpleTextOf(e->content.get());
if (simpleText.length()>20){
simpleText = simpleText.left(20)+"...";
}
mRecvTextEdit->addWarning("发送超时:"+simpleText);
}
else if (event->what == ViewEventType::MESSAGE)
{
auto e = static_cast<const MessageViewEvent*>(event);
auto time = e->when.time_since_epoch().count();
for (auto content : e->contents)
{
mRecvTextEdit->addFellowContent(content.get(), time);
}
}
}

void MainWindow::setBadgeNumber(int number)
{
PlatformDepend::instance().setBadgeNumber(number);
}

QString MainWindow::simpleTextOf(const Content *content)
{
switch (content->type()) {
case ContentType::Text:
return static_cast<const TextContent*>(content)->text.c_str();
break;
case ContentType::File:
return static_cast<const FileContent*>(content)->filename.c_str();
case ContentType::Knock:
return "窗口抖动";
default:
return "***";
break;
}
}

+ 100
- 0
mainwindow.h View File

@@ -0,0 +1,100 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "fellowlistwidget.h"
#include "searchfellowdlg.h"
#include "recvtextedit.h"
#include "feiqlib/feiqengine.h"
#include "filemanagerdlg.h"
#include <QSettings>
#include <unordered_map>
#include "chooseemojidlg.h"
#include <QFileInfo>
#include "sendtextedit.h"

using namespace std;

namespace Ui {
class MainWindow;
}

Q_DECLARE_METATYPE(shared_ptr<ViewEvent>)
class MainWindow : public QMainWindow, IFeiqView
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();

protected:
void enterEvent(QEvent *event);
void timerEvent(QTimerEvent *event);

signals:
void showErrorAndQuit(const QString& text);
void statChanged(FileTask* fileTask);
void progressChanged(FileTask* fileTask);
void feiqViewEvent(shared_ptr<ViewEvent> event);

private slots:
void finishSearch(const Fellow* fellow);
void openSettings();
void openSearchDlg();
void openDownloadDlg();
void onShowErrorAndQuit(const QString& text);
void navigateToFileTask(IdType packetNo, IdType fileId, bool upload);
void sendKnock();
void sendText();
void openChartTo(const Fellow* fellow);
void handleFeiqViewEvent(shared_ptr<ViewEvent> event);
void refreshFellowList();
void addFellow();
void openChooseEmojiDlg();
void sendFile();
void sendFile(string filepath);
void sendFiles(QList<QFileInfo> files);

private:
void userAddFellow(QString ip);
void notifyUnread(const ViewEvent* event);
void showNotification(const Fellow* fellow, const QString& text);
shared_ptr<Fellow> checkCurFellow();
void showResult(pair<bool, string> ret, const Content *content);
vector<const Fellow*> fellowSearchDriver(const QString& text);
void initFeiq();
void updateUnread(const Fellow* fellow);
int getUnreadCount();
void flushUnread(const Fellow* fellow);
void readEvent(const ViewEvent* event);
void setBadgeNumber(int number);
QString simpleTextOf(const Content* content);

// IFileTaskObserver interface
public:
void onStateChanged(FileTask *fileTask);
void onProgress(FileTask *fileTask);

// IFeiqView interface
public:
void onEvent(shared_ptr<ViewEvent> event);

private:
Ui::MainWindow *ui;
FellowListWidget mFellowList;
SearchFellowDlg* mSearchFellowDlg;
FileManagerDlg* mDownloadFileDlg;
ChooseEmojiDlg* mChooseEmojiDlg;
QSettings* mSettings;
FeiqEngine mFeiq;
RecvTextEdit* mRecvTextEdit;
SendTextEdit* mSendTextEdit;
QString mTitle;
unordered_map<const Fellow*, list<shared_ptr<ViewEvent>>> mUnreadEvents;

int mUnreadTimerInterval;
int mUnreadTimerId;
};

#endif // MAINWINDOW_H

+ 209
- 0
mainwindow.ui View File

@@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>744</width>
<height>576</height>
</rect>
</property>
<property name="font">
<font>
<family>Monaco</family>
</font>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>feiq</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="fellowListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="RecvTextEdit" name="recvEdit">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="SendTextEdit" name="sendEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>744</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="searchMenu">
<property name="title">
<string>好友</string>
</property>
<addaction name="actionSearchFellow"/>
<addaction name="actionRefreshFellows"/>
<addaction name="actionAddFellow"/>
</widget>
<widget class="QMenu" name="settingMenu">
<property name="title">
<string>设置</string>
</property>
<addaction name="actionSettings"/>
</widget>
<widget class="QMenu" name="menuDownload">
<property name="title">
<string>下载</string>
</property>
<addaction name="actionOpendl"/>
</widget>
<widget class="QMenu" name="menuEdit">
<property name="title">
<string>编辑</string>
</property>
<addaction name="actionSendText"/>
<addaction name="actionSendKnock"/>
<addaction name="actionSendFile"/>
<addaction name="actionInsertEmoji"/>
</widget>
<addaction name="searchMenu"/>
<addaction name="settingMenu"/>
<addaction name="menuDownload"/>
<addaction name="menuEdit"/>
</widget>
<action name="actionSearchFellow">
<property name="text">
<string>查找</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionSettings">
<property name="text">
<string>打开设置文件</string>
</property>
</action>
<action name="actionOpendl">
<property name="text">
<string>打开文件管理对话框</string>
</property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</action>
<action name="actionSendText">
<property name="text">
<string>发送文本消息</string>
</property>
<property name="shortcut">
<string>Ctrl+Return</string>
</property>
</action>
<action name="actionSendKnock">
<property name="text">
<string>发送窗口抖动</string>
</property>
</action>
<action name="actionSendFile">
<property name="text">
<string>发送文件</string>
</property>
</action>
<action name="actionRefreshFellows">
<property name="text">
<string>刷新</string>
</property>
</action>
<action name="actionAddFellow">
<property name="text">
<string>手动添加</string>
</property>
</action>
<action name="actionInsertEmoji">
<property name="text">
<string>插入表情</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>RecvTextEdit</class>
<extends>QTextEdit</extends>
<header>recvtextedit.h</header>
</customwidget>
<customwidget>
<class>SendTextEdit</class>
<extends>QTextEdit</extends>
<header>sendtextedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

+ 14
- 0
osx/notification.h View File

@@ -0,0 +1,14 @@
#ifndef OSXNOTIFICATION_H
#define OSXNOTIFICATION_H

#include <QString>

class Notification
{
public:
explicit Notification();
void show(const QString& title, const QString& content);
void hideAll();
};

#endif // OSXNOTIFICATION_H

+ 22
- 0
osx/notification.mm View File

@@ -0,0 +1,22 @@
#include "notification.h"

#import <cocoa/Cocoa.h>

Notification::Notification()
{

}

void Notification::show(const QString &title, const QString &content)
{
NSUserNotification *userNotification = [[[NSUserNotification alloc] init] autorelease];
userNotification.title = title.toNSString();
userNotification.informativeText = content.toNSString();
NSUserNotificationCenter* center = [NSUserNotificationCenter defaultUserNotificationCenter];
[center deliverNotification:userNotification];
}

void Notification::hideAll()
{
[[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
}

+ 25
- 0
osx/osxplatform.cpp View File

@@ -0,0 +1,25 @@
#include "osxplatform.h"
#include <QtMac>

OsxPlatform::OsxPlatform()
{

}

void OsxPlatform::showNotify(const QString &title, const QString &content)
{
mNotify.show(title, content);
}

void OsxPlatform::hideAllNotify()
{
mNotify.hideAll();
}

void OsxPlatform::setBadgeNumber(int number)
{
if (number == 0)
QtMac::setBadgeLabelText("");
else
QtMac::setBadgeLabelText(QString::number(number));
}

+ 22
- 0
osx/osxplatform.h View File

@@ -0,0 +1,22 @@
#ifndef OSXPLATFORM_H
#define OSXPLATFORM_H

#include "../platformdepend.h"
#include "notification.h"

class OsxPlatform : public IPlatform
{
public:
OsxPlatform();

public:
void showNotify(const QString& title, const QString& content) override;
void hideAllNotify() override;

void setBadgeNumber(int number) override;

private:
Notification mNotify;
};

#endif // OSXPLATFORM_H

+ 59
- 0
platformdepend.cpp View File

@@ -0,0 +1,59 @@
#include "platformdepend.h"

#ifdef Q_OS_OSX
#include "osx/osxplatform.h"
#endif

class MockPlatform : public IPlatform
{
public:
void showNotify(const QString& title, const QString& content)
{
(void)title;
(void)content;
}
void hideAllNotify()
{

}

void setBadgeNumber(int number)
{
(void)number;
}
};

PlatformDepend::PlatformDepend()
{
#ifdef Q_OS_OSX
mImpl = new OsxPlatform();
#else
mImpl = new MockPlatform();
#endif
}

PlatformDepend::~PlatformDepend()
{
delete mImpl;
}

PlatformDepend &PlatformDepend::instance()
{
static PlatformDepend me;
return me;
}

void PlatformDepend::showNotify(const QString &title, const QString &content)
{
mImpl->showNotify(title, content);
}

void PlatformDepend::hideAllNotify()
{
mImpl->hideAllNotify();
}

void PlatformDepend::setBadgeNumber(int number)
{
mImpl->setBadgeNumber(number);
}

+ 35
- 0
platformdepend.h View File

@@ -0,0 +1,35 @@
#ifndef PLATFORMDEPEND_H
#define PLATFORMDEPEND_H

#include <QString>
class IPlatform
{
public:
virtual ~IPlatform(){}

virtual void showNotify(const QString& title, const QString& content) = 0;
virtual void hideAllNotify() = 0;

virtual void setBadgeNumber(int number) = 0;
};

class PlatformDepend : public IPlatform
{
private:
PlatformDepend();
~PlatformDepend();

public:
static PlatformDepend& instance();

public:
void showNotify(const QString& title, const QString& content) override;
void hideAllNotify() override;

void setBadgeNumber(int number) override;

private:
IPlatform* mImpl;
};

#endif // PLATFORMDEPEND_H

+ 57
- 0
readme.md View File

@@ -0,0 +1,57 @@
# 概览

这是基于qt实现的mac版飞秋。

mac下的“飞秋”大多数只是飞鸽传书协议,而且未发现令人满意的开源项目,所以基于c++与qt实现了基础的飞秋协议。

## 支持特性
* 收发文本、文件
* 可与飞秋互发表情
* 查找好友
* 窗口抖动
* 指定IP增加好友
* 可添加自定义网段穿透屏蔽了广播包的路由器
* 未读消息的好友自动置顶
* 定时更新好友列表
* 一些个性化设置

个性化设置(~/.feiq_setting.ini)包括:

```ini
[user]
name = CompileLife ;设置用户名
host = Niubility Macbook ;设置主机名

[app]
title = Feiq by CompileLife ;设置一个高端大气上档次的窗口标题名称,亮瞎围观你飞秋的人
unread_timer=600 ;启用定时提醒未读消息,间隔10分钟

[network]
custom_group=192.168.74.|192.168.82. ;设置一些广播包无法触及的子网,点号结束一个网段的定义,竖线分隔各个网段
```

## 计划中的特性
* 常用好友在好友列表中优先靠前排列

## 尚未支持的特性
* 设置、显示文本格式:挺鸡肋的,暂时没兴趣实现
* 图片收发:仅支持获取图片id,图片数据的协议未破解
* 文件夹收发:飞秋貌似使用了自定义的文件夹收发协议
* 日志:部分完成,日志功能我平时极少使用,后续版本不一定加入

## 一些BUG
* 在接收文本框中显示gif图:QTextEdit不支持GIF动画,只会显示第一帧;结合QMovie可能可以实现(有网友反馈会内存泄露)

# 开发者

界面的实现与飞秋协议部分是分离的。

feiqlib是通信、协议解析、mvc构架部分,基于c++ 11封装,仅适用unix代码。理论上可移植到任意的unix/linux系统上。

界面部分基于qt实现。使用了部分平台相关的特性,如在其他平台使用,可参考osx目录使用对应平台的native特性。

目前使用到的平台相关特性有:
1. mac dock上的badge文本(图标上的小红点)
2. mac 通知中心的通知消息

引用代码,请注明代码出处。

+ 216
- 0
recvtextedit.cpp View File

@@ -0,0 +1,216 @@
#include "recvtextedit.h"
#include <QDate>
#include "emoji.h"
#include <QMouseEvent>

RecvTextEdit::RecvTextEdit(QWidget *parent)
:QTextEdit(parent)
{
setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
}

void RecvTextEdit::mousePressEvent(QMouseEvent *e)
{
mPressedAnchor = (e->button() & Qt::LeftButton) ? anchorAt(e->pos()) : "";
QTextEdit::mousePressEvent(e);
}

void RecvTextEdit::mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() & Qt::LeftButton)
{
if (anchorAt(e->pos()) == mPressedAnchor && !mPressedAnchor.isEmpty())
parseLink(mPressedAnchor);
}
QTextEdit::mouseReleaseEvent(e);
}

void RecvTextEdit::addFellowContent(const Content *content, long long msSinceEpoch)
{
drawDaySeperatorIfNewDay(msSinceEpoch);

QString hint = "";
if (mFellow)
hint = mFellow->getName().c_str();
else
hint = "匿名";
hint = hint+" <font color=gray>"+ timeStr(msSinceEpoch)+"</font>";
moveCursor(QTextCursor::End);
insertHtml(hint);
append("");
showContent(content, false);
append("\n");
moveCursor(QTextCursor::End);
}

void RecvTextEdit::addMyContent(const Content *content, long long msSinceEpoch)
{
drawDaySeperatorIfNewDay(msSinceEpoch);

QString hint = "我 <font color=gray>"+timeStr(msSinceEpoch)+"</font>";
moveCursor(QTextCursor::End);
insertHtml(hint);
append("");
showContent(content, true);
append("\n");
moveCursor(QTextCursor::End);
}

void RecvTextEdit::setCurFellow(const Fellow *fellow)
{
if (mFellow)
mDocs[mFellow] = document()->clone();//document将被清除或删除了,需clone

auto it = mDocs.find(fellow);
if (it != mDocs.end())
{
setDocument((*it).second);
moveCursor(QTextCursor::End);
}
else
{
clear();
}

mFellow = fellow;
}

void RecvTextEdit::addWarning(const QString &warning)
{
auto align = alignment();
setAlignment(Qt::AlignCenter);
auto color = textColor();
setTextColor(QColor(128,128,128));
append(warning);
append("");//结束当前段落,否则下一行恢复对齐方式时会将刚append的内容左对齐
setAlignment(align);
setTextColor(color);
}

const Fellow *RecvTextEdit::curFellow()
{
return mFellow;
}

void RecvTextEdit::parseLink(const QString &link)
{
QStringList parts = link.split("_");
if (parts.count()<3)
return;

auto packetNo = parts.at(0).toLongLong();
auto fileId = parts.at(1).toLongLong();
bool upload = parts.at(2) == "up";

emit navigateToFileTask(packetNo, fileId, upload);
}

QString RecvTextEdit::timeStr(long long msSinceEpoch)
{
QDateTime time;
time.setMSecsSinceEpoch(msSinceEpoch);
return time.toString("MM-dd HH:mm:ss");
}

void RecvTextEdit::showContent(const Content *content, bool mySelf)
{
switch (content->type())
{
case ContentType::File:
showFile(static_cast<const FileContent*>(content), mySelf);
break;
case ContentType::Knock:
showKnock(static_cast<const KnockContent*>(content), mySelf);
break;
case ContentType::Image:
showImage(static_cast<const ImageContent*>(content));
break;
case ContentType::Text:
showText(static_cast<const TextContent*>(content));
break;
default:
showUnSupport();
break;
}
}

void RecvTextEdit::showFile(const FileContent *content, bool fromMySelf)
{
if (content->fileType == IPMSG_FILE_REGULAR)
{
stringstream ss;
ss<<"<a href="<<content->packetNo<<"_"<<content->fileId<<"_"<<(fromMySelf?"up":"down")<<">"
<<content->filename<<"("<<content->size<<")"
<<"</a>";
insertHtml(ss.str().c_str());
}
else
{
showUnSupport("对方发来非普通文件(可能是文件夹),收不来……");
}
}

void RecvTextEdit::showImage(const ImageContent *content)
{
showUnSupport("对方发来图片,来图片,图片,片……额~还不支持!");
}

void RecvTextEdit::showText(const TextContent *content)
{
insertHtml(textHtmlStr(content));
}

void RecvTextEdit::showKnock(const KnockContent *content, bool mySelf)
{
if (mySelf)
insertHtml("[发送了一个窗口抖动]");
else
insertHtml("[发来窗口抖动]");
}

void RecvTextEdit::showUnSupport(const QString& text)
{
QString t = text;
if (t.isEmpty())
t = "对方发来尚未支持的内容,无法显示";

insertHtml("<font color=\"red\">"+t+"</font>");
}

void RecvTextEdit::drawDaySeperatorIfNewDay(long long sinceEpoch)
{
QDateTime cur;
cur.setMSecsSinceEpoch(sinceEpoch);

if (mLastEdit > 0)
{
QDateTime last;
last.setMSecsSinceEpoch(mLastEdit);

if (last.daysTo(cur)>0)
{
addWarning("-----------------------------");
}
}

mLastEdit = sinceEpoch;
}

QString RecvTextEdit::textHtmlStr(const TextContent *content)
{
auto str = QString(content->text.c_str());
auto htmlStr = str.toHtmlEscaped();
htmlStr.replace("\r\n", "<br>");
htmlStr.replace("\r", "<br>");
htmlStr.replace("\n", "<br>");

for (auto i = 0; i < EMOJI_LEN; i++)
{
auto resName = QString(":/default/res/face/")+QString::number(i+1)+".gif";
auto emojiStr = g_emojis[i];
QString imgTag = "<img src=\""+resName+"\"/>";
htmlStr.replace(emojiStr, imgTag);
}

return htmlStr;
}

+ 52
- 0
recvtextedit.h View File

@@ -0,0 +1,52 @@
#ifndef RECVTEXTEDIT_H
#define RECVTEXTEDIT_H

#include "feiqlib/content.h"
#include "feiqlib/fellow.h"
#include <QObject>
#include <unordered_map>
#include <QTextEdit>

using namespace std;

class RecvTextEdit: public QTextEdit
{
Q_OBJECT
public:
RecvTextEdit(QWidget* parent = 0);

protected:
virtual void mousePressEvent(QMouseEvent *e) override;
virtual void mouseReleaseEvent(QMouseEvent *e) override;

public:
void addFellowContent(const Content* content, long long msSinceEpoch);
void addMyContent(const Content* content, long long msSinceEpoch);
void setCurFellow(const Fellow* fellow);
void addWarning(const QString& warning);
const Fellow* curFellow();

signals:
void navigateToFileTask(IdType packetNo, IdType fileId, bool upload);

private:
QString timeStr(long long msSinceEpoch);
void showContent(const Content* content, bool mySelf);
void showFile(const FileContent* content, bool fromMySelf);
void showImage(const ImageContent* content);
void showText(const TextContent* content);
void showKnock(const KnockContent* content, bool mySelf);
void showUnSupport(const QString &text = "");
void drawDaySeperatorIfNewDay(long long sinceEpoch);
QString textHtmlStr(const TextContent* content);
void parseLink(const QString& link);

private:
const Fellow* mFellow = nullptr;
unordered_map<const Fellow*, QTextDocument*> mDocs;
long long mLastEdit=0;
QString mPressedAnchor;

};

#endif // RECVTEXTEDIT_H

BIN
res/face/1.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.8 kB

BIN
res/face/10.gif View File

Before After
Width: 22  |  Height: 22  |  Size: 3.5 kB

BIN
res/face/11.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 3.7 kB

BIN
res/face/12.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 8.0 kB

BIN
res/face/13.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 2.2 kB

BIN
res/face/14.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.7 kB

BIN
res/face/15.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 4.0 kB

BIN
res/face/16.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 3.0 kB

BIN
res/face/17.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.6 kB

BIN
res/face/18.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.4 kB

BIN
res/face/19.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 3.4 kB

BIN
res/face/2.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.6 kB

BIN
res/face/20.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 8.1 kB

BIN
res/face/21.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 8.1 kB

BIN
res/face/22.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.8 kB

BIN
res/face/23.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.9 kB

BIN
res/face/24.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 3.0 kB

BIN
res/face/25.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 2.0 kB

BIN
res/face/26.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 2.3 kB

BIN
res/face/27.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 2.4 kB

BIN
res/face/28.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 4.0 kB

BIN
res/face/29.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 2.9 kB

BIN
res/face/3.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.8 kB

BIN
res/face/30.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 3.3 kB

BIN
res/face/31.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 5.7 kB

BIN
res/face/32.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 654 B

BIN
res/face/33.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 1.8 kB

BIN
res/face/34.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 5.2 kB

BIN
res/face/35.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 7.2 kB

BIN
res/face/36.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 4.3 kB

BIN
res/face/37.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 2.1 kB

BIN
res/face/38.gif View File

Before After
Width: 24  |  Height: 24  |  Size: 13 kB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save