commit 1f87aeb59f3b341db57eb1756bdf1c44a9e5aefe Author: chenyong Date: Sun Sep 25 18:55:44 2016 +0800 创建git diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa24b2e --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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 . + +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 +. + + 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 +. diff --git a/addfellowdialog.cpp b/addfellowdialog.cpp new file mode 100644 index 0000000..2e3729e --- /dev/null +++ b/addfellowdialog.cpp @@ -0,0 +1,41 @@ +#include "addfellowdialog.h" +#include "ui_addfellowdialog.h" +#include +#include + +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); +} diff --git a/addfellowdialog.h b/addfellowdialog.h new file mode 100644 index 0000000..6452b16 --- /dev/null +++ b/addfellowdialog.h @@ -0,0 +1,29 @@ +#ifndef ADDFELLOWDIALOG_H +#define ADDFELLOWDIALOG_H + +#include + +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 diff --git a/addfellowdialog.ui b/addfellowdialog.ui new file mode 100644 index 0000000..7f94902 --- /dev/null +++ b/addfellowdialog.ui @@ -0,0 +1,38 @@ + + + AddFellowDialog + + + + 0 + 0 + 400 + 45 + + + + 手动添加好友 + + + + + + + + + 请输入要添加的好友ip + + + + + + + 确定 + + + + + + + + diff --git a/chooseemojidlg.cpp b/chooseemojidlg.cpp new file mode 100644 index 0000000..ff64a33 --- /dev/null +++ b/chooseemojidlg.cpp @@ -0,0 +1,57 @@ +#include "chooseemojidlg.h" +#include "ui_chooseemojidlg.h" +#include +#include +#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]); +} diff --git a/chooseemojidlg.h b/chooseemojidlg.h new file mode 100644 index 0000000..da19773 --- /dev/null +++ b/chooseemojidlg.h @@ -0,0 +1,31 @@ +#ifndef CHOOSEEMOJIDLG_H +#define CHOOSEEMOJIDLG_H + +#include + +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 diff --git a/chooseemojidlg.ui b/chooseemojidlg.ui new file mode 100644 index 0000000..c37997d --- /dev/null +++ b/chooseemojidlg.ui @@ -0,0 +1,77 @@ + + + ChooseEmojiDlg + + + + 0 + 0 + 401 + 181 + + + + + 0 + 0 + + + + true + + + 选择要插入的表情 + + + + + 0 + 150 + 351 + 31 + + + + QFrame::Box + + + + + + + + + 0 + 0 + 401 + 151 + + + + background-image: url(:/default/res/face/page.bmp); + + + + + + 360 + 150 + 41 + 31 + + + + + + + + + + ChooseEmojiWidget + QLabel +
chooseemojiwidget.h
+
+
+ + +
diff --git a/chooseemojiwidget.cpp b/chooseemojiwidget.cpp new file mode 100644 index 0000000..15d726b --- /dev/null +++ b/chooseemojiwidget.cpp @@ -0,0 +1,49 @@ +#include "chooseemojiwidget.h" +#include +#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; +} diff --git a/chooseemojiwidget.h b/chooseemojiwidget.h new file mode 100644 index 0000000..646e8b4 --- /dev/null +++ b/chooseemojiwidget.h @@ -0,0 +1,24 @@ +#ifndef CHOOSEEMOJIWIDGET_H +#define CHOOSEEMOJIWIDGET_H + +#include + +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 diff --git a/default.qrc b/default.qrc new file mode 100644 index 0000000..efb68f1 --- /dev/null +++ b/default.qrc @@ -0,0 +1,102 @@ + + + res/icon.png + res/face/1.gif + res/face/2.gif + res/face/3.gif + res/face/4.gif + res/face/5.gif + res/face/6.gif + res/face/7.gif + res/face/8.gif + res/face/9.gif + res/face/10.gif + res/face/11.gif + res/face/12.gif + res/face/13.gif + res/face/14.gif + res/face/15.gif + res/face/16.gif + res/face/17.gif + res/face/18.gif + res/face/19.gif + res/face/20.gif + res/face/21.gif + res/face/22.gif + res/face/23.gif + res/face/24.gif + res/face/25.gif + res/face/26.gif + res/face/27.gif + res/face/28.gif + res/face/29.gif + res/face/30.gif + res/face/31.gif + res/face/32.gif + res/face/33.gif + res/face/34.gif + res/face/35.gif + res/face/36.gif + res/face/37.gif + res/face/38.gif + res/face/39.gif + res/face/40.gif + res/face/41.gif + res/face/42.gif + res/face/43.gif + res/face/44.gif + res/face/45.gif + res/face/46.gif + res/face/47.gif + res/face/48.gif + res/face/49.gif + res/face/50.gif + res/face/51.gif + res/face/52.gif + res/face/53.gif + res/face/54.gif + res/face/55.gif + res/face/56.gif + res/face/57.gif + res/face/58.gif + res/face/59.gif + res/face/60.gif + res/face/61.gif + res/face/62.gif + res/face/63.gif + res/face/64.gif + res/face/65.gif + res/face/66.gif + res/face/67.gif + res/face/68.gif + res/face/69.gif + res/face/70.gif + res/face/71.gif + res/face/72.gif + res/face/73.gif + res/face/74.gif + res/face/75.gif + res/face/76.gif + res/face/77.gif + res/face/78.gif + res/face/79.gif + res/face/80.gif + res/face/81.gif + res/face/82.gif + res/face/83.gif + res/face/84.gif + res/face/85.gif + res/face/86.gif + res/face/87.gif + res/face/88.gif + res/face/89.gif + res/face/90.gif + res/face/91.gif + res/face/92.gif + res/face/93.gif + res/face/94.gif + res/face/95.gif + res/face/96.gif + res/face/page.bmp + + diff --git a/downloadfiledlg.ui b/downloadfiledlg.ui new file mode 100644 index 0000000..2e348a5 --- /dev/null +++ b/downloadfiledlg.ui @@ -0,0 +1,130 @@ + + + DownloadFileDlg + + + + 0 + 0 + 542 + 324 + + + + 文件管理 + + + + 4 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + 清除 + + + + + + + 删除 + + + + + + + 保存 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 5 + + + false + + + 70 + + + true + + + false + + + + 类型 + + + + + 状态 + + + + + 好友 + + + + + 文件名 + + + + + 进度 + + + + + + + + + + + + diff --git a/emoji.cpp b/emoji.cpp new file mode 100644 index 0000000..3de1c3d --- /dev/null +++ b/emoji.cpp @@ -0,0 +1,209 @@ +#include "emoji.h" + +const char* g_emojis[EMOJI_LEN]={ + "/:)", + "/:~", + "/:*", + "/:|", + "/8-)", + "/:<", + "/:$", + "/:X", + "/:Z", + "/:'(", + "/:-|", + "/:@", + "/:P", + "/:D", + "/:O", + "/", + + "/:(", + "/:+", + "/: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", + "爱情" +}; diff --git a/emoji.h b/emoji.h new file mode 100644 index 0000000..a9faacf --- /dev/null +++ b/emoji.h @@ -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 diff --git a/feiq.pro b/feiq.pro new file mode 100644 index 0000000..2c96e59 --- /dev/null +++ b/feiq.pro @@ -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 diff --git a/feiqlib/asynwait.cpp b/feiqlib/asynwait.cpp new file mode 100644 index 0000000..4483563 --- /dev/null +++ b/feiqlib/asynwait.cpp @@ -0,0 +1,75 @@ +#include "asynwait.h" +#include +#include + +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(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 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); + } + } +} + diff --git a/feiqlib/asynwait.h b/feiqlib/asynwait.h new file mode 100644 index 0000000..e820b86 --- /dev/null +++ b/feiqlib/asynwait.h @@ -0,0 +1,42 @@ +#ifndef ASYNWAIT_H +#define ASYNWAIT_H + +#include "uniqueid.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +class AsynWait +{ +public: + typedef function 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 timeo; + }; + + list mWaitPacks; + mutex mPacksMutex; + bool mStarted=false; + int mPrecision; + thread mThd; +}; + +#endif // ASYNWAIT_H diff --git a/feiqlib/content.h b/feiqlib/content.h new file mode 100644 index 0000000..9ec0465 --- /dev/null +++ b/feiqlib/content.h @@ -0,0 +1,170 @@ +#ifndef CONTENT_H +#define CONTENT_H + +#include +#include "protocol.h" +#include "uniqueid.h" +#include +#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 createFileContentToSend(const string& filePath) + { + static UniqueId mFileId; + struct stat fInfo; + auto ret = stat(filePath.c_str(), &fInfo); + if (ret != 0) + return nullptr; + + unique_ptr 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 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(ptr); + } +}; + +#endif // CONTENT_H diff --git a/feiqlib/defer.cpp b/feiqlib/defer.cpp new file mode 100644 index 0000000..b37fe1e --- /dev/null +++ b/feiqlib/defer.cpp @@ -0,0 +1,13 @@ +#include "defer.h" + +Defer::Defer(function deleter) + :mDeleter(deleter) +{ + +} + +Defer::~Defer() +{ + if (mDeleter) + mDeleter(); +} diff --git a/feiqlib/defer.h b/feiqlib/defer.h new file mode 100644 index 0000000..081274a --- /dev/null +++ b/feiqlib/defer.h @@ -0,0 +1,16 @@ +#ifndef DEFER_H +#define DEFER_H + +#include +using namespace std; + +class Defer +{ +public: + Defer(function deleter); + ~Defer(); +private: + function mDeleter; +}; + +#endif // DEFER_H diff --git a/feiqlib/encoding.cpp b/feiqlib/encoding.cpp new file mode 100644 index 0000000..e3a022d --- /dev/null +++ b/feiqlib/encoding.cpp @@ -0,0 +1,73 @@ +#include "encoding.h" +#include +#include +#include +#include +#include + +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 Encoding::convert(const vector &str) +{ + vector 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 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(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; +} diff --git a/feiqlib/encoding.h b/feiqlib/encoding.h new file mode 100644 index 0000000..485057f --- /dev/null +++ b/feiqlib/encoding.h @@ -0,0 +1,34 @@ +#ifndef ENCODING_H +#define ENCODING_H + +#include +#include +using namespace std; +#include + +class Encoding +{ +public: + Encoding(const string& fromCharset, const string& toCharset); + ~Encoding(); + vector convert(const vector& 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 diff --git a/feiqlib/feiqcommu.cpp b/feiqlib/feiqcommu.cpp new file mode 100644 index 0000000..774765d --- /dev/null +++ b/feiqlib/feiqcommu.cpp @@ -0,0 +1,289 @@ +#include "feiqcommu.h" +#include "udpcommu.h" +#include "ipmsg.h" +#include +#include "fellow.h" +#include +#include +#include +#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 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 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< FeiqCommu::requestFileData(const string &ip, + const FileContent& file, int offset) +{ + unique_ptr 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 &data) +{ + auto post = make_shared(); + 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 FeiqCommu::pack(SendProtocol &sender, IdType* packetId) +{ + //搜集数据 + char sep = HLIST_ENTRY_SEPARATOR; + auto packetNo = mPacketNo.get(); + auto cmdId = sender.cmdId(); + + //拼接消息头 + stringstream os; + os< buf(len); + os.read(buf.data(), len); + + if (packetId != nullptr) + *packetId = packetNo; + return buf; +} + +void FeiqCommu::onTcpClientConnected(int socket) +{ + if (mFileServerHandler) + { + //接收请求 + unique_ptr client(new TcpSocket(socket)); + std::array buf; + int ret = client->recv(buf.data(), MAX_RCV_SIZE); + if (ret <= 0) + return; + + //解析请求 + vector 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& data, Post& post) +{ + auto ptr = data.begin(); + auto last = data.end(); + + //取出协议的前5项 + array 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 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(); + 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 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; +} + diff --git a/feiqlib/feiqcommu.h b/feiqlib/feiqcommu.h new file mode 100644 index 0000000..a8170f5 --- /dev/null +++ b/feiqlib/feiqcommu.h @@ -0,0 +1,86 @@ +#ifndef FEIQCOMMU_H +#define FEIQCOMMU_H + +#include +#include +#include +#include +#include +#include "post.h" +#include "protocol.h" +#include "udpcommu.h" +#include +#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, 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 start(); + void stop(); + + /** + * @brief send 发送sender打包的内容 + * @param ip 发给谁 + * @param sender 要发送什么 + * @return 发送成功,返回发送包ID,否则返回-1,并设置失败原因 + */ + pair send(const string& ip, SendProtocol& sender); + + /** + * @brief requestFileData 请求好友开始发送文件数据 + * @param ip 向谁请求 + * @param file 要请求的文件 + * @return 如果请求成功,返回tcp连接,据此获取数据,否则返回nullptr + */ + unique_ptr requestFileData(const string& ip, const FileContent &file, int offset); + + /** + * @brief setFileServerHandler 设置文件服务的处理 + * @param fileServerHandler 参数:客户端socket连接,请求的文件id,请求的数据偏移 + */ + void setFileServerHandler(FileServerHandler fileServerHandler); +public: + static bool dumpRaw(vector &data, Post &post); + static VersionInfo dumpVersionInfo(const string& version); +private: + void onRecv(const string& ip, vector &data); + vector pack(SendProtocol& sender, IdType *packetId = nullptr); + void onTcpClientConnected(int socket); +private: + vector mRecvPrtocols; + UdpCommu mUdp; + string mHost=""; + string mName=""; + string mVersion=""; + UniqueId mPacketNo; + string mMac; + TcpServer mTcpServer; + FileServerHandler mFileServerHandler; +}; + +#endif // FEIQCOMMU_H diff --git a/feiqlib/feiqengine.cpp b/feiqlib/feiqengine.cpp new file mode 100644 index 0000000..7b99ad2 --- /dev/null +++ b/feiqlib/feiqengine.cpp @@ -0,0 +1,934 @@ +#include "feiqengine.h" +#include "protocol.h" +#include "ipmsg.h" +#include +#include "utils.h" +#include +#include "defer.h" +#include +#include +#include +#include + +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(mContent); + if (content->format.empty()) + { + os<convert(content->text); + } + else + { + os<convert(content->text) + <<"{" + <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(mContent); + char sep = HLIST_ENTRY_SEPARATOR; + auto filename = content->filename; + stringReplace(filename, ":", "::");//估摸着协议不会变,偷懒下 + os<<(char)0 + <fileId) + <convert(filename) + <size + <modifyTime + <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<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<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<convert(mName); + } +private: + const string& mName; +}; + +//定义触发器 +typedef std::function post)> OnPostReady; +#define DECLARE_TRIGGER(name)\ + public:\ + name(OnPostReady trigger) : mTrigger(trigger){}\ + private:\ + OnPostReady mTrigger;\ + void trigger(shared_ptr post){mTrigger(post);} + +/** + * @brief The RecvAnsEntry class 好友响应我们的上线消息 + */ +class RecvAnsEntry : public RecvProtocol +{ + DECLARE_TRIGGER(RecvAnsEntry) +public: + bool read(shared_ptr 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) + { + 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) + { + 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) + { + if (IS_CMD_SET(post->cmdId, IPMSG_KNOCK)) + { + post->contents.push_back(make_shared()); + } + return false; + } +}; +/** + * @brief The AnsSendCheck class + */ +class RecvSendCheck : public RecvProtocol +{ + DECLARE_TRIGGER(RecvSendCheck) +public: + bool read(shared_ptr 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) + { + 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) + { + 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(std::move(content))); + } + + return false; + } +private: + unique_ptr createTextContent(const string& raw) + { + auto content = unique_ptr(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) + { + 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(std::move(content))); + + found = ++endTask; + } + + return false; + } +private: + unique_ptr createFileContent(vector::iterator from, + vector::iterator to) + { + unique_ptr 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) + { + cout<<"==========================="<cmdId<from->toString()<extra){ + cout<= 8){ + cout< post) + { + if (post->cmdId == IPMSG_RECVMSG) + { + IdType id = static_cast(stoll(toString(post->extra))); + auto content = make_shared(); + content->id = id; + post->addContent(content); + trigger(post); + return true; + } + return false; + } +}; + +class RecvImage : public RecvProtocol +{ +public: + bool read(shared_ptr 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(); + 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) + { + if (!post->contents.empty()) + trigger(post); + return true; + } +}; + +//添加一条接收协议,触发时更新好友信息,并调用func +#define ADD_RECV_PROTOCOL(protocol, func)\ +{\ + RecvProtocol* p = new protocol([this](shared_ptr post){\ + post->from = this->addOrUpdateFellow(post->from);\ + this->func(post);});\ + mRecvProtocols.push_back(unique_ptr(p));\ + mCommu.addRecvProtocol(p);\ + } + +//添加一条接收协议,无触发 +#define ADD_RECV_PROTOCOL2(protocol)\ +{\ + RecvProtocol* p = new protocol();\ + mRecvProtocols.push_back(unique_ptr(p));\ + mCommu.addRecvProtocol(p);\ +} + +//添加一条接收协议,触发时更新好友信息 +#define ADD_RECV_PROTOCOL3(protocol)\ +{\ + RecvProtocol* p = new protocol([this](shared_ptr post){\ + post->from = this->addOrUpdateFellow(post->from);});\ + mRecvProtocols.push_back(unique_ptr(p));\ + mCommu.addRecvProtocol(p);\ +} + +//添加一条发送协议 +#define ADD_SEND_PROTOCOL(protocol, sender, args...)\ +{\ + mContentSender[protocol]=make_shared(##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 FeiqEngine::send(shared_ptr fellow, shared_ptr 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(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 FeiqEngine::sendFiles(shared_ptr fellow, list> &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"<size; + std::array 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 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) +{ + AnsBrEntry ans(mName); + mCommu.send(post->from->getIp(), ans); +} + +void FeiqEngine::onMsg(shared_ptr post) +{ + static vector rejectedImages; + + auto event = make_shared(); + 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(*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((*it).get()); + string begin = "/~#>"; + string end = "text, begin) && endsWith(tc->text, end)) + { + rejected=true; + } + } + else if ((*it)->type() == ContentType::Image) + { + //这个包还没被拒绝过,发送拒绝消息 + auto ic = static_cast((*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) +{ + SendSentCheck reply(post->packetNo); + mCommu.send(post->from->getIp(), reply); +} + +void FeiqEngine::onReadCheck(shared_ptr post) +{ + SendReadCheck reply(post->packetNo); + mCommu.send(post->from->getIp(), reply); +} + +void FeiqEngine::onSendTimeo(IdType packetId, const string& ip, shared_ptr content) +{ + auto event = make_shared(); + event->fellow = mModel.findFirstFellowOf(ip); + if (event->fellow == nullptr) + return; + + event->content = content; + mMsgThd.sendMessage(event); +} + +void FeiqEngine::onReadMessage(shared_ptr post) +{ + if (post->contents.empty()) + return; + auto content = dynamic_pointer_cast(post->contents[0]); + mAsyncWait.clearWaitPack(content->id); +} + +void FeiqEngine::fileServerHandler(unique_ptr client, int packetNo, int fileId, int offset) +{ + auto task = mModel.findTask(packetNo, fileId); + if (task == nullptr) + return; + + auto func = [task, offset](unique_ptr 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 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 FeiqEngine::addOrUpdateFellow(shared_ptr 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(); + event->what = ViewEventType::FELLOW_UPDATE; + event->fellow = f; + event->when = Post::now(); + mMsgThd.sendMessage(event); + } + + return f; +} + +void FeiqEngine::dispatchMsg(shared_ptr msg) +{ + mView->onEvent(msg); +} + +void FeiqEngine::broadcastToCurstomGroup(SendProtocol &protocol) +{ + for (auto ip : mBroadcast) + { + if (!mStarted) + break;//发送过程是一个耗时网络操作,如果已经stop,则中断 + + mCommu.send(ip, protocol); + } +} diff --git a/feiqlib/feiqengine.h b/feiqlib/feiqengine.h new file mode 100644 index 0000000..bc7b7ac --- /dev/null +++ b/feiqlib/feiqengine.h @@ -0,0 +1,92 @@ +#ifndef FEIQENGINE_H +#define FEIQENGINE_H + +#include "content.h" +#include "feiqcommu.h" +#include +#include +#include +#include +#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 send(shared_ptr fellow, shared_ptr content); + pair sendFiles(shared_ptr fellow, list > &files); + bool downloadFile(FileTask* task); + +public: + pair 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); + void onBrEntry(shared_ptr post); + void onBrExit(shared_ptr post); + void onMsg(shared_ptr post); + void onSendCheck(shared_ptr post); + void onReadCheck(shared_ptr post); + void onSendTimeo(IdType packetId, const string &ip, shared_ptr content); + void onReadMessage(shared_ptr post); + +private: + void fileServerHandler(unique_ptr client, int packetNo, int fileId, int offset); + +private: + shared_ptr addOrUpdateFellow(shared_ptr fellow); + void dispatchMsg(shared_ptr msg); + void broadcastToCurstomGroup(SendProtocol& protocol); + +private: + FeiqCommu mCommu; + vector> mRecvProtocols; + FeiqModel mModel; + string mHost; + string mName; + MsgQueueThread mMsgThd; + IFeiqView* mView; + vector mBroadcast; + bool mStarted=false; + AsynWait mAsyncWait;//异步等待对方回包 + + struct EnumClassHash + { + template + std::size_t operator()(T t) const + { + return static_cast(t); + } + }; + //可以用unique_ptr,但是unique_ptr要求知道具体定义 + unordered_map, EnumClassHash> mContentSender; +}; + +#endif // FEIQENGINE_H diff --git a/feiqlib/feiqmodel.cpp b/feiqlib/feiqmodel.cpp new file mode 100644 index 0000000..7ef2610 --- /dev/null +++ b/feiqlib/feiqmodel.cpp @@ -0,0 +1,119 @@ +#include "feiqmodel.h" +#include + +FeiqModel::FeiqModel() +{ + +} + +void FeiqModel::addFellow(shared_ptr fellow) +{ + lock_guard guard(mFellowLock); + mFellows.push_back(fellow); +} + +shared_ptr FeiqModel::getFullInfoOf(shared_ptr fellow) +{ + lock_guard guard(mFellowLock); + auto predict = [&fellow](shared_ptr tmp){return fellow->isSame(*tmp);}; + auto found = std::find_if(mFellows.begin(), mFellows.end(), predict); + return found == mFellows.end() ? nullptr : *found; +} + +shared_ptr FeiqModel::findFirstFellowOf(const string &ip) +{ + lock_guard guard(mFellowLock); + auto predict = [&ip](shared_ptr tmp){return tmp->getIp() == ip;}; + auto found = std::find_if(mFellows.begin(), mFellows.end(), predict); + return found == mFellows.end() ? nullptr : *found; +} + +list > FeiqModel::searchFellow(const string &text) +{ + lock_guard guard(mFellowLock); + list> fellows; + if (text.empty()) + { + fellows = mFellows; + } + else + { + for (shared_ptr 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 FeiqModel::getShared(const Fellow *fellow) +{ + if (fellow == nullptr) + return nullptr; + + lock_guard guard(mFellowLock); + for (shared_ptr f : mFellows) + { + if (f.get() == fellow) + return f; + } + + return nullptr; +} + +shared_ptr FeiqModel::addDownloadTask(shared_ptr fellow, shared_ptr fileContent) +{ + lock_guard guard(mFileTaskLock); + auto task = make_shared(fileContent, FileTaskType::Download); + task->setFellow(fellow); + mFileTasks.push_back(task); + return task; +} + +shared_ptr FeiqModel::addUploadTask(shared_ptr fellow, shared_ptr fileContent) +{ + lock_guard guard(mFileTaskLock); + auto task = make_shared(fileContent, FileTaskType::Upload); + task->setFellow(fellow); + mFileTasks.push_back(task); + return task; +} + +void FeiqModel::removeFileTask(function predict) +{ + lock_guard g(mFileTaskLock); + mFileTasks.remove_if([predict](shared_ptr t){ + return predict(*t); + }); +} + +shared_ptr FeiqModel::findTask(IdType packetNo, IdType fileId, FileTaskType type) +{ + lock_guard 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 > FeiqModel::searchTask(function predict) +{ + lock_guard g(mFileTaskLock); + list> allTask; + + for (auto task : mFileTasks) + if (predict(*(task.get()))) + allTask.push_back(task); + + return allTask; +} diff --git a/feiqlib/feiqmodel.h b/feiqlib/feiqmodel.h new file mode 100644 index 0000000..131f6cc --- /dev/null +++ b/feiqlib/feiqmodel.h @@ -0,0 +1,38 @@ +#ifndef FEIQMODEL_H +#define FEIQMODEL_H + +#include "fellow.h" +#include +#include +#include +#include "filetask.h" +#include "uniqueid.h" +using namespace std; + +class FeiqModel +{ +public: + FeiqModel(); + +public: + void addFellow(shared_ptr fellow); + shared_ptr getFullInfoOf(shared_ptr fellow); + shared_ptr findFirstFellowOf(const string& ip); + list> searchFellow(const string& text); + shared_ptr getShared(const Fellow* fellow); + +public: + shared_ptr addDownloadTask(shared_ptr fellow, shared_ptr fileContent); + shared_ptr addUploadTask(shared_ptr fellow, shared_ptr fileContent); + shared_ptr findTask(IdType packetNo, IdType fileId, FileTaskType type = FileTaskType::Upload); + list> searchTask(function predict); + void removeFileTask(function predict); + +private: + list> mFellows; + list> mFileTasks; + mutex mFellowLock; + mutex mFileTaskLock; +}; + +#endif // FEIQMODEL_H diff --git a/feiqlib/fellow.h b/feiqlib/fellow.h new file mode 100644 index 0000000..918afe4 --- /dev/null +++ b/feiqlib/fellow.h @@ -0,0 +1,104 @@ +#ifndef FELLOW_H +#define FELLOW_H + +#include +#include +#include +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="< 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) +{ + mFellow = fellow; +} + +void FileTask::cancel() +{ + mCancelPending=true; +} + +bool FileTask::hasCancelPending() +{ + return mCancelPending; +} + +shared_ptr 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 FileTask::getContent() const +{ + return mContent; +} + +FileTaskType FileTask::type() const +{ + return mType; +} diff --git a/feiqlib/filetask.h b/feiqlib/filetask.h new file mode 100644 index 0000000..cec65b6 --- /dev/null +++ b/feiqlib/filetask.h @@ -0,0 +1,64 @@ +#ifndef FILETASK_H +#define FILETASK_H + + +#include "content.h" +#include +#include +#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, FileTaskType type); + void setObserver(IFileTaskObserver* observer); +public: + void setProcess(int val); + void setState(FileTaskState val, const string& msg=""); + void setFellow(shared_ptr fellow); + void cancel(); + bool hasCancelPending(); +public: + shared_ptr fellow() const; + int getProcess() const; + FileTaskState getState() const; + string getDetailInfo() const; + shared_ptr getContent() const; + FileTaskType type() const; +private: + shared_ptr mFellow;//要发送给的用户,或文件来自该用户 + int mProcess=0; + FileTaskState mState = FileTaskState::NotStart; + shared_ptr mContent; + IFileTaskObserver* mObserver; + FileTaskType mType = FileTaskType::Upload; + string mMsg; + bool mCancelPending=false; + int mNotifySize; + int mLastProcess=0; +}; + +#endif // FILETASK_H diff --git a/feiqlib/history.cpp b/feiqlib/history.cpp new file mode 100644 index 0000000..d48d572 --- /dev/null +++ b/feiqlib/history.cpp @@ -0,0 +1,199 @@ +#include "history.h" +#include +#include "defer.h" +#include +#include +#include +#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":"<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 History::query(const string& selection, const vector& args) +{ + vector 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:"<(std::move(fellow)); + record.when = time_point(milliseconds(when)); + + Parcel parcel; + parcel.fillWith(contentData, contentLen); + parcel.resetForRead(); + record.what = ContentParcelFactory::createFromParcel(parcel); + + result.push_back(record); + } + } + + return result; +} + +unique_ptr History::getFellow(int id) +{ + +} + +int History::findFellowId(const string &ip) +{ + +} + diff --git a/feiqlib/history.h b/feiqlib/history.h new file mode 100644 index 0000000..40714c0 --- /dev/null +++ b/feiqlib/history.h @@ -0,0 +1,50 @@ +#ifndef HISTORY_H +#define HISTORY_H + +#include "fellow.h" +#include +#include +#include "content.h" +#include "post.h" +#include +#include + +using namespace std; + +struct HistoryRecord{ + time_point when; + shared_ptr who; + shared_ptr 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 query(const string& selection, const vector &args); +private: + unique_ptr getFellow(int id); + int findFellowId(const string& ip); +private: + sqlite3* mDb = nullptr; +}; + +#endif // HISTORY_H diff --git a/feiqlib/ifeiqview.h b/feiqlib/ifeiqview.h new file mode 100644 index 0000000..2feeacb --- /dev/null +++ b/feiqlib/ifeiqview.h @@ -0,0 +1,66 @@ +#ifndef IFEIQVIEW_H +#define IFEIQVIEW_H + +#include +#include +#include + +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; +}; + +class SendTimeoEvent : public FellowViewEvent +{ +public: + SendTimeoEvent(){ + what = ViewEventType::SEND_TIMEO; + } + + shared_ptr content; +}; + +class MessageViewEvent : public FellowViewEvent +{ +public: + MessageViewEvent(){ + what = ViewEventType::MESSAGE; + } + vector> contents; +}; + +class IFeiqView : public IFileTaskObserver +{ +public: + virtual ~IFeiqView(){} + virtual void onEvent(shared_ptr event) = 0; +}; + +#endif // IFEIQVIEW_H diff --git a/feiqlib/ipmsg.h b/feiqlib/ipmsg.h new file mode 100644 index 0000000..5d4f908 --- /dev/null +++ b/feiqlib/ipmsg.h @@ -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 diff --git a/feiqlib/msgqueuethread.cpp b/feiqlib/msgqueuethread.cpp new file mode 100644 index 0000000..a902c01 --- /dev/null +++ b/feiqlib/msgqueuethread.cpp @@ -0,0 +1,29 @@ +#include "msgqueuethread.h" +#include + +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 lock(mQueueMutex); + mQueueCnd.notify_all(); +} diff --git a/feiqlib/msgqueuethread.h b/feiqlib/msgqueuethread.h new file mode 100644 index 0000000..7b24838 --- /dev/null +++ b/feiqlib/msgqueuethread.h @@ -0,0 +1,82 @@ +#ifndef MSGQUEUETHREAD_H +#define MSGQUEUETHREAD_H + +#include +#include +#include +#include +#include +using namespace std; + +//TODO:实现移到cpp中 +template +class MsgQueueThread +{ + typedef function)> 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 lock(mQueueMutex); + while(!mQueue.empty()) + mQueue.pop(); + mQueueCnd.notify_all(); + lock.unlock(); + mThread.join(); + } + + void sendMessage(shared_ptr msg) + { + unique_lock lock(mQueueMutex); + mQueue.push(msg); + mQueueCnd.notify_one(); + } + +private: + void loop() + { + while (mRun) { + unique_lock 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> mQueue; + bool mRun=false; + Handler mHandler; + thread mThread; +}; + +#endif // MSGQUEUETHREAD_H diff --git a/feiqlib/parcelable.h b/feiqlib/parcelable.h new file mode 100644 index 0000000..fa95011 --- /dev/null +++ b/feiqlib/parcelable.h @@ -0,0 +1,145 @@ +#ifndef PARCELABLE_H +#define PARCELABLE_H + +#include +#include +#include + +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< + void write(const T& val){ + writePtr(&val, 1); + } + + template + void read(T& val){ + readPtr(&val); + } + + template + void writePtr(const T* ptr, int n){ + Head head{(int)(n * sizeof(T))}; + head.write(ss); + ss.write((const char*)ptr, head.size); + } + + template + void readPtr(T* ptr){ + Head head(ss); + unique_ptr 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 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(data), len); + } + +public: + streampos mark() + { + return ss.tellg(); + } + + void unmark(streampos markPos) + { + ss.seekg(markPos); + } + +public: + vector raw() + { + auto size = ss.tellp(); + resetForRead(); + + vector 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 diff --git a/feiqlib/post.h b/feiqlib/post.h new file mode 100644 index 0000000..00323ba --- /dev/null +++ b/feiqlib/post.h @@ -0,0 +1,47 @@ +#ifndef POST_H +#define POST_H + +#include +#include +#include +#include +#include "content.h" +#include + +using namespace std; +using namespace std::chrono; +class Fellow; + +/** + * @brief The Post struct + * 定义一次好友发来的请求/数据包 + */ +class Post +{ +public: + static time_point now(){ + return time_point_cast(system_clock::now()); + } + + decltype(now()) when = Post::now(); + vector extra; + + string packetNo; + IdType cmdId; + + shared_ptr from; + vector> contents; + + Post() + { + from = make_shared(); + } + + void addContent(shared_ptr content) + { + content->setPacketNo(packetNo); + contents.push_back(content); + } +}; + +#endif // POST_H diff --git a/feiqlib/protocol.h b/feiqlib/protocol.h new file mode 100644 index 0000000..f41e016 --- /dev/null +++ b/feiqlib/protocol.h @@ -0,0 +1,27 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include +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) = 0; +}; + +#endif // PROTOCOL_H diff --git a/feiqlib/tcpserver.cpp b/feiqlib/tcpserver.cpp new file mode 100644 index 0000000..643e30f --- /dev/null +++ b/feiqlib/tcpserver.cpp @@ -0,0 +1,84 @@ +#include "tcpserver.h" +#include +#include +#include +#include +#include +#include + +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); + } + } +} diff --git a/feiqlib/tcpserver.h b/feiqlib/tcpserver.h new file mode 100644 index 0000000..4b13eb9 --- /dev/null +++ b/feiqlib/tcpserver.h @@ -0,0 +1,23 @@ +#ifndef TCPSERVER_H +#define TCPSERVER_H + +#include + +class TcpServer +{ +public: + TcpServer(); + typedef std::function 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 diff --git a/feiqlib/tcpsocket.cpp b/feiqlib/tcpsocket.cpp new file mode 100644 index 0000000..1256ac8 --- /dev/null +++ b/feiqlib/tcpsocket.cpp @@ -0,0 +1,113 @@ +#include "tcpsocket.h" +#include +#include +#include +#include +#include +#include + +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(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; +} diff --git a/feiqlib/tcpsocket.h b/feiqlib/tcpsocket.h new file mode 100644 index 0000000..955660f --- /dev/null +++ b/feiqlib/tcpsocket.h @@ -0,0 +1,34 @@ +#ifndef TCPSOCKET_H +#define TCPSOCKET_H + +#include +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 diff --git a/feiqlib/udpcommu.cpp b/feiqlib/udpcommu.cpp new file mode 100644 index 0000000..0195a45 --- /dev/null +++ b/feiqlib/udpcommu.cpp @@ -0,0 +1,189 @@ +#include "udpcommu.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 data(std::begin(buf), std::begin(buf)+size); + mRecvHandler(ip, data); + } + + printf("end recv thread\n"); + mAsyncMode=false; +} diff --git a/feiqlib/udpcommu.h b/feiqlib/udpcommu.h new file mode 100644 index 0000000..aad3858 --- /dev/null +++ b/feiqlib/udpcommu.h @@ -0,0 +1,68 @@ +#ifndef UDPCOMMU_H +#define UDPCOMMU_H + +#include +#include +#include +using namespace std; + +#define MAX_RCV_SIZE 4096 +typedef function &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 diff --git a/feiqlib/uniqueid.cpp b/feiqlib/uniqueid.cpp new file mode 100644 index 0000000..44d3097 --- /dev/null +++ b/feiqlib/uniqueid.cpp @@ -0,0 +1,16 @@ +#include "uniqueid.h" +#include + +UniqueId::UniqueId() +{ + mId = 0; +} + +IdType UniqueId::get() +{ + auto id = ++mId; + if (id >= INT_MAX || id < 0) + mId=1; + + return mId; +} diff --git a/feiqlib/uniqueid.h b/feiqlib/uniqueid.h new file mode 100644 index 0000000..fd54ade --- /dev/null +++ b/feiqlib/uniqueid.h @@ -0,0 +1,21 @@ +#ifndef UNIQUEID_H +#define UNIQUEID_H + +#include +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 diff --git a/feiqlib/utils.cpp b/feiqlib/utils.cpp new file mode 100644 index 0000000..8fde770 --- /dev/null +++ b/feiqlib/utils.cpp @@ -0,0 +1,84 @@ +#include "utils.h" + +vector splitAllowSeperator(vector::iterator from, vector::iterator to, char sep) +{ + vector values; + vector 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 &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; +} diff --git a/feiqlib/utils.h b/feiqlib/utils.h new file mode 100644 index 0000000..5624012 --- /dev/null +++ b/feiqlib/utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +using namespace std; +vector splitAllowSeperator(vector::iterator from, vector::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& buf); +#endif // UTILS_H diff --git a/fellowlistwidget.cpp b/fellowlistwidget.cpp new file mode 100644 index 0000000..75870d8 --- /dev/null +++ b/fellowlistwidget.cpp @@ -0,0 +1,106 @@ +#include "fellowlistwidget.h" +#include + +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(item->data(Qt::UserRole).value()); + 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(widget->data(Qt::UserRole).value()); + if (f->getIp() == fellow.getIp()) + return widget; + } + + return nullptr; +} diff --git a/fellowlistwidget.h b/fellowlistwidget.h new file mode 100644 index 0000000..f53fd1d --- /dev/null +++ b/fellowlistwidget.h @@ -0,0 +1,37 @@ +#ifndef FELLOWLISTWIDGET_H +#define FELLOWLISTWIDGET_H + +#include "feiqlib/feiqmodel.h" +#include + +//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 diff --git a/filemanagerdlg.cpp b/filemanagerdlg.cpp new file mode 100644 index 0000000..6b6729e --- /dev/null +++ b/filemanagerdlg.cpp @@ -0,0 +1,230 @@ +#include "filemanagerdlg.h" +#include "ui_downloadfiledlg.h" +#include +#include +#include + +#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 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<getProcess()<<"/"<getContent()->size<<"("<taskTable->currentRow(); + auto widget = ui->taskTable->item(row, TYPE_COL); + if (widget == nullptr) + return nullptr; + + auto task = widget->data(Qt::UserRole); + return static_cast(task.value()); +} + +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(widget->data(Qt::UserRole).value()); + if (itemTask == task) + return i; + } + + return -1; +} + +void FileManagerDlg::showEvent(QShowEvent *) +{ + refresh(); +} diff --git a/filemanagerdlg.h b/filemanagerdlg.h new file mode 100644 index 0000000..3028991 --- /dev/null +++ b/filemanagerdlg.h @@ -0,0 +1,55 @@ +#ifndef DOWNLOADFILEDLG_H +#define DOWNLOADFILEDLG_H + +#include +#include +#include "feiqlib/filetask.h" +#include "feiqlib/feiqengine.h" + +using namespace std; + +namespace Ui { +class DownloadFileDlg; +} + + +class FileManagerDlg : public QDialog +{ + Q_OBJECT + + typedef function 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 diff --git a/icon.icns b/icon.icns new file mode 100644 index 0000000..3657c50 Binary files /dev/null and b/icon.icns differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..db382a1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,21 @@ +#include "mainwindow.h" +#include +#include + +//待测: +//*无当前交谈用户,且有未读消息时,未读消息能否正确置顶? + +//应用图标 +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占用功能 +//美化用户列表 diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..4b9c8b6 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,526 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "addfellowdialog.h" +#include +#include "platformdepend.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + qRegisterMetaType>("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)), this, SLOT(sendFiles(QList))); + + //初始化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)), this, SLOT(handleFeiqViewEvent(shared_ptr))); + + //后台初始化通信 + 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 MainWindow::checkCurFellow() +{ + auto fellow = mFeiq.getModel().getShared(mRecvTextEdit->curFellow()); + if (fellow == nullptr) + { + mRecvTextEdit->addWarning("这是要发给谁?"); + } + + return fellow; +} + +void MainWindow::showResult(pair 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 event) +{ + emit feiqViewEvent(event); +} + +void MainWindow::onShowErrorAndQuit(const QString &text) +{ + QMessageBox::warning(this, "出错了,为什么?你猜!", text, "退出应用"); + + QApplication::exit(-1); +} + +void MainWindow::handleFeiqViewEvent(shared_ptr event) +{ + if (event->what == ViewEventType::FELLOW_UPDATE) + { + auto e = static_cast(event.get()); + mFellowList.update(*(e->fellow.get())); + } + else if (event->what == ViewEventType::SEND_TIMEO || event->what == ViewEventType::MESSAGE) + { + //地球人都知道这个分支中的ViewEvent集成自FellowViewEvent + auto e = static_cast(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 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->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(event); + auto fellow = e->fellow.get(); + showNotification(fellow, "发送超时:"+simpleTextOf(e->content.get())); + } + else if (event->what == ViewEventType::MESSAGE) + { + auto e = static_cast(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(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(); + 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(); + 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 MainWindow::fellowSearchDriver(const QString &text) +{ + auto fellows = mFeiq.getModel().searchFellow(text.toStdString()); + vector 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(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(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(content)->text.c_str(); + break; + case ContentType::File: + return static_cast(content)->filename.c_str(); + case ContentType::Knock: + return "窗口抖动"; + default: + return "***"; + break; + } +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..0939ef5 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,100 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "fellowlistwidget.h" +#include "searchfellowdlg.h" +#include "recvtextedit.h" +#include "feiqlib/feiqengine.h" +#include "filemanagerdlg.h" +#include +#include +#include "chooseemojidlg.h" +#include +#include "sendtextedit.h" + +using namespace std; + +namespace Ui { +class MainWindow; +} + +Q_DECLARE_METATYPE(shared_ptr) +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 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 event); + void refreshFellowList(); + void addFellow(); + void openChooseEmojiDlg(); + void sendFile(); + void sendFile(string filepath); + void sendFiles(QList files); + +private: + void userAddFellow(QString ip); + void notifyUnread(const ViewEvent* event); + void showNotification(const Fellow* fellow, const QString& text); + shared_ptr checkCurFellow(); + void showResult(pair ret, const Content *content); + vector 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 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>> mUnreadEvents; + + int mUnreadTimerInterval; + int mUnreadTimerId; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..a839daf --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,209 @@ + + + MainWindow + + + + 0 + 0 + 744 + 576 + + + + + Monaco + + + + true + + + feiq + + + + + 0 + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Qt::ClickFocus + + + + + + + + + 0 + + + + + Qt::ClickFocus + + + true + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + 744 + 22 + + + + + 好友 + + + + + + + + 设置 + + + + + + 下载 + + + + + + 编辑 + + + + + + + + + + + + + + 查找 + + + Ctrl+S + + + + + 打开设置文件 + + + + + 打开文件管理对话框 + + + Ctrl+D + + + + + 发送文本消息 + + + Ctrl+Return + + + + + 发送窗口抖动 + + + + + 发送文件 + + + + + 刷新 + + + + + 手动添加 + + + + + 插入表情 + + + Ctrl+E + + + + + + + RecvTextEdit + QTextEdit +
recvtextedit.h
+
+ + SendTextEdit + QTextEdit +
sendtextedit.h
+
+
+ + +
diff --git a/osx/notification.h b/osx/notification.h new file mode 100644 index 0000000..c00b599 --- /dev/null +++ b/osx/notification.h @@ -0,0 +1,14 @@ +#ifndef OSXNOTIFICATION_H +#define OSXNOTIFICATION_H + +#include + +class Notification +{ +public: + explicit Notification(); + void show(const QString& title, const QString& content); + void hideAll(); +}; + +#endif // OSXNOTIFICATION_H diff --git a/osx/notification.mm b/osx/notification.mm new file mode 100644 index 0000000..529df9d --- /dev/null +++ b/osx/notification.mm @@ -0,0 +1,22 @@ +#include "notification.h" + +#import + +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]; +} diff --git a/osx/osxplatform.cpp b/osx/osxplatform.cpp new file mode 100644 index 0000000..eaa2f48 --- /dev/null +++ b/osx/osxplatform.cpp @@ -0,0 +1,25 @@ +#include "osxplatform.h" +#include + +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)); +} diff --git a/osx/osxplatform.h b/osx/osxplatform.h new file mode 100644 index 0000000..8525159 --- /dev/null +++ b/osx/osxplatform.h @@ -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 diff --git a/platformdepend.cpp b/platformdepend.cpp new file mode 100644 index 0000000..f28853f --- /dev/null +++ b/platformdepend.cpp @@ -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); +} diff --git a/platformdepend.h b/platformdepend.h new file mode 100644 index 0000000..cc0c27e --- /dev/null +++ b/platformdepend.h @@ -0,0 +1,35 @@ +#ifndef PLATFORMDEPEND_H +#define PLATFORMDEPEND_H + +#include +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 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5fbbf8f --- /dev/null +++ b/readme.md @@ -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 通知中心的通知消息 + +引用代码,请注明代码出处。 \ No newline at end of file diff --git a/recvtextedit.cpp b/recvtextedit.cpp new file mode 100644 index 0000000..8c5a600 --- /dev/null +++ b/recvtextedit.cpp @@ -0,0 +1,216 @@ +#include "recvtextedit.h" +#include +#include "emoji.h" +#include + +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+" "+ timeStr(msSinceEpoch)+""; + 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 = "我 "+timeStr(msSinceEpoch)+""; + 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(content), mySelf); + break; + case ContentType::Knock: + showKnock(static_cast(content), mySelf); + break; + case ContentType::Image: + showImage(static_cast(content)); + break; + case ContentType::Text: + showText(static_cast(content)); + break; + default: + showUnSupport(); + break; + } +} + +void RecvTextEdit::showFile(const FileContent *content, bool fromMySelf) +{ + if (content->fileType == IPMSG_FILE_REGULAR) + { + stringstream ss; + ss<<"fileId<<"_"<<(fromMySelf?"up":"down")<<">" + <filename<<"("<size<<")" + <<""; + 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(""+t+""); +} + +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", "
"); + htmlStr.replace("\r", "
"); + htmlStr.replace("\n", "
"); + + 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 = ""; + htmlStr.replace(emojiStr, imgTag); + } + + return htmlStr; +} diff --git a/recvtextedit.h b/recvtextedit.h new file mode 100644 index 0000000..958703b --- /dev/null +++ b/recvtextedit.h @@ -0,0 +1,52 @@ +#ifndef RECVTEXTEDIT_H +#define RECVTEXTEDIT_H + +#include "feiqlib/content.h" +#include "feiqlib/fellow.h" +#include +#include +#include + +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 mDocs; + long long mLastEdit=0; + QString mPressedAnchor; + +}; + +#endif // RECVTEXTEDIT_H diff --git a/res/face/1.gif b/res/face/1.gif new file mode 100644 index 0000000..5be27cb Binary files /dev/null and b/res/face/1.gif differ diff --git a/res/face/10.gif b/res/face/10.gif new file mode 100644 index 0000000..abdbb3f Binary files /dev/null and b/res/face/10.gif differ diff --git a/res/face/11.gif b/res/face/11.gif new file mode 100644 index 0000000..905c15b Binary files /dev/null and b/res/face/11.gif differ diff --git a/res/face/12.gif b/res/face/12.gif new file mode 100644 index 0000000..b512dd5 Binary files /dev/null and b/res/face/12.gif differ diff --git a/res/face/13.gif b/res/face/13.gif new file mode 100644 index 0000000..547529c Binary files /dev/null and b/res/face/13.gif differ diff --git a/res/face/14.gif b/res/face/14.gif new file mode 100644 index 0000000..3475300 Binary files /dev/null and b/res/face/14.gif differ diff --git a/res/face/15.gif b/res/face/15.gif new file mode 100644 index 0000000..6a788f8 Binary files /dev/null and b/res/face/15.gif differ diff --git a/res/face/16.gif b/res/face/16.gif new file mode 100644 index 0000000..5ef6d38 Binary files /dev/null and b/res/face/16.gif differ diff --git a/res/face/17.gif b/res/face/17.gif new file mode 100644 index 0000000..debab8e Binary files /dev/null and b/res/face/17.gif differ diff --git a/res/face/18.gif b/res/face/18.gif new file mode 100644 index 0000000..ed5d29f Binary files /dev/null and b/res/face/18.gif differ diff --git a/res/face/19.gif b/res/face/19.gif new file mode 100644 index 0000000..85886fe Binary files /dev/null and b/res/face/19.gif differ diff --git a/res/face/2.gif b/res/face/2.gif new file mode 100644 index 0000000..a2644a9 Binary files /dev/null and b/res/face/2.gif differ diff --git a/res/face/20.gif b/res/face/20.gif new file mode 100644 index 0000000..b6af218 Binary files /dev/null and b/res/face/20.gif differ diff --git a/res/face/21.gif b/res/face/21.gif new file mode 100644 index 0000000..e045ff2 Binary files /dev/null and b/res/face/21.gif differ diff --git a/res/face/22.gif b/res/face/22.gif new file mode 100644 index 0000000..efd650f Binary files /dev/null and b/res/face/22.gif differ diff --git a/res/face/23.gif b/res/face/23.gif new file mode 100644 index 0000000..cb8cf6d Binary files /dev/null and b/res/face/23.gif differ diff --git a/res/face/24.gif b/res/face/24.gif new file mode 100644 index 0000000..96b04df Binary files /dev/null and b/res/face/24.gif differ diff --git a/res/face/25.gif b/res/face/25.gif new file mode 100644 index 0000000..96516b8 Binary files /dev/null and b/res/face/25.gif differ diff --git a/res/face/26.gif b/res/face/26.gif new file mode 100644 index 0000000..5f925c7 Binary files /dev/null and b/res/face/26.gif differ diff --git a/res/face/27.gif b/res/face/27.gif new file mode 100644 index 0000000..97f8b1a Binary files /dev/null and b/res/face/27.gif differ diff --git a/res/face/28.gif b/res/face/28.gif new file mode 100644 index 0000000..a7cded7 Binary files /dev/null and b/res/face/28.gif differ diff --git a/res/face/29.gif b/res/face/29.gif new file mode 100644 index 0000000..bb46890 Binary files /dev/null and b/res/face/29.gif differ diff --git a/res/face/3.gif b/res/face/3.gif new file mode 100644 index 0000000..40cfda4 Binary files /dev/null and b/res/face/3.gif differ diff --git a/res/face/30.gif b/res/face/30.gif new file mode 100644 index 0000000..f59dd58 Binary files /dev/null and b/res/face/30.gif differ diff --git a/res/face/31.gif b/res/face/31.gif new file mode 100644 index 0000000..96ed098 Binary files /dev/null and b/res/face/31.gif differ diff --git a/res/face/32.gif b/res/face/32.gif new file mode 100644 index 0000000..608d0ad Binary files /dev/null and b/res/face/32.gif differ diff --git a/res/face/33.gif b/res/face/33.gif new file mode 100644 index 0000000..e24a180 Binary files /dev/null and b/res/face/33.gif differ diff --git a/res/face/34.gif b/res/face/34.gif new file mode 100644 index 0000000..073e743 Binary files /dev/null and b/res/face/34.gif differ diff --git a/res/face/35.gif b/res/face/35.gif new file mode 100644 index 0000000..772eff2 Binary files /dev/null and b/res/face/35.gif differ diff --git a/res/face/36.gif b/res/face/36.gif new file mode 100644 index 0000000..217c1c5 Binary files /dev/null and b/res/face/36.gif differ diff --git a/res/face/37.gif b/res/face/37.gif new file mode 100644 index 0000000..e9d4213 Binary files /dev/null and b/res/face/37.gif differ diff --git a/res/face/38.gif b/res/face/38.gif new file mode 100644 index 0000000..d6da2c3 Binary files /dev/null and b/res/face/38.gif differ diff --git a/res/face/39.gif b/res/face/39.gif new file mode 100644 index 0000000..c1e6ac9 Binary files /dev/null and b/res/face/39.gif differ diff --git a/res/face/4.gif b/res/face/4.gif new file mode 100644 index 0000000..6d6f762 Binary files /dev/null and b/res/face/4.gif differ diff --git a/res/face/40.gif b/res/face/40.gif new file mode 100644 index 0000000..92efec6 Binary files /dev/null and b/res/face/40.gif differ diff --git a/res/face/41.gif b/res/face/41.gif new file mode 100644 index 0000000..489f0f9 Binary files /dev/null and b/res/face/41.gif differ diff --git a/res/face/42.gif b/res/face/42.gif new file mode 100644 index 0000000..734f6d8 Binary files /dev/null and b/res/face/42.gif differ diff --git a/res/face/43.gif b/res/face/43.gif new file mode 100644 index 0000000..24a8eb6 Binary files /dev/null and b/res/face/43.gif differ diff --git a/res/face/44.gif b/res/face/44.gif new file mode 100644 index 0000000..99139e1 Binary files /dev/null and b/res/face/44.gif differ diff --git a/res/face/45.gif b/res/face/45.gif new file mode 100644 index 0000000..f60897e Binary files /dev/null and b/res/face/45.gif differ diff --git a/res/face/46.gif b/res/face/46.gif new file mode 100644 index 0000000..4350491 Binary files /dev/null and b/res/face/46.gif differ diff --git a/res/face/47.gif b/res/face/47.gif new file mode 100644 index 0000000..650d3dd Binary files /dev/null and b/res/face/47.gif differ diff --git a/res/face/48.gif b/res/face/48.gif new file mode 100644 index 0000000..b909e16 Binary files /dev/null and b/res/face/48.gif differ diff --git a/res/face/49.gif b/res/face/49.gif new file mode 100644 index 0000000..5c8e071 Binary files /dev/null and b/res/face/49.gif differ diff --git a/res/face/5.gif b/res/face/5.gif new file mode 100644 index 0000000..6ccdaa2 Binary files /dev/null and b/res/face/5.gif differ diff --git a/res/face/50.gif b/res/face/50.gif new file mode 100644 index 0000000..f3cb074 Binary files /dev/null and b/res/face/50.gif differ diff --git a/res/face/51.gif b/res/face/51.gif new file mode 100644 index 0000000..5b3057a Binary files /dev/null and b/res/face/51.gif differ diff --git a/res/face/52.gif b/res/face/52.gif new file mode 100644 index 0000000..27a30c1 Binary files /dev/null and b/res/face/52.gif differ diff --git a/res/face/53.gif b/res/face/53.gif new file mode 100644 index 0000000..dcfa48a Binary files /dev/null and b/res/face/53.gif differ diff --git a/res/face/54.gif b/res/face/54.gif new file mode 100644 index 0000000..029cf0f Binary files /dev/null and b/res/face/54.gif differ diff --git a/res/face/55.gif b/res/face/55.gif new file mode 100644 index 0000000..69f183f Binary files /dev/null and b/res/face/55.gif differ diff --git a/res/face/56.gif b/res/face/56.gif new file mode 100644 index 0000000..d41e8aa Binary files /dev/null and b/res/face/56.gif differ diff --git a/res/face/57.gif b/res/face/57.gif new file mode 100644 index 0000000..56352dd Binary files /dev/null and b/res/face/57.gif differ diff --git a/res/face/58.gif b/res/face/58.gif new file mode 100644 index 0000000..b28d848 Binary files /dev/null and b/res/face/58.gif differ diff --git a/res/face/59.gif b/res/face/59.gif new file mode 100644 index 0000000..e18da84 Binary files /dev/null and b/res/face/59.gif differ diff --git a/res/face/6.gif b/res/face/6.gif new file mode 100644 index 0000000..90c3d86 Binary files /dev/null and b/res/face/6.gif differ diff --git a/res/face/60.gif b/res/face/60.gif new file mode 100644 index 0000000..edf96f0 Binary files /dev/null and b/res/face/60.gif differ diff --git a/res/face/61.gif b/res/face/61.gif new file mode 100644 index 0000000..3f0e2b9 Binary files /dev/null and b/res/face/61.gif differ diff --git a/res/face/62.gif b/res/face/62.gif new file mode 100644 index 0000000..47b1aaa Binary files /dev/null and b/res/face/62.gif differ diff --git a/res/face/63.gif b/res/face/63.gif new file mode 100644 index 0000000..918288b Binary files /dev/null and b/res/face/63.gif differ diff --git a/res/face/64.gif b/res/face/64.gif new file mode 100644 index 0000000..7f71a8c Binary files /dev/null and b/res/face/64.gif differ diff --git a/res/face/65.gif b/res/face/65.gif new file mode 100644 index 0000000..66d2113 Binary files /dev/null and b/res/face/65.gif differ diff --git a/res/face/66.gif b/res/face/66.gif new file mode 100644 index 0000000..034933e Binary files /dev/null and b/res/face/66.gif differ diff --git a/res/face/67.gif b/res/face/67.gif new file mode 100644 index 0000000..8d5c4fd Binary files /dev/null and b/res/face/67.gif differ diff --git a/res/face/68.gif b/res/face/68.gif new file mode 100644 index 0000000..d58fcf6 Binary files /dev/null and b/res/face/68.gif differ diff --git a/res/face/69.gif b/res/face/69.gif new file mode 100644 index 0000000..c4e00bd Binary files /dev/null and b/res/face/69.gif differ diff --git a/res/face/7.gif b/res/face/7.gif new file mode 100644 index 0000000..a26b001 Binary files /dev/null and b/res/face/7.gif differ diff --git a/res/face/70.gif b/res/face/70.gif new file mode 100644 index 0000000..da23bfa Binary files /dev/null and b/res/face/70.gif differ diff --git a/res/face/71.gif b/res/face/71.gif new file mode 100644 index 0000000..310ec65 Binary files /dev/null and b/res/face/71.gif differ diff --git a/res/face/72.gif b/res/face/72.gif new file mode 100644 index 0000000..51761ba Binary files /dev/null and b/res/face/72.gif differ diff --git a/res/face/73.gif b/res/face/73.gif new file mode 100644 index 0000000..345cb43 Binary files /dev/null and b/res/face/73.gif differ diff --git a/res/face/74.gif b/res/face/74.gif new file mode 100644 index 0000000..e0f28a0 Binary files /dev/null and b/res/face/74.gif differ diff --git a/res/face/75.gif b/res/face/75.gif new file mode 100644 index 0000000..24284cf Binary files /dev/null and b/res/face/75.gif differ diff --git a/res/face/76.gif b/res/face/76.gif new file mode 100644 index 0000000..a0ccf2e Binary files /dev/null and b/res/face/76.gif differ diff --git a/res/face/77.gif b/res/face/77.gif new file mode 100644 index 0000000..7e113ee Binary files /dev/null and b/res/face/77.gif differ diff --git a/res/face/78.gif b/res/face/78.gif new file mode 100644 index 0000000..c0293c3 Binary files /dev/null and b/res/face/78.gif differ diff --git a/res/face/79.gif b/res/face/79.gif new file mode 100644 index 0000000..1c52bde Binary files /dev/null and b/res/face/79.gif differ diff --git a/res/face/8.gif b/res/face/8.gif new file mode 100644 index 0000000..2f45399 Binary files /dev/null and b/res/face/8.gif differ diff --git a/res/face/80.gif b/res/face/80.gif new file mode 100644 index 0000000..4f26d7d Binary files /dev/null and b/res/face/80.gif differ diff --git a/res/face/81.gif b/res/face/81.gif new file mode 100644 index 0000000..9cb9aa7 Binary files /dev/null and b/res/face/81.gif differ diff --git a/res/face/82.gif b/res/face/82.gif new file mode 100644 index 0000000..27019f8 Binary files /dev/null and b/res/face/82.gif differ diff --git a/res/face/83.gif b/res/face/83.gif new file mode 100644 index 0000000..8f882f5 Binary files /dev/null and b/res/face/83.gif differ diff --git a/res/face/84.gif b/res/face/84.gif new file mode 100644 index 0000000..d0d0856 Binary files /dev/null and b/res/face/84.gif differ diff --git a/res/face/85.gif b/res/face/85.gif new file mode 100644 index 0000000..61652a7 Binary files /dev/null and b/res/face/85.gif differ diff --git a/res/face/86.gif b/res/face/86.gif new file mode 100644 index 0000000..9a77936 Binary files /dev/null and b/res/face/86.gif differ diff --git a/res/face/87.gif b/res/face/87.gif new file mode 100644 index 0000000..2329101 Binary files /dev/null and b/res/face/87.gif differ diff --git a/res/face/88.gif b/res/face/88.gif new file mode 100644 index 0000000..644748a Binary files /dev/null and b/res/face/88.gif differ diff --git a/res/face/89.gif b/res/face/89.gif new file mode 100644 index 0000000..fbf275b Binary files /dev/null and b/res/face/89.gif differ diff --git a/res/face/9.gif b/res/face/9.gif new file mode 100644 index 0000000..f6c8834 Binary files /dev/null and b/res/face/9.gif differ diff --git a/res/face/90.gif b/res/face/90.gif new file mode 100644 index 0000000..076f0c6 Binary files /dev/null and b/res/face/90.gif differ diff --git a/res/face/91.gif b/res/face/91.gif new file mode 100644 index 0000000..d254af4 Binary files /dev/null and b/res/face/91.gif differ diff --git a/res/face/92.gif b/res/face/92.gif new file mode 100644 index 0000000..8f09d33 Binary files /dev/null and b/res/face/92.gif differ diff --git a/res/face/93.gif b/res/face/93.gif new file mode 100644 index 0000000..df70756 Binary files /dev/null and b/res/face/93.gif differ diff --git a/res/face/94.gif b/res/face/94.gif new file mode 100644 index 0000000..4d8b15e Binary files /dev/null and b/res/face/94.gif differ diff --git a/res/face/95.gif b/res/face/95.gif new file mode 100644 index 0000000..05726dc Binary files /dev/null and b/res/face/95.gif differ diff --git a/res/face/96.gif b/res/face/96.gif new file mode 100644 index 0000000..adaf20e Binary files /dev/null and b/res/face/96.gif differ diff --git a/res/face/page.bmp b/res/face/page.bmp new file mode 100644 index 0000000..d1709a8 Binary files /dev/null and b/res/face/page.bmp differ diff --git a/res/icon.png b/res/icon.png new file mode 100644 index 0000000..2bc847d Binary files /dev/null and b/res/icon.png differ diff --git a/searchfellowdlg.cpp b/searchfellowdlg.cpp new file mode 100644 index 0000000..88b900e --- /dev/null +++ b/searchfellowdlg.cpp @@ -0,0 +1,59 @@ +#include "searchfellowdlg.h" +#include "ui_searchfellowdlg.h" + +SearchFellowDlg::SearchFellowDlg(QWidget *parent) : + QDialog(parent), + ui(new Ui::SearchFellowDlg) +{ + ui->setupUi(this); + + connect(ui->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(search(QString))); + connect(ui->cancelBtn, SIGNAL(pressed()), this, SLOT(hide())); + connect(ui->okBtn, SIGNAL(pressed()), this, SLOT(searchDone())); + connect(ui->resultListWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(searchDone())); +} + +SearchFellowDlg::~SearchFellowDlg() +{ + delete ui; +} + +void SearchFellowDlg::setSearchDriver(function(const QString &)> driver) +{ + mSearchDriver = driver; +} + +void SearchFellowDlg::search(const QString &text) +{ + if (mSearchDriver) + { + mCurResult = mSearchDriver(text); + ui->resultListWidget->clear(); + for (const Fellow* fellow : mCurResult) + ui->resultListWidget->addItem(QString(fellow->toString().c_str())); + if (mCurResult.size() > 0) + ui->resultListWidget->setCurrentRow(0); + + ui->okBtn->setEnabled(mCurResult.size()>0); + } +} + +void SearchFellowDlg::searchDone() +{ + auto row = ui->resultListWidget->currentRow(); + if (row >= 0) + { + emit onFellowSelected(mCurResult[row]); + hide(); + } +} + +void SearchFellowDlg::showEvent(QShowEvent *) +{ + loadAllFellows(); +} + +void SearchFellowDlg::loadAllFellows() +{ + search("");//search 空字符串将获得所有好友 +} diff --git a/searchfellowdlg.h b/searchfellowdlg.h new file mode 100644 index 0000000..a70c068 --- /dev/null +++ b/searchfellowdlg.h @@ -0,0 +1,43 @@ +#ifndef SEARCHFELLOWDLG_H +#define SEARCHFELLOWDLG_H + +#include +#include +#include +#include "feiqlib/fellow.h" + +using namespace std; + +namespace Ui { +class SearchFellowDlg; +} + +class SearchFellowDlg : public QDialog +{ + Q_OBJECT + +public: + explicit SearchFellowDlg(QWidget *parent = 0); + ~SearchFellowDlg(); + + void setSearchDriver(function(const QString &)> driver); + +signals: + void onFellowSelected(const Fellow* fellow); + +public slots: + void search(const QString& text); + void searchDone(); +protected: + virtual void showEvent(QShowEvent *) override; + +private: + void loadAllFellows(); + +private: + Ui::SearchFellowDlg *ui; + function(const QString& text)> mSearchDriver; + vector mCurResult; +}; + +#endif // SEARCHFELLOWDLG_H diff --git a/searchfellowdlg.ui b/searchfellowdlg.ui new file mode 100644 index 0000000..8b98383 --- /dev/null +++ b/searchfellowdlg.ui @@ -0,0 +1,69 @@ + + + SearchFellowDlg + + + + 0 + 0 + 362 + 260 + + + + 查找好友 + + + + 10 + + + 10 + + + 10 + + + 10 + + + 8 + + + + + + + + Qt::TabFocus + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + 取消 + + + false + + + + + + + 打开会话 + + + + + + + + diff --git a/sendtextedit.cpp b/sendtextedit.cpp new file mode 100644 index 0000000..68ba3b9 --- /dev/null +++ b/sendtextedit.cpp @@ -0,0 +1,50 @@ +#include "sendtextedit.h" +#include +#include +#include +#include + +SendTextEdit::SendTextEdit(QWidget *parent) + :QTextEdit(parent) +{ + setAcceptDrops(true); +} + +void SendTextEdit::dragEnterEvent(QDragEnterEvent *e) +{ + if (e->mimeData()->hasUrls()) + { + auto urls = e->mimeData()->urls(); + for (auto url : urls) + { + if (QFileInfo(url.toLocalFile()).isFile()) + { + e->accept(); + return; + } + } + } + else + { + QTextEdit::dragEnterEvent(e); + } +} + +void SendTextEdit::dropEvent(QDropEvent *e) +{ + if (e->mimeData()->hasUrls()) + { + auto urls = e->mimeData()->urls(); + QList files; + for (auto url : urls) + { + files.append(QFileInfo(url.toLocalFile())); + e->accept(); + } + emit acceptDropFiles(files); + } + else + { + QTextEdit::dropEvent(e); + } +} diff --git a/sendtextedit.h b/sendtextedit.h new file mode 100644 index 0000000..1b02349 --- /dev/null +++ b/sendtextedit.h @@ -0,0 +1,22 @@ +#ifndef SENDTEXTEDIT_H +#define SENDTEXTEDIT_H + +#include +#include +#include + +class SendTextEdit : public QTextEdit +{ + Q_OBJECT +public: + SendTextEdit(QWidget* parent = 0); + +signals: + void acceptDropFiles(QList); + +protected: + virtual void dragEnterEvent(QDragEnterEvent *e) override; + virtual void dropEvent(QDropEvent *e) override; +}; + +#endif // SENDTEXTEDIT_H