| @@ -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 | |||||
| @@ -0,0 +1,674 @@ | |||||
| GNU GENERAL PUBLIC LICENSE | |||||
| Version 3, 29 June 2007 | |||||
| Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | |||||
| Everyone is permitted to copy and distribute verbatim copies | |||||
| of this license document, but changing it is not allowed. | |||||
| Preamble | |||||
| The GNU General Public License is a free, copyleft license for | |||||
| software and other kinds of works. | |||||
| The licenses for most software and other practical works are designed | |||||
| to take away your freedom to share and change the works. By contrast, | |||||
| the GNU General Public License is intended to guarantee your freedom to | |||||
| share and change all versions of a program--to make sure it remains free | |||||
| software for all its users. We, the Free Software Foundation, use the | |||||
| GNU General Public License for most of our software; it applies also to | |||||
| any other work released this way by its authors. You can apply it to | |||||
| your programs, too. | |||||
| When we speak of free software, we are referring to freedom, not | |||||
| price. Our General Public Licenses are designed to make sure that you | |||||
| have the freedom to distribute copies of free software (and charge for | |||||
| them if you wish), that you receive source code or can get it if you | |||||
| want it, that you can change the software or use pieces of it in new | |||||
| free programs, and that you know you can do these things. | |||||
| To protect your rights, we need to prevent others from denying you | |||||
| these rights or asking you to surrender the rights. Therefore, you have | |||||
| certain responsibilities if you distribute copies of the software, or if | |||||
| you modify it: responsibilities to respect the freedom of others. | |||||
| For example, if you distribute copies of such a program, whether | |||||
| gratis or for a fee, you must pass on to the recipients the same | |||||
| freedoms that you received. You must make sure that they, too, receive | |||||
| or can get the source code. And you must show them these terms so they | |||||
| know their rights. | |||||
| Developers that use the GNU GPL protect your rights with two steps: | |||||
| (1) assert copyright on the software, and (2) offer you this License | |||||
| giving you legal permission to copy, distribute and/or modify it. | |||||
| For the developers' and authors' protection, the GPL clearly explains | |||||
| that there is no warranty for this free software. For both users' and | |||||
| authors' sake, the GPL requires that modified versions be marked as | |||||
| changed, so that their problems will not be attributed erroneously to | |||||
| authors of previous versions. | |||||
| Some devices are designed to deny users access to install or run | |||||
| modified versions of the software inside them, although the manufacturer | |||||
| can do so. This is fundamentally incompatible with the aim of | |||||
| protecting users' freedom to change the software. The systematic | |||||
| pattern of such abuse occurs in the area of products for individuals to | |||||
| use, which is precisely where it is most unacceptable. Therefore, we | |||||
| have designed this version of the GPL to prohibit the practice for those | |||||
| products. If such problems arise substantially in other domains, we | |||||
| stand ready to extend this provision to those domains in future versions | |||||
| of the GPL, as needed to protect the freedom of users. | |||||
| Finally, every program is threatened constantly by software patents. | |||||
| States should not allow patents to restrict development and use of | |||||
| software on general-purpose computers, but in those that do, we wish to | |||||
| avoid the special danger that patents applied to a free program could | |||||
| make it effectively proprietary. To prevent this, the GPL assures that | |||||
| patents cannot be used to render the program non-free. | |||||
| The precise terms and conditions for copying, distribution and | |||||
| modification follow. | |||||
| TERMS AND CONDITIONS | |||||
| 0. Definitions. | |||||
| "This License" refers to version 3 of the GNU General Public License. | |||||
| "Copyright" also means copyright-like laws that apply to other kinds of | |||||
| works, such as semiconductor masks. | |||||
| "The Program" refers to any copyrightable work licensed under this | |||||
| License. Each licensee is addressed as "you". "Licensees" and | |||||
| "recipients" may be individuals or organizations. | |||||
| To "modify" a work means to copy from or adapt all or part of the work | |||||
| in a fashion requiring copyright permission, other than the making of an | |||||
| exact copy. The resulting work is called a "modified version" of the | |||||
| earlier work or a work "based on" the earlier work. | |||||
| A "covered work" means either the unmodified Program or a work based | |||||
| on the Program. | |||||
| To "propagate" a work means to do anything with it that, without | |||||
| permission, would make you directly or secondarily liable for | |||||
| infringement under applicable copyright law, except executing it on a | |||||
| computer or modifying a private copy. Propagation includes copying, | |||||
| distribution (with or without modification), making available to the | |||||
| public, and in some countries other activities as well. | |||||
| To "convey" a work means any kind of propagation that enables other | |||||
| parties to make or receive copies. Mere interaction with a user through | |||||
| a computer network, with no transfer of a copy, is not conveying. | |||||
| An interactive user interface displays "Appropriate Legal Notices" | |||||
| to the extent that it includes a convenient and prominently visible | |||||
| feature that (1) displays an appropriate copyright notice, and (2) | |||||
| tells the user that there is no warranty for the work (except to the | |||||
| extent that warranties are provided), that licensees may convey the | |||||
| work under this License, and how to view a copy of this License. If | |||||
| the interface presents a list of user commands or options, such as a | |||||
| menu, a prominent item in the list meets this criterion. | |||||
| 1. Source Code. | |||||
| The "source code" for a work means the preferred form of the work | |||||
| for making modifications to it. "Object code" means any non-source | |||||
| form of a work. | |||||
| A "Standard Interface" means an interface that either is an official | |||||
| standard defined by a recognized standards body, or, in the case of | |||||
| interfaces specified for a particular programming language, one that | |||||
| is widely used among developers working in that language. | |||||
| The "System Libraries" of an executable work include anything, other | |||||
| than the work as a whole, that (a) is included in the normal form of | |||||
| packaging a Major Component, but which is not part of that Major | |||||
| Component, and (b) serves only to enable use of the work with that | |||||
| Major Component, or to implement a Standard Interface for which an | |||||
| implementation is available to the public in source code form. A | |||||
| "Major Component", in this context, means a major essential component | |||||
| (kernel, window system, and so on) of the specific operating system | |||||
| (if any) on which the executable work runs, or a compiler used to | |||||
| produce the work, or an object code interpreter used to run it. | |||||
| The "Corresponding Source" for a work in object code form means all | |||||
| the source code needed to generate, install, and (for an executable | |||||
| work) run the object code and to modify the work, including scripts to | |||||
| control those activities. However, it does not include the work's | |||||
| System Libraries, or general-purpose tools or generally available free | |||||
| programs which are used unmodified in performing those activities but | |||||
| which are not part of the work. For example, Corresponding Source | |||||
| includes interface definition files associated with source files for | |||||
| the work, and the source code for shared libraries and dynamically | |||||
| linked subprograms that the work is specifically designed to require, | |||||
| such as by intimate data communication or control flow between those | |||||
| subprograms and other parts of the work. | |||||
| The Corresponding Source need not include anything that users | |||||
| can regenerate automatically from other parts of the Corresponding | |||||
| Source. | |||||
| The Corresponding Source for a work in source code form is that | |||||
| same work. | |||||
| 2. Basic Permissions. | |||||
| All rights granted under this License are granted for the term of | |||||
| copyright on the Program, and are irrevocable provided the stated | |||||
| conditions are met. This License explicitly affirms your unlimited | |||||
| permission to run the unmodified Program. The output from running a | |||||
| covered work is covered by this License only if the output, given its | |||||
| content, constitutes a covered work. This License acknowledges your | |||||
| rights of fair use or other equivalent, as provided by copyright law. | |||||
| You may make, run and propagate covered works that you do not | |||||
| convey, without conditions so long as your license otherwise remains | |||||
| in force. You may convey covered works to others for the sole purpose | |||||
| of having them make modifications exclusively for you, or provide you | |||||
| with facilities for running those works, provided that you comply with | |||||
| the terms of this License in conveying all material for which you do | |||||
| not control copyright. Those thus making or running the covered works | |||||
| for you must do so exclusively on your behalf, under your direction | |||||
| and control, on terms that prohibit them from making any copies of | |||||
| your copyrighted material outside their relationship with you. | |||||
| Conveying under any other circumstances is permitted solely under | |||||
| the conditions stated below. Sublicensing is not allowed; section 10 | |||||
| makes it unnecessary. | |||||
| 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | |||||
| No covered work shall be deemed part of an effective technological | |||||
| measure under any applicable law fulfilling obligations under article | |||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | |||||
| similar laws prohibiting or restricting circumvention of such | |||||
| measures. | |||||
| When you convey a covered work, you waive any legal power to forbid | |||||
| circumvention of technological measures to the extent such circumvention | |||||
| is effected by exercising rights under this License with respect to | |||||
| the covered work, and you disclaim any intention to limit operation or | |||||
| modification of the work as a means of enforcing, against the work's | |||||
| users, your or third parties' legal rights to forbid circumvention of | |||||
| technological measures. | |||||
| 4. Conveying Verbatim Copies. | |||||
| You may convey verbatim copies of the Program's source code as you | |||||
| receive it, in any medium, provided that you conspicuously and | |||||
| appropriately publish on each copy an appropriate copyright notice; | |||||
| keep intact all notices stating that this License and any | |||||
| non-permissive terms added in accord with section 7 apply to the code; | |||||
| keep intact all notices of the absence of any warranty; and give all | |||||
| recipients a copy of this License along with the Program. | |||||
| You may charge any price or no price for each copy that you convey, | |||||
| and you may offer support or warranty protection for a fee. | |||||
| 5. Conveying Modified Source Versions. | |||||
| You may convey a work based on the Program, or the modifications to | |||||
| produce it from the Program, in the form of source code under the | |||||
| terms of section 4, provided that you also meet all of these conditions: | |||||
| a) The work must carry prominent notices stating that you modified | |||||
| it, and giving a relevant date. | |||||
| b) The work must carry prominent notices stating that it is | |||||
| released under this License and any conditions added under section | |||||
| 7. This requirement modifies the requirement in section 4 to | |||||
| "keep intact all notices". | |||||
| c) You must license the entire work, as a whole, under this | |||||
| License to anyone who comes into possession of a copy. This | |||||
| License will therefore apply, along with any applicable section 7 | |||||
| additional terms, to the whole of the work, and all its parts, | |||||
| regardless of how they are packaged. This License gives no | |||||
| permission to license the work in any other way, but it does not | |||||
| invalidate such permission if you have separately received it. | |||||
| d) If the work has interactive user interfaces, each must display | |||||
| Appropriate Legal Notices; however, if the Program has interactive | |||||
| interfaces that do not display Appropriate Legal Notices, your | |||||
| work need not make them do so. | |||||
| A compilation of a covered work with other separate and independent | |||||
| works, which are not by their nature extensions of the covered work, | |||||
| and which are not combined with it such as to form a larger program, | |||||
| in or on a volume of a storage or distribution medium, is called an | |||||
| "aggregate" if the compilation and its resulting copyright are not | |||||
| used to limit the access or legal rights of the compilation's users | |||||
| beyond what the individual works permit. Inclusion of a covered work | |||||
| in an aggregate does not cause this License to apply to the other | |||||
| parts of the aggregate. | |||||
| 6. Conveying Non-Source Forms. | |||||
| You may convey a covered work in object code form under the terms | |||||
| of sections 4 and 5, provided that you also convey the | |||||
| machine-readable Corresponding Source under the terms of this License, | |||||
| in one of these ways: | |||||
| a) Convey the object code in, or embodied in, a physical product | |||||
| (including a physical distribution medium), accompanied by the | |||||
| Corresponding Source fixed on a durable physical medium | |||||
| customarily used for software interchange. | |||||
| b) Convey the object code in, or embodied in, a physical product | |||||
| (including a physical distribution medium), accompanied by a | |||||
| written offer, valid for at least three years and valid for as | |||||
| long as you offer spare parts or customer support for that product | |||||
| model, to give anyone who possesses the object code either (1) a | |||||
| copy of the Corresponding Source for all the software in the | |||||
| product that is covered by this License, on a durable physical | |||||
| medium customarily used for software interchange, for a price no | |||||
| more than your reasonable cost of physically performing this | |||||
| conveying of source, or (2) access to copy the | |||||
| Corresponding Source from a network server at no charge. | |||||
| c) Convey individual copies of the object code with a copy of the | |||||
| written offer to provide the Corresponding Source. This | |||||
| alternative is allowed only occasionally and noncommercially, and | |||||
| only if you received the object code with such an offer, in accord | |||||
| with subsection 6b. | |||||
| d) Convey the object code by offering access from a designated | |||||
| place (gratis or for a charge), and offer equivalent access to the | |||||
| Corresponding Source in the same way through the same place at no | |||||
| further charge. You need not require recipients to copy the | |||||
| Corresponding Source along with the object code. If the place to | |||||
| copy the object code is a network server, the Corresponding Source | |||||
| may be on a different server (operated by you or a third party) | |||||
| that supports equivalent copying facilities, provided you maintain | |||||
| clear directions next to the object code saying where to find the | |||||
| Corresponding Source. Regardless of what server hosts the | |||||
| Corresponding Source, you remain obligated to ensure that it is | |||||
| available for as long as needed to satisfy these requirements. | |||||
| e) Convey the object code using peer-to-peer transmission, provided | |||||
| you inform other peers where the object code and Corresponding | |||||
| Source of the work are being offered to the general public at no | |||||
| charge under subsection 6d. | |||||
| A separable portion of the object code, whose source code is excluded | |||||
| from the Corresponding Source as a System Library, need not be | |||||
| included in conveying the object code work. | |||||
| A "User Product" is either (1) a "consumer product", which means any | |||||
| tangible personal property which is normally used for personal, family, | |||||
| or household purposes, or (2) anything designed or sold for incorporation | |||||
| into a dwelling. In determining whether a product is a consumer product, | |||||
| doubtful cases shall be resolved in favor of coverage. For a particular | |||||
| product received by a particular user, "normally used" refers to a | |||||
| typical or common use of that class of product, regardless of the status | |||||
| of the particular user or of the way in which the particular user | |||||
| actually uses, or expects or is expected to use, the product. A product | |||||
| is a consumer product regardless of whether the product has substantial | |||||
| commercial, industrial or non-consumer uses, unless such uses represent | |||||
| the only significant mode of use of the product. | |||||
| "Installation Information" for a User Product means any methods, | |||||
| procedures, authorization keys, or other information required to install | |||||
| and execute modified versions of a covered work in that User Product from | |||||
| a modified version of its Corresponding Source. The information must | |||||
| suffice to ensure that the continued functioning of the modified object | |||||
| code is in no case prevented or interfered with solely because | |||||
| modification has been made. | |||||
| If you convey an object code work under this section in, or with, or | |||||
| specifically for use in, a User Product, and the conveying occurs as | |||||
| part of a transaction in which the right of possession and use of the | |||||
| User Product is transferred to the recipient in perpetuity or for a | |||||
| fixed term (regardless of how the transaction is characterized), the | |||||
| Corresponding Source conveyed under this section must be accompanied | |||||
| by the Installation Information. But this requirement does not apply | |||||
| if neither you nor any third party retains the ability to install | |||||
| modified object code on the User Product (for example, the work has | |||||
| been installed in ROM). | |||||
| The requirement to provide Installation Information does not include a | |||||
| requirement to continue to provide support service, warranty, or updates | |||||
| for a work that has been modified or installed by the recipient, or for | |||||
| the User Product in which it has been modified or installed. Access to a | |||||
| network may be denied when the modification itself materially and | |||||
| adversely affects the operation of the network or violates the rules and | |||||
| protocols for communication across the network. | |||||
| Corresponding Source conveyed, and Installation Information provided, | |||||
| in accord with this section must be in a format that is publicly | |||||
| documented (and with an implementation available to the public in | |||||
| source code form), and must require no special password or key for | |||||
| unpacking, reading or copying. | |||||
| 7. Additional Terms. | |||||
| "Additional permissions" are terms that supplement the terms of this | |||||
| License by making exceptions from one or more of its conditions. | |||||
| Additional permissions that are applicable to the entire Program shall | |||||
| be treated as though they were included in this License, to the extent | |||||
| that they are valid under applicable law. If additional permissions | |||||
| apply only to part of the Program, that part may be used separately | |||||
| under those permissions, but the entire Program remains governed by | |||||
| this License without regard to the additional permissions. | |||||
| When you convey a copy of a covered work, you may at your option | |||||
| remove any additional permissions from that copy, or from any part of | |||||
| it. (Additional permissions may be written to require their own | |||||
| removal in certain cases when you modify the work.) You may place | |||||
| additional permissions on material, added by you to a covered work, | |||||
| for which you have or can give appropriate copyright permission. | |||||
| Notwithstanding any other provision of this License, for material you | |||||
| add to a covered work, you may (if authorized by the copyright holders of | |||||
| that material) supplement the terms of this License with terms: | |||||
| a) Disclaiming warranty or limiting liability differently from the | |||||
| terms of sections 15 and 16 of this License; or | |||||
| b) Requiring preservation of specified reasonable legal notices or | |||||
| author attributions in that material or in the Appropriate Legal | |||||
| Notices displayed by works containing it; or | |||||
| c) Prohibiting misrepresentation of the origin of that material, or | |||||
| requiring that modified versions of such material be marked in | |||||
| reasonable ways as different from the original version; or | |||||
| d) Limiting the use for publicity purposes of names of licensors or | |||||
| authors of the material; or | |||||
| e) Declining to grant rights under trademark law for use of some | |||||
| trade names, trademarks, or service marks; or | |||||
| f) Requiring indemnification of licensors and authors of that | |||||
| material by anyone who conveys the material (or modified versions of | |||||
| it) with contractual assumptions of liability to the recipient, for | |||||
| any liability that these contractual assumptions directly impose on | |||||
| those licensors and authors. | |||||
| All other non-permissive additional terms are considered "further | |||||
| restrictions" within the meaning of section 10. If the Program as you | |||||
| received it, or any part of it, contains a notice stating that it is | |||||
| governed by this License along with a term that is a further | |||||
| restriction, you may remove that term. If a license document contains | |||||
| a further restriction but permits relicensing or conveying under this | |||||
| License, you may add to a covered work material governed by the terms | |||||
| of that license document, provided that the further restriction does | |||||
| not survive such relicensing or conveying. | |||||
| If you add terms to a covered work in accord with this section, you | |||||
| must place, in the relevant source files, a statement of the | |||||
| additional terms that apply to those files, or a notice indicating | |||||
| where to find the applicable terms. | |||||
| Additional terms, permissive or non-permissive, may be stated in the | |||||
| form of a separately written license, or stated as exceptions; | |||||
| the above requirements apply either way. | |||||
| 8. Termination. | |||||
| You may not propagate or modify a covered work except as expressly | |||||
| provided under this License. Any attempt otherwise to propagate or | |||||
| modify it is void, and will automatically terminate your rights under | |||||
| this License (including any patent licenses granted under the third | |||||
| paragraph of section 11). | |||||
| However, if you cease all violation of this License, then your | |||||
| license from a particular copyright holder is reinstated (a) | |||||
| provisionally, unless and until the copyright holder explicitly and | |||||
| finally terminates your license, and (b) permanently, if the copyright | |||||
| holder fails to notify you of the violation by some reasonable means | |||||
| prior to 60 days after the cessation. | |||||
| Moreover, your license from a particular copyright holder is | |||||
| reinstated permanently if the copyright holder notifies you of the | |||||
| violation by some reasonable means, this is the first time you have | |||||
| received notice of violation of this License (for any work) from that | |||||
| copyright holder, and you cure the violation prior to 30 days after | |||||
| your receipt of the notice. | |||||
| Termination of your rights under this section does not terminate the | |||||
| licenses of parties who have received copies or rights from you under | |||||
| this License. If your rights have been terminated and not permanently | |||||
| reinstated, you do not qualify to receive new licenses for the same | |||||
| material under section 10. | |||||
| 9. Acceptance Not Required for Having Copies. | |||||
| You are not required to accept this License in order to receive or | |||||
| run a copy of the Program. Ancillary propagation of a covered work | |||||
| occurring solely as a consequence of using peer-to-peer transmission | |||||
| to receive a copy likewise does not require acceptance. However, | |||||
| nothing other than this License grants you permission to propagate or | |||||
| modify any covered work. These actions infringe copyright if you do | |||||
| not accept this License. Therefore, by modifying or propagating a | |||||
| covered work, you indicate your acceptance of this License to do so. | |||||
| 10. Automatic Licensing of Downstream Recipients. | |||||
| Each time you convey a covered work, the recipient automatically | |||||
| receives a license from the original licensors, to run, modify and | |||||
| propagate that work, subject to this License. You are not responsible | |||||
| for enforcing compliance by third parties with this License. | |||||
| An "entity transaction" is a transaction transferring control of an | |||||
| organization, or substantially all assets of one, or subdividing an | |||||
| organization, or merging organizations. If propagation of a covered | |||||
| work results from an entity transaction, each party to that | |||||
| transaction who receives a copy of the work also receives whatever | |||||
| licenses to the work the party's predecessor in interest had or could | |||||
| give under the previous paragraph, plus a right to possession of the | |||||
| Corresponding Source of the work from the predecessor in interest, if | |||||
| the predecessor has it or can get it with reasonable efforts. | |||||
| You may not impose any further restrictions on the exercise of the | |||||
| rights granted or affirmed under this License. For example, you may | |||||
| not impose a license fee, royalty, or other charge for exercise of | |||||
| rights granted under this License, and you may not initiate litigation | |||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | |||||
| any patent claim is infringed by making, using, selling, offering for | |||||
| sale, or importing the Program or any portion of it. | |||||
| 11. Patents. | |||||
| A "contributor" is a copyright holder who authorizes use under this | |||||
| License of the Program or a work on which the Program is based. The | |||||
| work thus licensed is called the contributor's "contributor version". | |||||
| A contributor's "essential patent claims" are all patent claims | |||||
| owned or controlled by the contributor, whether already acquired or | |||||
| hereafter acquired, that would be infringed by some manner, permitted | |||||
| by this License, of making, using, or selling its contributor version, | |||||
| but do not include claims that would be infringed only as a | |||||
| consequence of further modification of the contributor version. For | |||||
| purposes of this definition, "control" includes the right to grant | |||||
| patent sublicenses in a manner consistent with the requirements of | |||||
| this License. | |||||
| Each contributor grants you a non-exclusive, worldwide, royalty-free | |||||
| patent license under the contributor's essential patent claims, to | |||||
| make, use, sell, offer for sale, import and otherwise run, modify and | |||||
| propagate the contents of its contributor version. | |||||
| In the following three paragraphs, a "patent license" is any express | |||||
| agreement or commitment, however denominated, not to enforce a patent | |||||
| (such as an express permission to practice a patent or covenant not to | |||||
| sue for patent infringement). To "grant" such a patent license to a | |||||
| party means to make such an agreement or commitment not to enforce a | |||||
| patent against the party. | |||||
| If you convey a covered work, knowingly relying on a patent license, | |||||
| and the Corresponding Source of the work is not available for anyone | |||||
| to copy, free of charge and under the terms of this License, through a | |||||
| publicly available network server or other readily accessible means, | |||||
| then you must either (1) cause the Corresponding Source to be so | |||||
| available, or (2) arrange to deprive yourself of the benefit of the | |||||
| patent license for this particular work, or (3) arrange, in a manner | |||||
| consistent with the requirements of this License, to extend the patent | |||||
| license to downstream recipients. "Knowingly relying" means you have | |||||
| actual knowledge that, but for the patent license, your conveying the | |||||
| covered work in a country, or your recipient's use of the covered work | |||||
| in a country, would infringe one or more identifiable patents in that | |||||
| country that you have reason to believe are valid. | |||||
| If, pursuant to or in connection with a single transaction or | |||||
| arrangement, you convey, or propagate by procuring conveyance of, a | |||||
| covered work, and grant a patent license to some of the parties | |||||
| receiving the covered work authorizing them to use, propagate, modify | |||||
| or convey a specific copy of the covered work, then the patent license | |||||
| you grant is automatically extended to all recipients of the covered | |||||
| work and works based on it. | |||||
| A patent license is "discriminatory" if it does not include within | |||||
| the scope of its coverage, prohibits the exercise of, or is | |||||
| conditioned on the non-exercise of one or more of the rights that are | |||||
| specifically granted under this License. You may not convey a covered | |||||
| work if you are a party to an arrangement with a third party that is | |||||
| in the business of distributing software, under which you make payment | |||||
| to the third party based on the extent of your activity of conveying | |||||
| the work, and under which the third party grants, to any of the | |||||
| parties who would receive the covered work from you, a discriminatory | |||||
| patent license (a) in connection with copies of the covered work | |||||
| conveyed by you (or copies made from those copies), or (b) primarily | |||||
| for and in connection with specific products or compilations that | |||||
| contain the covered work, unless you entered into that arrangement, | |||||
| or that patent license was granted, prior to 28 March 2007. | |||||
| Nothing in this License shall be construed as excluding or limiting | |||||
| any implied license or other defenses to infringement that may | |||||
| otherwise be available to you under applicable patent law. | |||||
| 12. No Surrender of Others' Freedom. | |||||
| If conditions are imposed on you (whether by court order, agreement or | |||||
| otherwise) that contradict the conditions of this License, they do not | |||||
| excuse you from the conditions of this License. If you cannot convey a | |||||
| covered work so as to satisfy simultaneously your obligations under this | |||||
| License and any other pertinent obligations, then as a consequence you may | |||||
| not convey it at all. For example, if you agree to terms that obligate you | |||||
| to collect a royalty for further conveying from those to whom you convey | |||||
| the Program, the only way you could satisfy both those terms and this | |||||
| License would be to refrain entirely from conveying the Program. | |||||
| 13. Use with the GNU Affero General Public License. | |||||
| Notwithstanding any other provision of this License, you have | |||||
| permission to link or combine any covered work with a work licensed | |||||
| under version 3 of the GNU Affero General Public License into a single | |||||
| combined work, and to convey the resulting work. The terms of this | |||||
| License will continue to apply to the part which is the covered work, | |||||
| but the special requirements of the GNU Affero General Public License, | |||||
| section 13, concerning interaction through a network will apply to the | |||||
| combination as such. | |||||
| 14. Revised Versions of this License. | |||||
| The Free Software Foundation may publish revised and/or new versions of | |||||
| the GNU General Public License from time to time. Such new versions will | |||||
| be similar in spirit to the present version, but may differ in detail to | |||||
| address new problems or concerns. | |||||
| Each version is given a distinguishing version number. If the | |||||
| Program specifies that a certain numbered version of the GNU General | |||||
| Public License "or any later version" applies to it, you have the | |||||
| option of following the terms and conditions either of that numbered | |||||
| version or of any later version published by the Free Software | |||||
| Foundation. If the Program does not specify a version number of the | |||||
| GNU General Public License, you may choose any version ever published | |||||
| by the Free Software Foundation. | |||||
| If the Program specifies that a proxy can decide which future | |||||
| versions of the GNU General Public License can be used, that proxy's | |||||
| public statement of acceptance of a version permanently authorizes you | |||||
| to choose that version for the Program. | |||||
| Later license versions may give you additional or different | |||||
| permissions. However, no additional obligations are imposed on any | |||||
| author or copyright holder as a result of your choosing to follow a | |||||
| later version. | |||||
| 15. Disclaimer of Warranty. | |||||
| THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | |||||
| APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | |||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | |||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | |||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |||||
| PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | |||||
| IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | |||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | |||||
| 16. Limitation of Liability. | |||||
| IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | |||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | |||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | |||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | |||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | |||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | |||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | |||||
| SUCH DAMAGES. | |||||
| 17. Interpretation of Sections 15 and 16. | |||||
| If the disclaimer of warranty and limitation of liability provided | |||||
| above cannot be given local legal effect according to their terms, | |||||
| reviewing courts shall apply local law that most closely approximates | |||||
| an absolute waiver of all civil liability in connection with the | |||||
| Program, unless a warranty or assumption of liability accompanies a | |||||
| copy of the Program in return for a fee. | |||||
| END OF TERMS AND CONDITIONS | |||||
| How to Apply These Terms to Your New Programs | |||||
| If you develop a new program, and you want it to be of the greatest | |||||
| possible use to the public, the best way to achieve this is to make it | |||||
| free software which everyone can redistribute and change under these terms. | |||||
| To do so, attach the following notices to the program. It is safest | |||||
| to attach them to the start of each source file to most effectively | |||||
| state the exclusion of warranty; and each file should have at least | |||||
| the "copyright" line and a pointer to where the full notice is found. | |||||
| {one line to give the program's name and a brief idea of what it does.} | |||||
| Copyright (C) {year} {name of author} | |||||
| This program is free software: you can redistribute it and/or modify | |||||
| it under the terms of the GNU General Public License as published by | |||||
| the Free Software Foundation, either version 3 of the License, or | |||||
| (at your option) any later version. | |||||
| This program is distributed in the hope that it will be useful, | |||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
| GNU General Public License for more details. | |||||
| You should have received a copy of the GNU General Public License | |||||
| along with this program. If not, see <http://www.gnu.org/licenses/>. | |||||
| Also add information on how to contact you by electronic and paper mail. | |||||
| If the program does terminal interaction, make it output a short | |||||
| notice like this when it starts in an interactive mode: | |||||
| {project} Copyright (C) {year} {fullname} | |||||
| This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||||
| This is free software, and you are welcome to redistribute it | |||||
| under certain conditions; type `show c' for details. | |||||
| The hypothetical commands `show w' and `show c' should show the appropriate | |||||
| parts of the General Public License. Of course, your program's commands | |||||
| might be different; for a GUI interface, you would use an "about box". | |||||
| You should also get your employer (if you work as a programmer) or school, | |||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | |||||
| For more information on this, and how to apply and follow the GNU GPL, see | |||||
| <http://www.gnu.org/licenses/>. | |||||
| The GNU General Public License does not permit incorporating your program | |||||
| into proprietary programs. If your program is a subroutine library, you | |||||
| may consider it more useful to permit linking proprietary applications with | |||||
| the library. If this is what you want to do, use the GNU Lesser General | |||||
| Public License instead of this License. But first, please read | |||||
| <http://www.gnu.org/philosophy/why-not-lgpl.html>. | |||||
| @@ -0,0 +1,41 @@ | |||||
| #include "addfellowdialog.h" | |||||
| #include "ui_addfellowdialog.h" | |||||
| #include <QHostAddress> | |||||
| #include <QMessageBox> | |||||
| AddFellowDialog::AddFellowDialog(QWidget *parent) : | |||||
| QDialog(parent), | |||||
| ui(new Ui::AddFellowDialog) | |||||
| { | |||||
| ui->setupUi(this); | |||||
| connect(ui->okBtn, SIGNAL(clicked(bool)), this, SLOT(onOkClicked())); | |||||
| } | |||||
| AddFellowDialog::~AddFellowDialog() | |||||
| { | |||||
| delete ui; | |||||
| } | |||||
| QString AddFellowDialog::getIp() | |||||
| { | |||||
| return ui->ipEdit->text(); | |||||
| } | |||||
| void AddFellowDialog::onOkClicked() | |||||
| { | |||||
| auto ip = ui->ipEdit->text(); | |||||
| if (isValidIp(ip)) | |||||
| { | |||||
| accept(); | |||||
| } | |||||
| else | |||||
| { | |||||
| QMessageBox::warning(this, "ip地址无效", "要添加的ip地址无效"); | |||||
| } | |||||
| } | |||||
| bool AddFellowDialog::isValidIp(const QString &ip) | |||||
| { | |||||
| QHostAddress address; | |||||
| return address.setAddress(ip); | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| #ifndef ADDFELLOWDIALOG_H | |||||
| #define ADDFELLOWDIALOG_H | |||||
| #include <QDialog> | |||||
| namespace Ui { | |||||
| class AddFellowDialog; | |||||
| } | |||||
| class AddFellowDialog : public QDialog | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| explicit AddFellowDialog(QWidget *parent = 0); | |||||
| ~AddFellowDialog(); | |||||
| public: | |||||
| QString getIp(); | |||||
| private slots: | |||||
| void onOkClicked(); | |||||
| private: | |||||
| bool isValidIp(const QString& ip); | |||||
| private: | |||||
| Ui::AddFellowDialog *ui; | |||||
| }; | |||||
| #endif // ADDFELLOWDIALOG_H | |||||
| @@ -0,0 +1,38 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <ui version="4.0"> | |||||
| <class>AddFellowDialog</class> | |||||
| <widget class="QDialog" name="AddFellowDialog"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>0</y> | |||||
| <width>400</width> | |||||
| <height>45</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="windowTitle"> | |||||
| <string>手动添加好友</string> | |||||
| </property> | |||||
| <layout class="QHBoxLayout" name="horizontalLayout"> | |||||
| <item> | |||||
| <widget class="QLineEdit" name="ipEdit"> | |||||
| <property name="text"> | |||||
| <string/> | |||||
| </property> | |||||
| <property name="placeholderText"> | |||||
| <string>请输入要添加的好友ip</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QPushButton" name="okBtn"> | |||||
| <property name="text"> | |||||
| <string>确定</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| <resources/> | |||||
| <connections/> | |||||
| </ui> | |||||
| @@ -0,0 +1,57 @@ | |||||
| #include "chooseemojidlg.h" | |||||
| #include "ui_chooseemojidlg.h" | |||||
| #include <QMouseEvent> | |||||
| #include <QMovie> | |||||
| #include "emoji.h" | |||||
| ChooseEmojiDlg::ChooseEmojiDlg(QWidget *parent) : | |||||
| QDialog(parent), | |||||
| ui(new Ui::ChooseEmojiDlg) | |||||
| { | |||||
| ui->setupUi(this); | |||||
| setWindowFlags(Qt::Tool); | |||||
| setFixedSize(size()); | |||||
| mMovie = new QMovie(this); | |||||
| ui->gifLabel->setMovie(mMovie); | |||||
| connect(ui->chooseWidget, SIGNAL(choose(int)), this, SLOT(choose(int))); | |||||
| connect(ui->chooseWidget, SIGNAL(choose(int)), this, SLOT(hide())); | |||||
| connect(ui->chooseWidget, SIGNAL(hesitate(int)), this, SLOT(hesitate(int))); | |||||
| } | |||||
| ChooseEmojiDlg::~ChooseEmojiDlg() | |||||
| { | |||||
| delete ui; | |||||
| } | |||||
| void ChooseEmojiDlg::hideEvent(QHideEvent *event) | |||||
| { | |||||
| mMovie->stop(); | |||||
| } | |||||
| void ChooseEmojiDlg::hesitate(int index) | |||||
| { | |||||
| if (index == -1) | |||||
| { | |||||
| ui->hintLabel->setText(""); | |||||
| mMovie->stop(); | |||||
| ui->gifLabel->setText(""); | |||||
| } | |||||
| else | |||||
| { | |||||
| ui->hintLabel->setText(QString(g_emojiText[index])+" "+QString(g_emojis[index])); | |||||
| auto gif = ":/default/res/face/"+QString::number(index+1)+".gif"; | |||||
| mMovie->stop(); | |||||
| mMovie->setFileName(gif); | |||||
| mMovie->start(); | |||||
| } | |||||
| } | |||||
| void ChooseEmojiDlg::choose(int index) | |||||
| { | |||||
| emit choose(g_emojis[index]); | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| #ifndef CHOOSEEMOJIDLG_H | |||||
| #define CHOOSEEMOJIDLG_H | |||||
| #include <QDialog> | |||||
| namespace Ui { | |||||
| class ChooseEmojiDlg; | |||||
| } | |||||
| class ChooseEmojiDlg : public QDialog | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| explicit ChooseEmojiDlg(QWidget *parent = 0); | |||||
| ~ChooseEmojiDlg(); | |||||
| protected: | |||||
| void hideEvent(QHideEvent *event); | |||||
| signals: | |||||
| void choose(const QString& emojiText); | |||||
| private slots: | |||||
| void hesitate(int index); | |||||
| void choose(int index); | |||||
| private: | |||||
| Ui::ChooseEmojiDlg *ui; | |||||
| QMovie* mMovie; | |||||
| }; | |||||
| #endif // CHOOSEEMOJIDLG_H | |||||
| @@ -0,0 +1,77 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <ui version="4.0"> | |||||
| <class>ChooseEmojiDlg</class> | |||||
| <widget class="QDialog" name="ChooseEmojiDlg"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>0</y> | |||||
| <width>401</width> | |||||
| <height>181</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="sizePolicy"> | |||||
| <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> | |||||
| <horstretch>0</horstretch> | |||||
| <verstretch>0</verstretch> | |||||
| </sizepolicy> | |||||
| </property> | |||||
| <property name="mouseTracking"> | |||||
| <bool>true</bool> | |||||
| </property> | |||||
| <property name="windowTitle"> | |||||
| <string>选择要插入的表情</string> | |||||
| </property> | |||||
| <widget class="QLabel" name="hintLabel"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>150</y> | |||||
| <width>351</width> | |||||
| <height>31</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="frameShape"> | |||||
| <enum>QFrame::Box</enum> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string/> | |||||
| </property> | |||||
| </widget> | |||||
| <widget class="ChooseEmojiWidget" name="chooseWidget"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>0</y> | |||||
| <width>401</width> | |||||
| <height>151</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="styleSheet"> | |||||
| <string notr="true">background-image: url(:/default/res/face/page.bmp);</string> | |||||
| </property> | |||||
| </widget> | |||||
| <widget class="QLabel" name="gifLabel"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>360</x> | |||||
| <y>150</y> | |||||
| <width>41</width> | |||||
| <height>31</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string/> | |||||
| </property> | |||||
| </widget> | |||||
| </widget> | |||||
| <customwidgets> | |||||
| <customwidget> | |||||
| <class>ChooseEmojiWidget</class> | |||||
| <extends>QLabel</extends> | |||||
| <header>chooseemojiwidget.h</header> | |||||
| </customwidget> | |||||
| </customwidgets> | |||||
| <resources/> | |||||
| <connections/> | |||||
| </ui> | |||||
| @@ -0,0 +1,49 @@ | |||||
| #include "chooseemojiwidget.h" | |||||
| #include <QMouseEvent> | |||||
| #include "emoji.h" | |||||
| ChooseEmojiWidget::ChooseEmojiWidget(QWidget* parent) | |||||
| :QLabel(parent), mIndex(-1) | |||||
| { | |||||
| setMouseTracking(true); | |||||
| } | |||||
| void ChooseEmojiWidget::mousePressEvent(QMouseEvent *event) | |||||
| { | |||||
| mIndex = getIndex(event->x(), event->y()); | |||||
| } | |||||
| void ChooseEmojiWidget::mouseMoveEvent(QMouseEvent *event) | |||||
| { | |||||
| auto index = getIndex(event->x(), event->y()); | |||||
| if (index >= 0 && index < EMOJI_LEN) | |||||
| { | |||||
| emit hesitate(index); | |||||
| } | |||||
| else | |||||
| { | |||||
| emit hesitate(-1); | |||||
| } | |||||
| } | |||||
| void ChooseEmojiWidget::mouseReleaseEvent(QMouseEvent *event) | |||||
| { | |||||
| auto index = getIndex(event->x(), event->y()); | |||||
| if (mIndex == index && mIndex >= 0 && mIndex < EMOJI_LEN) | |||||
| { | |||||
| emit choose(mIndex); | |||||
| } | |||||
| mIndex = -1; | |||||
| } | |||||
| int ChooseEmojiWidget::getIndex(int x, int y) | |||||
| { | |||||
| int col = x/25; | |||||
| int row = y/25; | |||||
| auto index = row * EMOJI_BMP_COL + col; | |||||
| if (index >= 0 && index < EMOJI_LEN) | |||||
| return index; | |||||
| return -1; | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| #ifndef CHOOSEEMOJIWIDGET_H | |||||
| #define CHOOSEEMOJIWIDGET_H | |||||
| #include <QLabel> | |||||
| class ChooseEmojiWidget : public QLabel | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| ChooseEmojiWidget(QWidget* parent=0); | |||||
| protected: | |||||
| void mousePressEvent(QMouseEvent *event) override; | |||||
| void mouseMoveEvent(QMouseEvent *event) override; | |||||
| void mouseReleaseEvent(QMouseEvent* event) override; | |||||
| private: | |||||
| int getIndex(int x, int y); | |||||
| signals: | |||||
| void choose(int emojiIndex); | |||||
| void hesitate(int emojiIndex); | |||||
| private: | |||||
| int mIndex; | |||||
| }; | |||||
| #endif // CHOOSEEMOJIWIDGET_H | |||||
| @@ -0,0 +1,102 @@ | |||||
| <RCC> | |||||
| <qresource prefix="/default"> | |||||
| <file>res/icon.png</file> | |||||
| <file>res/face/1.gif</file> | |||||
| <file>res/face/2.gif</file> | |||||
| <file>res/face/3.gif</file> | |||||
| <file>res/face/4.gif</file> | |||||
| <file>res/face/5.gif</file> | |||||
| <file>res/face/6.gif</file> | |||||
| <file>res/face/7.gif</file> | |||||
| <file>res/face/8.gif</file> | |||||
| <file>res/face/9.gif</file> | |||||
| <file>res/face/10.gif</file> | |||||
| <file>res/face/11.gif</file> | |||||
| <file>res/face/12.gif</file> | |||||
| <file>res/face/13.gif</file> | |||||
| <file>res/face/14.gif</file> | |||||
| <file>res/face/15.gif</file> | |||||
| <file>res/face/16.gif</file> | |||||
| <file>res/face/17.gif</file> | |||||
| <file>res/face/18.gif</file> | |||||
| <file>res/face/19.gif</file> | |||||
| <file>res/face/20.gif</file> | |||||
| <file>res/face/21.gif</file> | |||||
| <file>res/face/22.gif</file> | |||||
| <file>res/face/23.gif</file> | |||||
| <file>res/face/24.gif</file> | |||||
| <file>res/face/25.gif</file> | |||||
| <file>res/face/26.gif</file> | |||||
| <file>res/face/27.gif</file> | |||||
| <file>res/face/28.gif</file> | |||||
| <file>res/face/29.gif</file> | |||||
| <file>res/face/30.gif</file> | |||||
| <file>res/face/31.gif</file> | |||||
| <file>res/face/32.gif</file> | |||||
| <file>res/face/33.gif</file> | |||||
| <file>res/face/34.gif</file> | |||||
| <file>res/face/35.gif</file> | |||||
| <file>res/face/36.gif</file> | |||||
| <file>res/face/37.gif</file> | |||||
| <file>res/face/38.gif</file> | |||||
| <file>res/face/39.gif</file> | |||||
| <file>res/face/40.gif</file> | |||||
| <file>res/face/41.gif</file> | |||||
| <file>res/face/42.gif</file> | |||||
| <file>res/face/43.gif</file> | |||||
| <file>res/face/44.gif</file> | |||||
| <file>res/face/45.gif</file> | |||||
| <file>res/face/46.gif</file> | |||||
| <file>res/face/47.gif</file> | |||||
| <file>res/face/48.gif</file> | |||||
| <file>res/face/49.gif</file> | |||||
| <file>res/face/50.gif</file> | |||||
| <file>res/face/51.gif</file> | |||||
| <file>res/face/52.gif</file> | |||||
| <file>res/face/53.gif</file> | |||||
| <file>res/face/54.gif</file> | |||||
| <file>res/face/55.gif</file> | |||||
| <file>res/face/56.gif</file> | |||||
| <file>res/face/57.gif</file> | |||||
| <file>res/face/58.gif</file> | |||||
| <file>res/face/59.gif</file> | |||||
| <file>res/face/60.gif</file> | |||||
| <file>res/face/61.gif</file> | |||||
| <file>res/face/62.gif</file> | |||||
| <file>res/face/63.gif</file> | |||||
| <file>res/face/64.gif</file> | |||||
| <file>res/face/65.gif</file> | |||||
| <file>res/face/66.gif</file> | |||||
| <file>res/face/67.gif</file> | |||||
| <file>res/face/68.gif</file> | |||||
| <file>res/face/69.gif</file> | |||||
| <file>res/face/70.gif</file> | |||||
| <file>res/face/71.gif</file> | |||||
| <file>res/face/72.gif</file> | |||||
| <file>res/face/73.gif</file> | |||||
| <file>res/face/74.gif</file> | |||||
| <file>res/face/75.gif</file> | |||||
| <file>res/face/76.gif</file> | |||||
| <file>res/face/77.gif</file> | |||||
| <file>res/face/78.gif</file> | |||||
| <file>res/face/79.gif</file> | |||||
| <file>res/face/80.gif</file> | |||||
| <file>res/face/81.gif</file> | |||||
| <file>res/face/82.gif</file> | |||||
| <file>res/face/83.gif</file> | |||||
| <file>res/face/84.gif</file> | |||||
| <file>res/face/85.gif</file> | |||||
| <file>res/face/86.gif</file> | |||||
| <file>res/face/87.gif</file> | |||||
| <file>res/face/88.gif</file> | |||||
| <file>res/face/89.gif</file> | |||||
| <file>res/face/90.gif</file> | |||||
| <file>res/face/91.gif</file> | |||||
| <file>res/face/92.gif</file> | |||||
| <file>res/face/93.gif</file> | |||||
| <file>res/face/94.gif</file> | |||||
| <file>res/face/95.gif</file> | |||||
| <file>res/face/96.gif</file> | |||||
| <file>res/face/page.bmp</file> | |||||
| </qresource> | |||||
| </RCC> | |||||
| @@ -0,0 +1,130 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <ui version="4.0"> | |||||
| <class>DownloadFileDlg</class> | |||||
| <widget class="QDialog" name="DownloadFileDlg"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>0</y> | |||||
| <width>542</width> | |||||
| <height>324</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="windowTitle"> | |||||
| <string>文件管理</string> | |||||
| </property> | |||||
| <layout class="QVBoxLayout" name="verticalLayout"> | |||||
| <property name="spacing"> | |||||
| <number>4</number> | |||||
| </property> | |||||
| <property name="leftMargin"> | |||||
| <number>8</number> | |||||
| </property> | |||||
| <property name="topMargin"> | |||||
| <number>8</number> | |||||
| </property> | |||||
| <property name="rightMargin"> | |||||
| <number>8</number> | |||||
| </property> | |||||
| <property name="bottomMargin"> | |||||
| <number>8</number> | |||||
| </property> | |||||
| <item> | |||||
| <layout class="QHBoxLayout" name="horizontalLayout"> | |||||
| <item> | |||||
| <widget class="QToolButton" name="clearBtn"> | |||||
| <property name="text"> | |||||
| <string>清除</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QToolButton" name="delBtn"> | |||||
| <property name="text"> | |||||
| <string>删除</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QToolButton" name="saveBtn"> | |||||
| <property name="text"> | |||||
| <string>保存</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <spacer name="horizontalSpacer"> | |||||
| <property name="orientation"> | |||||
| <enum>Qt::Horizontal</enum> | |||||
| </property> | |||||
| <property name="sizeHint" stdset="0"> | |||||
| <size> | |||||
| <width>40</width> | |||||
| <height>20</height> | |||||
| </size> | |||||
| </property> | |||||
| </spacer> | |||||
| </item> | |||||
| </layout> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QTableWidget" name="taskTable"> | |||||
| <property name="editTriggers"> | |||||
| <set>QAbstractItemView::NoEditTriggers</set> | |||||
| </property> | |||||
| <property name="selectionMode"> | |||||
| <enum>QAbstractItemView::SingleSelection</enum> | |||||
| </property> | |||||
| <property name="selectionBehavior"> | |||||
| <enum>QAbstractItemView::SelectRows</enum> | |||||
| </property> | |||||
| <property name="columnCount"> | |||||
| <number>5</number> | |||||
| </property> | |||||
| <attribute name="horizontalHeaderCascadingSectionResizes"> | |||||
| <bool>false</bool> | |||||
| </attribute> | |||||
| <attribute name="horizontalHeaderDefaultSectionSize"> | |||||
| <number>70</number> | |||||
| </attribute> | |||||
| <attribute name="horizontalHeaderStretchLastSection"> | |||||
| <bool>true</bool> | |||||
| </attribute> | |||||
| <attribute name="verticalHeaderVisible"> | |||||
| <bool>false</bool> | |||||
| </attribute> | |||||
| <column> | |||||
| <property name="text"> | |||||
| <string>类型</string> | |||||
| </property> | |||||
| </column> | |||||
| <column> | |||||
| <property name="text"> | |||||
| <string>状态</string> | |||||
| </property> | |||||
| </column> | |||||
| <column> | |||||
| <property name="text"> | |||||
| <string>好友</string> | |||||
| </property> | |||||
| </column> | |||||
| <column> | |||||
| <property name="text"> | |||||
| <string>文件名</string> | |||||
| </property> | |||||
| </column> | |||||
| <column> | |||||
| <property name="text"> | |||||
| <string>进度</string> | |||||
| </property> | |||||
| </column> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QLineEdit" name="searchEdit"/> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| <resources/> | |||||
| <connections/> | |||||
| </ui> | |||||
| @@ -0,0 +1,209 @@ | |||||
| #include "emoji.h" | |||||
| const char* g_emojis[EMOJI_LEN]={ | |||||
| "/:)", | |||||
| "/:~", | |||||
| "/:*", | |||||
| "/:|", | |||||
| "/8-)", | |||||
| "/:<", | |||||
| "/:$", | |||||
| "/:X", | |||||
| "/:Z", | |||||
| "/:'(", | |||||
| "/:-|", | |||||
| "/:@", | |||||
| "/:P", | |||||
| "/:D", | |||||
| "/:O", | |||||
| "/<rotate>", | |||||
| "/:(", | |||||
| "/:+", | |||||
| "/:lenhan", | |||||
| "/:Q", | |||||
| "/:T", | |||||
| "/;P", | |||||
| "/;-D", | |||||
| "/;d", | |||||
| "/;o", | |||||
| "/:g", | |||||
| "/|-)", | |||||
| "/:!", | |||||
| "/:L", | |||||
| "/:>", | |||||
| "/;bin", | |||||
| "/:fw", | |||||
| "/;fd", | |||||
| "/:-S", | |||||
| "/;?", | |||||
| "/;x", | |||||
| "/;@", | |||||
| "/:8", | |||||
| "/;!", | |||||
| "/!!!", | |||||
| "/:xx", | |||||
| "/:bye", | |||||
| "/:csweat", | |||||
| "/:knose", | |||||
| "/:applause", | |||||
| "/:cdale", | |||||
| "/:huaixiao", | |||||
| "/:shake", | |||||
| "/:lhenhen", | |||||
| "/:rhenhen", | |||||
| "/:yawn", | |||||
| "/:snooty", | |||||
| "/:chagrin", | |||||
| "/:kcry", | |||||
| "/:yinxian", | |||||
| "/:qinqin", | |||||
| "/:xiaren", | |||||
| "/:kelin", | |||||
| "/:caidao", | |||||
| "/:xig", | |||||
| "/:bj", | |||||
| "/:basketball", | |||||
| "/:pingpong", | |||||
| "/:jump", | |||||
| "/:coffee", | |||||
| "/:eat", | |||||
| "/:pig", | |||||
| "/:rose", | |||||
| "/:fade", | |||||
| "/:kiss", | |||||
| "/:heart", | |||||
| "/:break", | |||||
| "/:cake", | |||||
| "/:shd", | |||||
| "/:bomb", | |||||
| "/:dao", | |||||
| "/:footb", | |||||
| "/:piaocon", | |||||
| "/:shit", | |||||
| "/:oh", | |||||
| "/:moon", | |||||
| "/:sun", | |||||
| "/;gift", | |||||
| "/:hug", | |||||
| "/:strong", | |||||
| "/;weak", | |||||
| "/:share", | |||||
| "/:shl", | |||||
| "/:baoquan", | |||||
| "/:cajole", | |||||
| "/:quantou", | |||||
| "/:chajin", | |||||
| "/:aini", | |||||
| "/:sayno", | |||||
| "/:sayok", | |||||
| "/:love" | |||||
| }; | |||||
| const char* g_emojiText[EMOJI_LEN]={ | |||||
| "微笑", | |||||
| "撇嘴", | |||||
| "色", | |||||
| "发呆", | |||||
| "得意", | |||||
| "流泪", | |||||
| "害羞", | |||||
| "闭嘴", | |||||
| "困", | |||||
| "大哭", | |||||
| "尴尬", | |||||
| "发怒", | |||||
| "调皮", | |||||
| "龇牙", | |||||
| "惊讶", | |||||
| "转圈", | |||||
| "难过", | |||||
| "酷", | |||||
| "冷汗", | |||||
| "抓狂", | |||||
| "吐", | |||||
| "偷笑", | |||||
| "可爱", | |||||
| "白眼", | |||||
| "傲慢", | |||||
| "饥饿", | |||||
| "困 ", | |||||
| "惊恐", | |||||
| "冷汗", | |||||
| "憨笑", | |||||
| "大兵", | |||||
| "飞吻", | |||||
| "奋斗", | |||||
| "咒骂", | |||||
| "疑问", | |||||
| "嘘……", | |||||
| "晕", | |||||
| "折磨", | |||||
| "衰", | |||||
| "骷髅", | |||||
| "敲打", | |||||
| "再见", | |||||
| "擦汗", | |||||
| "抠鼻", | |||||
| "鼓掌", | |||||
| "糗大了", | |||||
| "坏笑", | |||||
| "发抖", | |||||
| "左哼哼", | |||||
| "右哼哼", | |||||
| "哈欠", | |||||
| "鄙视", | |||||
| "委屈", | |||||
| "快哭了", | |||||
| "阴险", | |||||
| "亲亲", | |||||
| "吓", | |||||
| "可怜", | |||||
| "菜刀", | |||||
| "西瓜", | |||||
| "啤酒", | |||||
| "篮球", | |||||
| "乒乓", | |||||
| "跳", | |||||
| "咖啡", | |||||
| "吃饭", | |||||
| "猪头", | |||||
| "玫瑰", | |||||
| "枯萎", | |||||
| "示爱", | |||||
| "爱心", | |||||
| "心碎", | |||||
| "蛋糕", | |||||
| "闪电", | |||||
| "炸弹", | |||||
| "匕首", | |||||
| "足球", | |||||
| "瓢虫", | |||||
| "大便", | |||||
| "怄火", | |||||
| "月亮", | |||||
| "太阳", | |||||
| "礼物", | |||||
| "拥抱", | |||||
| "点赞", | |||||
| "弱", | |||||
| "握手", | |||||
| "胜利", | |||||
| "抱拳", | |||||
| "勾引", | |||||
| "拳头", | |||||
| "差劲", | |||||
| "爱你", | |||||
| "no", | |||||
| "ok", | |||||
| "爱情" | |||||
| }; | |||||
| @@ -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 | |||||
| @@ -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 | |||||
| @@ -0,0 +1,75 @@ | |||||
| #include "asynwait.h" | |||||
| #include <thread> | |||||
| #include <unistd.h> | |||||
| AsynWait::AsynWait() | |||||
| { | |||||
| } | |||||
| void AsynWait::start(int precision) | |||||
| { | |||||
| if (mStarted == true) | |||||
| return; | |||||
| mStarted=true; | |||||
| mPrecision = precision; | |||||
| thread thd(&AsynWait::run, this); | |||||
| mThd.swap(thd); | |||||
| } | |||||
| void AsynWait::stop() | |||||
| { | |||||
| mStarted=false; | |||||
| mThd.join(); | |||||
| } | |||||
| void AsynWait::addWaitPack(IdType packetId, AsynWait::OnWaitTimeout onTimeout, int msTimeo) | |||||
| { | |||||
| WaitPack pack; | |||||
| pack.id = packetId; | |||||
| pack.handler = onTimeout; | |||||
| pack.timeo = time_point_cast<milliseconds>(system_clock::now()) + milliseconds(msTimeo); | |||||
| mPacksMutex.lock(); | |||||
| mWaitPacks.push_back(pack); | |||||
| mPacksMutex.unlock(); | |||||
| } | |||||
| void AsynWait::clearWaitPack(IdType packetId) | |||||
| { | |||||
| mPacksMutex.lock(); | |||||
| mWaitPacks.remove_if([packetId](WaitPack pack){ | |||||
| return pack.id = packetId; | |||||
| }); | |||||
| mPacksMutex.unlock(); | |||||
| } | |||||
| void AsynWait::run() | |||||
| { | |||||
| list<WaitPack> timeos; | |||||
| while (mStarted) { | |||||
| usleep(mPrecision*1000); | |||||
| timeos.clear(); | |||||
| auto cur = system_clock::now(); | |||||
| mPacksMutex.lock(); | |||||
| mWaitPacks.remove_if([&cur, &timeos](const WaitPack& pack){ | |||||
| if (cur > pack.timeo) | |||||
| { | |||||
| timeos.push_back(pack); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| }); | |||||
| mPacksMutex.unlock(); | |||||
| if (!timeos.empty()) | |||||
| { | |||||
| for (auto& pack : timeos) | |||||
| pack.handler(pack.id); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,42 @@ | |||||
| #ifndef ASYNWAIT_H | |||||
| #define ASYNWAIT_H | |||||
| #include "uniqueid.h" | |||||
| #include <functional> | |||||
| #include <list> | |||||
| #include <mutex> | |||||
| #include <chrono> | |||||
| #include <thread> | |||||
| using namespace std; | |||||
| using namespace std::chrono; | |||||
| class AsynWait | |||||
| { | |||||
| public: | |||||
| typedef function<void (IdType)> OnWaitTimeout; | |||||
| AsynWait(); | |||||
| void start(int precision=200); | |||||
| void stop(); | |||||
| public: | |||||
| void addWaitPack(IdType packetId, OnWaitTimeout onTimeout, int msTimeo); | |||||
| void clearWaitPack(IdType packetId); | |||||
| private: | |||||
| void run(); | |||||
| private: | |||||
| struct WaitPack{ | |||||
| IdType id; | |||||
| OnWaitTimeout handler; | |||||
| time_point<system_clock, milliseconds> timeo; | |||||
| }; | |||||
| list<WaitPack> mWaitPacks; | |||||
| mutex mPacksMutex; | |||||
| bool mStarted=false; | |||||
| int mPrecision; | |||||
| thread mThd; | |||||
| }; | |||||
| #endif // ASYNWAIT_H | |||||
| @@ -0,0 +1,170 @@ | |||||
| #ifndef CONTENT_H | |||||
| #define CONTENT_H | |||||
| #include <string> | |||||
| #include "protocol.h" | |||||
| #include "uniqueid.h" | |||||
| #include <sys/stat.h> | |||||
| #include "ipmsg.h" | |||||
| #include "utils.h" | |||||
| #include "parcelable.h" | |||||
| using namespace std; | |||||
| enum class ContentType{Text, Knock, File, Image, Id}; | |||||
| /** | |||||
| * @brief 消息内容 | |||||
| */ | |||||
| class Content : public Parcelable | |||||
| { | |||||
| public: | |||||
| IdType packetNo; | |||||
| void setPacketNo(string val){ | |||||
| packetNo = stoul(val); | |||||
| } | |||||
| void setPacketNo(IdType val){ | |||||
| packetNo = val; | |||||
| } | |||||
| virtual ~Content(){} | |||||
| ContentType type() const {return mType;} | |||||
| protected: | |||||
| ContentType mType; | |||||
| public: | |||||
| virtual void writeTo(Parcel& out) const override | |||||
| { | |||||
| out.write(mType); | |||||
| out.write(packetNo); | |||||
| } | |||||
| virtual void readFrom(Parcel& in) override | |||||
| { | |||||
| in.read(mType); | |||||
| in.read(packetNo); | |||||
| } | |||||
| }; | |||||
| class IdContent : public Content | |||||
| { | |||||
| public: | |||||
| IdContent(){mType = ContentType::Id;} | |||||
| IdType id; | |||||
| }; | |||||
| class TextContent : public Content | |||||
| { | |||||
| public: | |||||
| TextContent(){mType = ContentType::Text;} | |||||
| string text; | |||||
| string format; | |||||
| public: | |||||
| virtual void writeTo(Parcel& out) const override | |||||
| { | |||||
| Content::writeTo(out); | |||||
| out.writeString(text); | |||||
| out.writeString(format); | |||||
| } | |||||
| virtual void readFrom(Parcel& in) override | |||||
| { | |||||
| Content::readFrom(in); | |||||
| in.readString(text); | |||||
| in.readString(format); | |||||
| } | |||||
| }; | |||||
| class FileContent : public Content | |||||
| { | |||||
| public: | |||||
| FileContent(){mType = ContentType::File;} | |||||
| IdType fileId; | |||||
| string filename; | |||||
| string path;//保存路径或要发送的文件的路径 | |||||
| int size = 0; | |||||
| int modifyTime = 0; | |||||
| int fileType = 0; | |||||
| public: | |||||
| static unique_ptr<FileContent> createFileContentToSend(const string& filePath) | |||||
| { | |||||
| static UniqueId mFileId; | |||||
| struct stat fInfo; | |||||
| auto ret = stat(filePath.c_str(), &fInfo); | |||||
| if (ret != 0) | |||||
| return nullptr; | |||||
| unique_ptr<FileContent> file(new FileContent()); | |||||
| file->fileId = mFileId.get(); | |||||
| file->path = filePath; | |||||
| file->filename = getFileNameFromPath(filePath); | |||||
| if (S_ISREG(fInfo.st_mode)) | |||||
| file->fileType = IPMSG_FILE_REGULAR; | |||||
| else if (S_ISREG(fInfo.st_mode)) | |||||
| file->fileType = IPMSG_FILE_DIR; | |||||
| else | |||||
| return nullptr;//先不支持其他类型 | |||||
| file->size = fInfo.st_size; | |||||
| file->modifyTime = fInfo.st_mtimespec.tv_sec; | |||||
| return file; | |||||
| } | |||||
| }; | |||||
| class KnockContent: public Content | |||||
| { | |||||
| public: | |||||
| KnockContent(){mType = ContentType::Knock;} | |||||
| }; | |||||
| class ImageContent: public Content | |||||
| { | |||||
| public: | |||||
| ImageContent(){mType = ContentType::Image;} | |||||
| string id; | |||||
| }; | |||||
| class ContentParcelFactory | |||||
| { | |||||
| public: | |||||
| static unique_ptr<Content> createFromParcel(Parcel& in) | |||||
| { | |||||
| //先读取父类信息 | |||||
| Content content; | |||||
| auto pos = in.mark(); | |||||
| content.readFrom(in); | |||||
| in.unmark(pos); | |||||
| //根据类型读取剩余数据 | |||||
| Content* ptr = nullptr; | |||||
| switch (content.type()) { | |||||
| case ContentType::Text: | |||||
| ptr = new TextContent; | |||||
| break; | |||||
| case ContentType::Knock: | |||||
| ptr = new KnockContent; | |||||
| break; | |||||
| case ContentType::File: | |||||
| ptr = new FileContent; | |||||
| break; | |||||
| case ContentType::Image: | |||||
| ptr = new ImageContent; | |||||
| break; | |||||
| case ContentType::Id: | |||||
| ptr = new IdContent; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| if (ptr) | |||||
| ptr->readFrom(in); | |||||
| return unique_ptr<Content>(ptr); | |||||
| } | |||||
| }; | |||||
| #endif // CONTENT_H | |||||
| @@ -0,0 +1,13 @@ | |||||
| #include "defer.h" | |||||
| Defer::Defer(function<void ()> deleter) | |||||
| :mDeleter(deleter) | |||||
| { | |||||
| } | |||||
| Defer::~Defer() | |||||
| { | |||||
| if (mDeleter) | |||||
| mDeleter(); | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| #ifndef DEFER_H | |||||
| #define DEFER_H | |||||
| #include <functional> | |||||
| using namespace std; | |||||
| class Defer | |||||
| { | |||||
| public: | |||||
| Defer(function<void ()> deleter); | |||||
| ~Defer(); | |||||
| private: | |||||
| function<void ()> mDeleter; | |||||
| }; | |||||
| #endif // DEFER_H | |||||
| @@ -0,0 +1,73 @@ | |||||
| #include "encoding.h" | |||||
| #include <iconv.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #include <errno.h> | |||||
| #include <stdio.h> | |||||
| Encoding* encIn = new Encoding("GBK", "UTF-8"); | |||||
| Encoding* encOut = new Encoding("UTF-8", "GBK"); | |||||
| Encoding::Encoding(const string &fromCharset, const string &toCharset) | |||||
| { | |||||
| //iconv_open is a freek(to goes first) | |||||
| mIconv = iconv_open(toCharset.c_str(), fromCharset.c_str()); | |||||
| } | |||||
| Encoding::~Encoding() | |||||
| { | |||||
| if (mIconv != (iconv_t)-1) | |||||
| iconv_close(mIconv); | |||||
| } | |||||
| vector<char> Encoding::convert(const vector<char> &str) | |||||
| { | |||||
| vector<char> result(str.size()*3); | |||||
| auto len = result.size(); | |||||
| if (convert(str.data(), str.size(), result.data(), &len)) | |||||
| { | |||||
| result.resize(len); | |||||
| result.shrink_to_fit(); | |||||
| return result; | |||||
| } | |||||
| return str; | |||||
| } | |||||
| string Encoding::convert(const string &str) | |||||
| { | |||||
| auto len = str.length()*3; | |||||
| unique_ptr<char[]> buf(new char[len]); | |||||
| if (convert(str.data(), str.size(), buf.get(), &len)) | |||||
| { | |||||
| string result = buf.get(); | |||||
| return result; | |||||
| } | |||||
| return str; | |||||
| } | |||||
| bool Encoding::convert(const char *input, size_t len, char *output, size_t *outLen) | |||||
| { | |||||
| if (mIconv == (iconv_t)-1) | |||||
| return false; | |||||
| //copy in str | |||||
| size_t inLen = len; | |||||
| auto inBuf = unique_ptr<char[]>(new char[inLen+1]); | |||||
| char* pIn = inBuf.get(); | |||||
| memcpy(pIn, input, inLen); | |||||
| pIn[inLen]=0; | |||||
| //do convert | |||||
| int ret = iconv(mIconv, &pIn, &inLen, &output, outLen); | |||||
| if (ret == -1) | |||||
| { | |||||
| perror("convert failed"); | |||||
| return false; | |||||
| } | |||||
| *output = 0; | |||||
| return true; | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| #ifndef ENCODING_H | |||||
| #define ENCODING_H | |||||
| #include <vector> | |||||
| #include <string> | |||||
| using namespace std; | |||||
| #include <iconv.h> | |||||
| class Encoding | |||||
| { | |||||
| public: | |||||
| Encoding(const string& fromCharset, const string& toCharset); | |||||
| ~Encoding(); | |||||
| vector<char> convert(const vector<char>& str); | |||||
| string convert(const string& str); | |||||
| /** | |||||
| * @brief convert 编码转换 | |||||
| * @param input 源内存首地址 | |||||
| * @param len 源长度 | |||||
| * @param output 目标内存首地址 | |||||
| * @param outLen 输入输出参数,输入目标缓冲区大小,输出实际使用大小 | |||||
| * @return | |||||
| */ | |||||
| bool convert(const char* input, size_t len, char* output, size_t* outLen); | |||||
| private: | |||||
| iconv_t mIconv; | |||||
| }; | |||||
| extern Encoding* encOut; | |||||
| extern Encoding* encIn; | |||||
| #endif // ENCODING_H | |||||
| @@ -0,0 +1,289 @@ | |||||
| #include "feiqcommu.h" | |||||
| #include "udpcommu.h" | |||||
| #include "ipmsg.h" | |||||
| #include <arpa/inet.h> | |||||
| #include "fellow.h" | |||||
| #include <sstream> | |||||
| #include <QDebug> | |||||
| #include <limits.h> | |||||
| #include "utils.h" | |||||
| FeiqCommu::FeiqCommu() | |||||
| { | |||||
| } | |||||
| void FeiqCommu::setMyHost(string host){mHost=host;} | |||||
| void FeiqCommu::setMyName(string name){ | |||||
| mName=name; | |||||
| std::replace(mName.begin(), mName.end(), HLIST_ENTRY_SEPARATOR, (char)HOSTLIST_DUMMY); | |||||
| } | |||||
| void FeiqCommu::addRecvProtocol(RecvProtocol *protocol){ | |||||
| mRecvPrtocols.push_back(protocol); | |||||
| } | |||||
| pair<bool, string> FeiqCommu::start() | |||||
| { | |||||
| if (!mUdp.bindTo(IPMSG_PORT)) | |||||
| { | |||||
| return {false, "bind failed:"+mUdp.getErrMsg()}; | |||||
| } | |||||
| if (!mUdp.startAsyncRecv( | |||||
| std::bind(&FeiqCommu::onRecv, this, placeholders::_1,placeholders::_2) | |||||
| ) | |||||
| ) | |||||
| { | |||||
| mUdp.close(); | |||||
| return {false, "start aysnc recv failed:"+mUdp.getErrMsg()}; | |||||
| } | |||||
| if (!mTcpServer.start(IPMSG_PORT)){ | |||||
| mUdp.close(); | |||||
| return {false, "无法启动文件服务"}; | |||||
| } | |||||
| mTcpServer.whenNewClient(std::bind(&FeiqCommu::onTcpClientConnected, this, placeholders::_1)); | |||||
| //其他字段是什么意思呢? | |||||
| mMac = mUdp.getBoundMac(); | |||||
| mVersion = "1_lbt6_0#128#"+mMac+"#0#0#0#4001#9"; | |||||
| return {true, ""}; | |||||
| } | |||||
| void FeiqCommu::stop() | |||||
| { | |||||
| mUdp.close(); | |||||
| } | |||||
| pair<IdType, string> FeiqCommu::send(const string &ip, SendProtocol &sender) | |||||
| { | |||||
| //打包 | |||||
| IdType packetNo=0; | |||||
| auto out = pack(sender, &packetNo); | |||||
| //发送 | |||||
| auto ret = mUdp.sentTo(ip, IPMSG_PORT, out.data(), out.size()); | |||||
| if (ret < 0) | |||||
| return {0, mUdp.getErrMsg()}; | |||||
| return {packetNo, ""}; | |||||
| } | |||||
| class SendRequestFile : public SendProtocol | |||||
| { | |||||
| public: | |||||
| int filetype=IPMSG_FILE_REGULAR; | |||||
| int fileid; | |||||
| int offset=0; | |||||
| int packetNo; | |||||
| int cmdId() override {return filetype == IPMSG_FILE_DIR ? IPMSG_GETDIRFILES : IPMSG_GETFILEDATA;} | |||||
| void write(ostream& os) override | |||||
| { | |||||
| char sep = HLIST_ENTRY_SEPARATOR; | |||||
| os<<std::hex<<packetNo<<sep | |||||
| <<fileid<<sep | |||||
| <<offset<<sep; | |||||
| } | |||||
| }; | |||||
| unique_ptr<TcpSocket> FeiqCommu::requestFileData(const string &ip, | |||||
| const FileContent& file, int offset) | |||||
| { | |||||
| unique_ptr<TcpSocket> client(new TcpSocket()); | |||||
| if (!client->connect(ip, IPMSG_PORT)) | |||||
| return nullptr; | |||||
| SendRequestFile requestSender; | |||||
| requestSender.packetNo = file.packetNo; | |||||
| requestSender.fileid = file.fileId; | |||||
| requestSender.offset = offset; | |||||
| auto request = pack(requestSender); | |||||
| int ret = client->send(request.data(), request.size()); | |||||
| if (ret < 0) | |||||
| return nullptr; | |||||
| return client; | |||||
| } | |||||
| void FeiqCommu::setFileServerHandler(FileServerHandler fileServerHandler) | |||||
| { | |||||
| mFileServerHandler = fileServerHandler; | |||||
| } | |||||
| void FeiqCommu::onRecv(const string &ip, vector<char> &data) | |||||
| { | |||||
| auto post = make_shared<Post>(); | |||||
| post->from->setIp(ip); | |||||
| //解析 | |||||
| if (!dumpRaw(data, *post)) | |||||
| return; | |||||
| //尝试获取mac | |||||
| auto info = dumpVersionInfo(post->from->version()); | |||||
| post->from->setMac(info.mac); | |||||
| //屏蔽自己的包 | |||||
| if (mMac == post->from->getMac()//匹配mac | |||||
| && mName == post->from->getName())//再匹配名字,以防mac获取失败 | |||||
| { | |||||
| return; | |||||
| } | |||||
| //除非收到下线包,否则都认为在线 | |||||
| post->from->setOnLine(true); | |||||
| //调用协议处理 | |||||
| for (auto& handler : mRecvPrtocols) | |||||
| { | |||||
| if (handler->read(post)) | |||||
| break; | |||||
| } | |||||
| } | |||||
| vector<char> FeiqCommu::pack(SendProtocol &sender, IdType* packetId) | |||||
| { | |||||
| //搜集数据 | |||||
| char sep = HLIST_ENTRY_SEPARATOR; | |||||
| auto packetNo = mPacketNo.get(); | |||||
| auto cmdId = sender.cmdId(); | |||||
| //拼接消息头 | |||||
| stringstream os; | |||||
| os<<mVersion<<sep<<packetNo<<sep<<mName<<sep<<mHost<<sep<<cmdId<<sep; | |||||
| //组装消息 | |||||
| sender.write(os); | |||||
| os.put(0); | |||||
| os.seekg(0, os.end); | |||||
| auto len = os.tellg(); | |||||
| os.seekg(0, os.beg); | |||||
| vector<char> buf(len); | |||||
| os.read(buf.data(), len); | |||||
| if (packetId != nullptr) | |||||
| *packetId = packetNo; | |||||
| return buf; | |||||
| } | |||||
| void FeiqCommu::onTcpClientConnected(int socket) | |||||
| { | |||||
| if (mFileServerHandler) | |||||
| { | |||||
| //接收请求 | |||||
| unique_ptr<TcpSocket> client(new TcpSocket(socket)); | |||||
| std::array<char,MAX_RCV_SIZE> buf; | |||||
| int ret = client->recv(buf.data(), MAX_RCV_SIZE); | |||||
| if (ret <= 0) | |||||
| return; | |||||
| //解析请求 | |||||
| vector<char> request(ret); | |||||
| std::copy(buf.begin(), buf.begin()+ret, request.begin()); | |||||
| Post post; | |||||
| if (!dumpRaw(request, post)) | |||||
| return; | |||||
| auto values = splitAllowSeperator(post.extra.begin(), post.extra.end(), HLIST_ENTRY_SEPARATOR); | |||||
| if (values.size() < 3) | |||||
| return; | |||||
| int packetNo = stoi(values[0], 0, 16); | |||||
| int fileId = stoi(values[1], 0, 16); | |||||
| int offset = stoi(values[2], 0, 16); | |||||
| //处理请求 | |||||
| mFileServerHandler(std::move(client), packetNo, fileId, offset); | |||||
| } | |||||
| } | |||||
| bool FeiqCommu::dumpRaw(vector<char>& data, Post& post) | |||||
| { | |||||
| auto ptr = data.begin(); | |||||
| auto last = data.end(); | |||||
| //取出协议的前5项 | |||||
| array<string, 5> value; | |||||
| auto count = 0uL; | |||||
| while (count < value.size()) { | |||||
| auto found = std::find(ptr, last, HLIST_ENTRY_SEPARATOR); | |||||
| if (found == last) | |||||
| break; | |||||
| auto size = std::distance(ptr, found); | |||||
| if (size == 0) | |||||
| { | |||||
| value[count]=""; | |||||
| } | |||||
| else | |||||
| { | |||||
| vector<char> buf(size+1); | |||||
| std::copy(ptr, found, buf.begin()); | |||||
| buf.push_back(0); | |||||
| std::replace(buf.begin(), buf.end(), HOSTLIST_DUMMY, HLIST_ENTRY_SEPARATOR); | |||||
| value[count]=toString(buf);//TODO:是否有编码问题? | |||||
| } | |||||
| ptr=found+1; | |||||
| ++count; | |||||
| } | |||||
| //协议错误 | |||||
| if (count < value.size()) | |||||
| return false; | |||||
| //解析 | |||||
| post.from->setVersion(value[0]); | |||||
| post.packetNo = value[1]; | |||||
| if (!post.from) | |||||
| post.from=make_shared<Fellow>(); | |||||
| post.from->setPcName(value[2]); | |||||
| post.from->setHost(value[3]); | |||||
| post.cmdId = stoull(value[4]); | |||||
| //取出extra部分 | |||||
| if (ptr != last) | |||||
| { | |||||
| auto size = std::distance(ptr, last); | |||||
| vector<char> buf(size); | |||||
| std::copy(ptr, last, buf.begin()); | |||||
| post.extra = buf; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| VersionInfo FeiqCommu::dumpVersionInfo(const string &version) | |||||
| { | |||||
| const char sep = '#'; | |||||
| VersionInfo info; | |||||
| auto tail = version.end(); | |||||
| auto head = version.begin(); | |||||
| //1 | |||||
| auto begin = std::find(head, tail, sep); | |||||
| if (begin == tail) | |||||
| return info; | |||||
| //2=begin | |||||
| begin = std::find(begin+1, tail, sep); | |||||
| if (begin == tail) | |||||
| return info; | |||||
| ++begin; | |||||
| //3=end | |||||
| auto end = std::find(begin, tail, sep); | |||||
| if (end == tail) | |||||
| return info; | |||||
| info.mac = version.substr(distance(head,begin), distance(begin,end)); | |||||
| return info; | |||||
| } | |||||
| @@ -0,0 +1,86 @@ | |||||
| #ifndef FEIQCOMMU_H | |||||
| #define FEIQCOMMU_H | |||||
| #include <string> | |||||
| #include <tuple> | |||||
| #include <vector> | |||||
| #include <memory> | |||||
| #include <array> | |||||
| #include "post.h" | |||||
| #include "protocol.h" | |||||
| #include "udpcommu.h" | |||||
| #include <atomic> | |||||
| #include "encoding.h" | |||||
| #include "tcpsocket.h" | |||||
| #include "tcpserver.h" | |||||
| #include "uniqueid.h" | |||||
| using namespace std; | |||||
| struct VersionInfo | |||||
| { | |||||
| string mac; | |||||
| }; | |||||
| /** | |||||
| * @brief 提供基础的feiq通信功能,对udp、tcp封装,负责消息的打包、解包 | |||||
| */ | |||||
| class FeiqCommu | |||||
| { | |||||
| public: | |||||
| typedef function<void (unique_ptr<TcpSocket>, int packetNo, int fileId, int offset)> FileServerHandler; | |||||
| FeiqCommu(); | |||||
| public: | |||||
| void setMyHost(string host); | |||||
| void setMyName(string name); | |||||
| void addRecvProtocol(RecvProtocol* protocol); | |||||
| public: | |||||
| /** | |||||
| * @brief start 启动feiq通信 | |||||
| * @return 是否启动成功,如果失败,返回具体失败原因 | |||||
| */ | |||||
| pair<bool, string> start(); | |||||
| void stop(); | |||||
| /** | |||||
| * @brief send 发送sender打包的内容 | |||||
| * @param ip 发给谁 | |||||
| * @param sender 要发送什么 | |||||
| * @return 发送成功,返回发送包ID,否则返回-1,并设置失败原因 | |||||
| */ | |||||
| pair<IdType, string> send(const string& ip, SendProtocol& sender); | |||||
| /** | |||||
| * @brief requestFileData 请求好友开始发送文件数据 | |||||
| * @param ip 向谁请求 | |||||
| * @param file 要请求的文件 | |||||
| * @return 如果请求成功,返回tcp连接,据此获取数据,否则返回nullptr | |||||
| */ | |||||
| unique_ptr<TcpSocket> requestFileData(const string& ip, const FileContent &file, int offset); | |||||
| /** | |||||
| * @brief setFileServerHandler 设置文件服务的处理 | |||||
| * @param fileServerHandler 参数:客户端socket连接,请求的文件id,请求的数据偏移 | |||||
| */ | |||||
| void setFileServerHandler(FileServerHandler fileServerHandler); | |||||
| public: | |||||
| static bool dumpRaw(vector<char> &data, Post &post); | |||||
| static VersionInfo dumpVersionInfo(const string& version); | |||||
| private: | |||||
| void onRecv(const string& ip, vector<char> &data); | |||||
| vector<char> pack(SendProtocol& sender, IdType *packetId = nullptr); | |||||
| void onTcpClientConnected(int socket); | |||||
| private: | |||||
| vector<RecvProtocol*> mRecvPrtocols; | |||||
| UdpCommu mUdp; | |||||
| string mHost=""; | |||||
| string mName=""; | |||||
| string mVersion=""; | |||||
| UniqueId mPacketNo; | |||||
| string mMac; | |||||
| TcpServer mTcpServer; | |||||
| FileServerHandler mFileServerHandler; | |||||
| }; | |||||
| #endif // FEIQCOMMU_H | |||||
| @@ -0,0 +1,934 @@ | |||||
| #include "feiqengine.h" | |||||
| #include "protocol.h" | |||||
| #include "ipmsg.h" | |||||
| #include <memory> | |||||
| #include "utils.h" | |||||
| #include <fstream> | |||||
| #include "defer.h" | |||||
| #include <arpa/inet.h> | |||||
| #include <unistd.h> | |||||
| #include <iostream> | |||||
| #include <iomanip> | |||||
| class ContentSender : public SendProtocol | |||||
| { | |||||
| public: | |||||
| void setContent(const Content* content) | |||||
| { | |||||
| mContent = content; | |||||
| } | |||||
| protected: | |||||
| const Content* mContent; | |||||
| }; | |||||
| class SendTextContent : public ContentSender | |||||
| { | |||||
| public: | |||||
| int cmdId() override{return IPMSG_SENDMSG|IPMSG_SENDCHECKOPT;} | |||||
| void write(ostream& os) override | |||||
| { | |||||
| auto content = static_cast<const TextContent*>(mContent); | |||||
| if (content->format.empty()) | |||||
| { | |||||
| os<<encOut->convert(content->text); | |||||
| } | |||||
| else | |||||
| { | |||||
| os<<encOut->convert(content->text) | |||||
| <<"{" | |||||
| <<encOut->convert(content->format) | |||||
| <<"}"; | |||||
| } | |||||
| } | |||||
| }; | |||||
| class SendKnockContent : public ContentSender | |||||
| { | |||||
| public: | |||||
| int cmdId() override{return IPMSG_KNOCK;} | |||||
| void write(ostream &os) override {(void)os;} | |||||
| }; | |||||
| class SendFileContent : public ContentSender | |||||
| { | |||||
| public: | |||||
| int cmdId() override {return IPMSG_SENDMSG|IPMSG_FILEATTACHOPT;} | |||||
| void write(ostream& os) override | |||||
| { | |||||
| auto content = static_cast<const FileContent*>(mContent); | |||||
| char sep = HLIST_ENTRY_SEPARATOR; | |||||
| auto filename = content->filename; | |||||
| stringReplace(filename, ":", "::");//估摸着协议不会变,偷懒下 | |||||
| os<<(char)0 | |||||
| <<to_string(content->fileId) | |||||
| <<sep | |||||
| <<encOut->convert(filename) | |||||
| <<sep | |||||
| <<std::hex<<content->size | |||||
| <<sep | |||||
| <<content->modifyTime | |||||
| <<sep | |||||
| <<content->fileType; | |||||
| } | |||||
| }; | |||||
| class SendImOnLine : public SendProtocol | |||||
| { | |||||
| public: | |||||
| SendImOnLine(const string& name):mName(name){} | |||||
| int cmdId() override{return IPMSG_BR_ENTRY;} | |||||
| void write(ostream &os) override | |||||
| { | |||||
| os<<encOut->convert(mName); | |||||
| } | |||||
| private: | |||||
| string mName; | |||||
| }; | |||||
| class SendImOffLine : public SendProtocol | |||||
| { | |||||
| public: | |||||
| SendImOffLine(const string& name):mName(name){} | |||||
| int cmdId() override {return IPMSG_BR_EXIT;} | |||||
| void write(ostream &os) override | |||||
| { | |||||
| os<<encOut->convert(mName); | |||||
| } | |||||
| private: | |||||
| string mName; | |||||
| }; | |||||
| /** | |||||
| * @brief The AnsSendCheck class 发送消息我收到了 | |||||
| */ | |||||
| class SendSentCheck : public SendProtocol | |||||
| { | |||||
| public: | |||||
| SendSentCheck(const string& packetNo) | |||||
| :mPacketNo(packetNo){} | |||||
| int cmdId() override{return IPMSG_RECVMSG;} | |||||
| void write(ostream& os) override | |||||
| { | |||||
| os<<mPacketNo; | |||||
| } | |||||
| private: | |||||
| string mPacketNo; | |||||
| }; | |||||
| /** | |||||
| * @brief The SendReadCheck class 发送消息我已经读了 | |||||
| */ | |||||
| class SendReadCheck : public SendProtocol | |||||
| { | |||||
| public: | |||||
| SendReadCheck(const string& packetNo) | |||||
| :mPacketNo(packetNo){} | |||||
| public: | |||||
| int cmdId() override {return IPMSG_READMSG;} | |||||
| void write(ostream& os) override | |||||
| { | |||||
| os<<mPacketNo; | |||||
| } | |||||
| private: | |||||
| string mPacketNo; | |||||
| }; | |||||
| /** | |||||
| * @brief The AnsBrEntry class 回复好友上线包 | |||||
| */ | |||||
| class AnsBrEntry : public SendProtocol | |||||
| { | |||||
| public: | |||||
| AnsBrEntry(const string& myName):mName(myName){} | |||||
| public: | |||||
| int cmdId() override { return IPMSG_ANSENTRY;} | |||||
| void write(ostream &os) override { | |||||
| os<<encOut->convert(mName); | |||||
| } | |||||
| private: | |||||
| const string& mName; | |||||
| }; | |||||
| //定义触发器 | |||||
| typedef std::function<void (shared_ptr<Post> post)> OnPostReady; | |||||
| #define DECLARE_TRIGGER(name)\ | |||||
| public:\ | |||||
| name(OnPostReady trigger) : mTrigger(trigger){}\ | |||||
| private:\ | |||||
| OnPostReady mTrigger;\ | |||||
| void trigger(shared_ptr<Post> post){mTrigger(post);} | |||||
| /** | |||||
| * @brief The RecvAnsEntry class 好友响应我们的上线消息 | |||||
| */ | |||||
| class RecvAnsEntry : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(RecvAnsEntry) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_CMD_SET(post->cmdId, IPMSG_ANSENTRY)) | |||||
| { | |||||
| auto converted = toString(encIn->convert(post->extra)); | |||||
| post->from->setName(converted); | |||||
| trigger(post); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The RecvBrEntry class 好友上线 | |||||
| */ | |||||
| class RecvBrEntry : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(RecvBrEntry) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_CMD_SET(post->cmdId, IPMSG_BR_ENTRY)) | |||||
| { | |||||
| post->from->setName(toString(encIn->convert(post->extra))); | |||||
| trigger(post); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The RecvBrExit class 好友下线 | |||||
| */ | |||||
| class RecvBrExit : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(RecvBrExit) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_CMD_SET(post->cmdId, IPMSG_BR_EXIT)) | |||||
| { | |||||
| post->from->setOnLine(false); | |||||
| trigger(post); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The RecvKnock class 窗口抖动 | |||||
| */ | |||||
| class RecvKnock : public RecvProtocol | |||||
| { | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_CMD_SET(post->cmdId, IPMSG_KNOCK)) | |||||
| { | |||||
| post->contents.push_back(make_shared<KnockContent>()); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The AnsSendCheck class | |||||
| */ | |||||
| class RecvSendCheck : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(RecvSendCheck) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_OPT_SET(post->cmdId, IPMSG_SENDCHECKOPT)) | |||||
| trigger(post); | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The RecvReadCheck class 接收到请求阅后通知 | |||||
| */ | |||||
| class RecvReadCheck : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(RecvReadCheck) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_OPT_SET(post->cmdId, IPMSG_READCHECKOPT)) | |||||
| trigger(post); | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The RecvText class 接收文本消息 | |||||
| */ | |||||
| class RecvText : public RecvProtocol | |||||
| { | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (!IS_CMD_SET(post->cmdId, IPMSG_SENDMSG)) | |||||
| return false; | |||||
| auto& extra = post->extra; | |||||
| auto end = extra.end(); | |||||
| auto begin = extra.begin(); | |||||
| auto found = std::find(begin, end, 0); | |||||
| if (found != begin)//有找到0,且不是第一个字符 | |||||
| { | |||||
| string rawText; | |||||
| rawText.assign(begin, found); | |||||
| auto content = createTextContent(encIn->convert(rawText)); | |||||
| post->contents.push_back(shared_ptr<Content>(std::move(content))); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private: | |||||
| unique_ptr<TextContent> createTextContent(const string& raw) | |||||
| { | |||||
| auto content = unique_ptr<TextContent>(new TextContent()); | |||||
| auto begin = raw.find('{'); | |||||
| auto end = raw.find("}", begin+1); | |||||
| if (begin != raw.npos && end != raw.npos) | |||||
| { | |||||
| content->text = raw.substr(0, begin); | |||||
| content->format = raw.substr(begin+1, end-begin-1); | |||||
| } | |||||
| else | |||||
| { | |||||
| content->text = raw; | |||||
| } | |||||
| return content; | |||||
| } | |||||
| }; | |||||
| class RecvFile : public RecvProtocol | |||||
| { | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (!IS_OPT_SET(post->cmdId, IPMSG_FILEATTACHOPT) || !IS_CMD_SET(post->cmdId, IPMSG_SENDMSG)) | |||||
| return false; | |||||
| //文件任务信息紧随文本消息之后,中间相隔一个ascii 0 | |||||
| //一个文件任务信息格式为fileId:filename:fileSize:modifyTime:fileType:其他扩展属性 | |||||
| //多个文件任务以ascii 7分割 | |||||
| //文件名含:,以::表示 | |||||
| auto& extra = post->extra; | |||||
| auto end = extra.end(); | |||||
| auto found = find(extra.begin(), end, 0)+1; | |||||
| while (found != end) | |||||
| { | |||||
| auto endTask = find(found, end, FILELIST_SEPARATOR); | |||||
| if (endTask == end) | |||||
| break; | |||||
| auto content = createFileContent(found, endTask); | |||||
| content->packetNo = stoul(post->packetNo); | |||||
| if (content != nullptr) | |||||
| post->contents.push_back(shared_ptr<Content>(std::move(content))); | |||||
| found = ++endTask; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| private: | |||||
| unique_ptr<FileContent> createFileContent(vector<char>::iterator from, | |||||
| vector<char>::iterator to) | |||||
| { | |||||
| unique_ptr<FileContent> content(new FileContent()); | |||||
| auto values = splitAllowSeperator(from, to, HLIST_ENTRY_SEPARATOR); | |||||
| const int fieldCount = 5; | |||||
| if (values.size() < fieldCount) | |||||
| return nullptr; | |||||
| content->fileId = stoi(values[0]); | |||||
| content->filename = encIn->convert(values[1]); | |||||
| content->size = stoi(values[2],0,16); | |||||
| content->modifyTime = stoi(values[3],0,16); | |||||
| content->fileType = stoi(values[4],0,16); | |||||
| return content; | |||||
| } | |||||
| }; | |||||
| class Debuger : public RecvProtocol | |||||
| { | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| cout<<"==========================="<<endl; | |||||
| cout<<"cmd id : "<<std::hex<<post->cmdId<<endl; | |||||
| cout<<"from: "<<post->from->toString()<<endl; | |||||
| int count = 0; | |||||
| for (unsigned char ch : post->extra){ | |||||
| cout<<setw(2)<<setfill('0')<<hex<<(unsigned int)ch<<" "; | |||||
| if (++count >= 8){ | |||||
| cout<<endl; | |||||
| count=0; | |||||
| } | |||||
| } | |||||
| cout<<endl; | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| class RecvReadMessage : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(RecvReadMessage) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (post->cmdId == IPMSG_RECVMSG) | |||||
| { | |||||
| IdType id = static_cast<IdType>(stoll(toString(post->extra))); | |||||
| auto content = make_shared<IdContent>(); | |||||
| content->id = id; | |||||
| post->addContent(content); | |||||
| trigger(post); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| class RecvImage : public RecvProtocol | |||||
| { | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (IS_CMD_SET(post->cmdId, IPMSG_SENDIMAGE) | |||||
| && IS_OPT_SET(post->cmdId, IPMSG_FILEATTACHOPT)) | |||||
| { | |||||
| char id[9]={0}; | |||||
| memcpy(id, post->extra.data(), 8); | |||||
| auto content = make_shared<ImageContent>(); | |||||
| content->id = id; | |||||
| post->contents.push_back(content); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| * @brief The EndRecv class 终止解析链 | |||||
| */ | |||||
| class EndRecv : public RecvProtocol | |||||
| { | |||||
| DECLARE_TRIGGER(EndRecv) | |||||
| public: | |||||
| bool read(shared_ptr<Post> post) | |||||
| { | |||||
| if (!post->contents.empty()) | |||||
| trigger(post); | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| //添加一条接收协议,触发时更新好友信息,并调用func | |||||
| #define ADD_RECV_PROTOCOL(protocol, func)\ | |||||
| {\ | |||||
| RecvProtocol* p = new protocol([this](shared_ptr<Post> post){\ | |||||
| post->from = this->addOrUpdateFellow(post->from);\ | |||||
| this->func(post);});\ | |||||
| mRecvProtocols.push_back(unique_ptr<RecvProtocol>(p));\ | |||||
| mCommu.addRecvProtocol(p);\ | |||||
| } | |||||
| //添加一条接收协议,无触发 | |||||
| #define ADD_RECV_PROTOCOL2(protocol)\ | |||||
| {\ | |||||
| RecvProtocol* p = new protocol();\ | |||||
| mRecvProtocols.push_back(unique_ptr<RecvProtocol>(p));\ | |||||
| mCommu.addRecvProtocol(p);\ | |||||
| } | |||||
| //添加一条接收协议,触发时更新好友信息 | |||||
| #define ADD_RECV_PROTOCOL3(protocol)\ | |||||
| {\ | |||||
| RecvProtocol* p = new protocol([this](shared_ptr<Post> post){\ | |||||
| post->from = this->addOrUpdateFellow(post->from);});\ | |||||
| mRecvProtocols.push_back(unique_ptr<RecvProtocol>(p));\ | |||||
| mCommu.addRecvProtocol(p);\ | |||||
| } | |||||
| //添加一条发送协议 | |||||
| #define ADD_SEND_PROTOCOL(protocol, sender, args...)\ | |||||
| {\ | |||||
| mContentSender[protocol]=make_shared<sender>(##args);\ | |||||
| } | |||||
| FeiqEngine::FeiqEngine() | |||||
| { | |||||
| ADD_RECV_PROTOCOL2(Debuger);//仅用于开发中的调试 | |||||
| ADD_RECV_PROTOCOL3(RecvAnsEntry); | |||||
| ADD_RECV_PROTOCOL(RecvBrEntry, onBrEntry); | |||||
| ADD_RECV_PROTOCOL3(RecvBrExit); | |||||
| ADD_RECV_PROTOCOL(RecvSendCheck, onSendCheck); | |||||
| ADD_RECV_PROTOCOL(RecvReadCheck, onReadCheck); | |||||
| ADD_RECV_PROTOCOL(RecvReadMessage, onReadMessage);//好友回复消息已经阅读 | |||||
| ADD_RECV_PROTOCOL2(RecvText); | |||||
| ADD_RECV_PROTOCOL2(RecvImage); | |||||
| ADD_RECV_PROTOCOL2(RecvKnock); | |||||
| ADD_RECV_PROTOCOL2(RecvFile); | |||||
| ADD_RECV_PROTOCOL(EndRecv, onMsg); | |||||
| ADD_SEND_PROTOCOL(ContentType::Text, SendTextContent); | |||||
| ADD_SEND_PROTOCOL(ContentType::Knock, SendKnockContent); | |||||
| ADD_SEND_PROTOCOL(ContentType::File, SendFileContent); | |||||
| mCommu.setFileServerHandler(std::bind(&FeiqEngine::fileServerHandler, | |||||
| this, | |||||
| placeholders::_1, | |||||
| placeholders::_2, | |||||
| placeholders::_3, | |||||
| placeholders::_4)); | |||||
| } | |||||
| pair<bool, string> FeiqEngine::send(shared_ptr<Fellow> fellow, shared_ptr<Content> content) | |||||
| { | |||||
| if (content == nullptr) | |||||
| return {false, "要发送的内容无效"}; | |||||
| auto& sender = mContentSender[content->type()]; | |||||
| if (sender == nullptr) | |||||
| return {false, "no send protocol can send"}; | |||||
| sender->setContent(content.get()); | |||||
| auto ip = fellow->getIp(); | |||||
| auto ret = mCommu.send(ip, *sender); | |||||
| if (ret.first == 0) | |||||
| { | |||||
| return {false, ret.second}; | |||||
| } | |||||
| content->setPacketNo(ret.first); | |||||
| if (content->type() == ContentType::File){ | |||||
| auto ptr = dynamic_pointer_cast<FileContent>(content); | |||||
| mModel.addUploadTask(fellow, ptr)->setObserver(mView); | |||||
| } | |||||
| else if (content->type() == ContentType::Text){ | |||||
| auto handler = std::bind(&FeiqEngine::onSendTimeo, this, placeholders::_1, ip, content); | |||||
| mAsyncWait.addWaitPack(content->packetNo, handler, 5000); | |||||
| } | |||||
| return {true, ""}; | |||||
| } | |||||
| pair<bool, string> FeiqEngine::sendFiles(shared_ptr<Fellow> fellow, list<shared_ptr<FileContent>> &files) | |||||
| { | |||||
| for (auto file : files) { | |||||
| auto ret = send(fellow, file); | |||||
| if (!ret.first) | |||||
| return ret; | |||||
| } | |||||
| return {true,""}; | |||||
| } | |||||
| bool FeiqEngine::downloadFile(FileTask* task) | |||||
| { | |||||
| if (task==nullptr) | |||||
| return false; | |||||
| task->setObserver(mView); | |||||
| auto func = [task, this](){ | |||||
| auto fellow = task->fellow(); | |||||
| auto content = task->getContent(); | |||||
| auto client = mCommu.requestFileData(fellow->getIp(), *content, 0); | |||||
| if (client == nullptr) | |||||
| { | |||||
| task->setState(FileTaskState::Error, "请求下载文件失败,可能好友已经取消"); | |||||
| return; | |||||
| } | |||||
| FILE* of = fopen(content->path.c_str(), "w+"); | |||||
| if (of == nullptr){ | |||||
| task->setState(FileTaskState::Error, "无法打开文件进行保存"); | |||||
| return; | |||||
| } | |||||
| // Defer{//TODO:工作异常 | |||||
| // [of](){ | |||||
| // cout<<"close file now"<<endl; | |||||
| // fclose(of); | |||||
| // } | |||||
| // }; | |||||
| const int unitSize = 2048;//一次请求2k | |||||
| const int maxTimeoCnt = 3;//最多允许超时3次 | |||||
| const int timeo = 2000;//允许超时2s | |||||
| int recv = 0; | |||||
| auto total = content->size; | |||||
| std::array<char, unitSize> buf; | |||||
| int timeoCnt = 0; | |||||
| task->setState(FileTaskState::Running); | |||||
| while (recv < total) | |||||
| { | |||||
| if (task->hasCancelPending()) | |||||
| { | |||||
| task->setState(FileTaskState::Canceled); | |||||
| fclose(of); | |||||
| return; | |||||
| } | |||||
| auto left = total - recv; | |||||
| auto request = unitSize > left ? left : unitSize; | |||||
| auto got = client->recv(buf.data(), request, timeo); | |||||
| if (got == -1 && ++timeoCnt >= maxTimeoCnt) | |||||
| { | |||||
| task->setState(FileTaskState::Error, "下载文件超时,好友可能掉线"); | |||||
| fclose(of); | |||||
| return; | |||||
| } | |||||
| else if (got < 0) | |||||
| { | |||||
| task->setState(FileTaskState::Error, "接收数据出错,可能网络错误"); | |||||
| fclose(of); | |||||
| return; | |||||
| } | |||||
| else | |||||
| { | |||||
| fwrite(buf.data(), 1, got, of); | |||||
| recv+=got; | |||||
| task->setProcess(recv); | |||||
| } | |||||
| } | |||||
| fclose(of); | |||||
| task->setState(FileTaskState::Finish); | |||||
| }; | |||||
| thread thd(func); | |||||
| thd.detach(); | |||||
| return task; | |||||
| } | |||||
| class GetPubKey : public SendProtocol | |||||
| { | |||||
| public: | |||||
| int cmdId() {return IPMSG_GETPUBKEY;} | |||||
| void write(ostream& os){ | |||||
| (void)os; | |||||
| } | |||||
| }; | |||||
| pair<bool, string> FeiqEngine::start() | |||||
| { | |||||
| if (mStarted) | |||||
| { | |||||
| return {true, "已经启动过"}; | |||||
| } | |||||
| mCommu.setMyHost(encOut->convert(mHost)); | |||||
| mCommu.setMyName(encOut->convert(mName)); | |||||
| auto result = mCommu.start(); | |||||
| if(result.first) | |||||
| { | |||||
| mAsyncWait.start(); | |||||
| mMsgThd.start(); | |||||
| mMsgThd.setHandler(std::bind(&FeiqEngine::dispatchMsg, this, placeholders::_1)); | |||||
| mStarted = true; | |||||
| sendImOnLine(); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| void FeiqEngine::stop() | |||||
| { | |||||
| if (mStarted) | |||||
| { | |||||
| mStarted=false; | |||||
| SendImOffLine imOffLine(mName); | |||||
| mCommu.send("255.255.255.255", imOffLine); | |||||
| broadcastToCurstomGroup(imOffLine); | |||||
| mCommu.stop(); | |||||
| mAsyncWait.stop(); | |||||
| mMsgThd.stop(); | |||||
| } | |||||
| } | |||||
| void FeiqEngine::addToBroadcast(const string &ip) | |||||
| { | |||||
| mBroadcast.push_back(ip); | |||||
| } | |||||
| void FeiqEngine::setMyHost(string host) | |||||
| { | |||||
| mHost=host; | |||||
| if (mName.empty()) | |||||
| mName = mHost; | |||||
| } | |||||
| void FeiqEngine::setMyName(string name){ | |||||
| mName=name; | |||||
| if (mName.empty()) | |||||
| mName = mHost; | |||||
| } | |||||
| void FeiqEngine::sendImOnLine(const string &ip) | |||||
| { | |||||
| SendImOnLine imOnLine(mName); | |||||
| if (ip.empty()) | |||||
| { | |||||
| mCommu.send("255.255.255.255", imOnLine); | |||||
| broadcastToCurstomGroup(imOnLine); | |||||
| } | |||||
| else | |||||
| { | |||||
| mCommu.send(ip, imOnLine); | |||||
| } | |||||
| } | |||||
| void FeiqEngine::enableIntervalDetect(int seconds) | |||||
| { | |||||
| thread thd([this, seconds](){ | |||||
| while(mStarted) | |||||
| { | |||||
| sleep(seconds); | |||||
| if (!mStarted) break; | |||||
| SendImOnLine imOnLine(mName); | |||||
| broadcastToCurstomGroup(imOnLine); | |||||
| } | |||||
| }); | |||||
| thd.detach(); | |||||
| } | |||||
| FeiqModel &FeiqEngine::getModel() | |||||
| { | |||||
| return mModel; | |||||
| } | |||||
| void FeiqEngine::onBrEntry(shared_ptr<Post> post) | |||||
| { | |||||
| AnsBrEntry ans(mName); | |||||
| mCommu.send(post->from->getIp(), ans); | |||||
| } | |||||
| void FeiqEngine::onMsg(shared_ptr<Post> post) | |||||
| { | |||||
| static vector<string> rejectedImages; | |||||
| auto event = make_shared<MessageViewEvent>(); | |||||
| event->when = post->when; | |||||
| event->fellow = post->from; | |||||
| auto it = post->contents.begin(); | |||||
| auto end = post->contents.end(); | |||||
| string reply; | |||||
| while (it != end)//过滤消息内容:删除不支持的包,并回复好友 | |||||
| { | |||||
| bool rejected = false; | |||||
| if ((*it)->type() == ContentType::File) | |||||
| { | |||||
| auto fc = static_pointer_cast<FileContent>(*it); | |||||
| if (fc->fileType == IPMSG_FILE_REGULAR)//TODO:与飞秋的文件夹传输协议还没支持 | |||||
| mModel.addDownloadTask(event->fellow, fc); | |||||
| else if (fc->fileType == IPMSG_FILE_DIR) | |||||
| { | |||||
| rejected=true; | |||||
| reply+="Mac飞秋还不支持接收目录:"+fc->filename+"\n"; | |||||
| } | |||||
| } | |||||
| else if ((*it)->type() == ContentType::Text) | |||||
| { | |||||
| auto tc = static_cast<TextContent*>((*it).get()); | |||||
| string begin = "/~#>"; | |||||
| string end = "<B~"; | |||||
| if (startsWith(tc->text, begin) && endsWith(tc->text, end)) | |||||
| { | |||||
| rejected=true; | |||||
| } | |||||
| } | |||||
| else if ((*it)->type() == ContentType::Image) | |||||
| { | |||||
| //这个包还没被拒绝过,发送拒绝消息 | |||||
| auto ic = static_cast<ImageContent*>((*it).get()); | |||||
| if (std::find(rejectedImages.begin(), rejectedImages.end(), ic->id)==rejectedImages.end()) | |||||
| { | |||||
| reply+="Mac飞秋还不支持接收图片,请用文件形式发送图片\n"; | |||||
| rejectedImages.push_back(ic->id); | |||||
| } | |||||
| rejected=true; | |||||
| } | |||||
| if (!rejected) | |||||
| { | |||||
| event->contents.push_back(*it); | |||||
| } | |||||
| ++it; | |||||
| } | |||||
| if (!reply.empty()) | |||||
| { | |||||
| SendTextContent send; | |||||
| TextContent content; | |||||
| content.text = reply; | |||||
| send.setContent(&content); | |||||
| mCommu.send(post->from->getIp(), send); | |||||
| } | |||||
| if (!event->contents.empty()) | |||||
| mMsgThd.sendMessage(event); | |||||
| } | |||||
| void FeiqEngine::onSendCheck(shared_ptr<Post> post) | |||||
| { | |||||
| SendSentCheck reply(post->packetNo); | |||||
| mCommu.send(post->from->getIp(), reply); | |||||
| } | |||||
| void FeiqEngine::onReadCheck(shared_ptr<Post> post) | |||||
| { | |||||
| SendReadCheck reply(post->packetNo); | |||||
| mCommu.send(post->from->getIp(), reply); | |||||
| } | |||||
| void FeiqEngine::onSendTimeo(IdType packetId, const string& ip, shared_ptr<Content> content) | |||||
| { | |||||
| auto event = make_shared<SendTimeoEvent>(); | |||||
| event->fellow = mModel.findFirstFellowOf(ip); | |||||
| if (event->fellow == nullptr) | |||||
| return; | |||||
| event->content = content; | |||||
| mMsgThd.sendMessage(event); | |||||
| } | |||||
| void FeiqEngine::onReadMessage(shared_ptr<Post> post) | |||||
| { | |||||
| if (post->contents.empty()) | |||||
| return; | |||||
| auto content = dynamic_pointer_cast<IdContent>(post->contents[0]); | |||||
| mAsyncWait.clearWaitPack(content->id); | |||||
| } | |||||
| void FeiqEngine::fileServerHandler(unique_ptr<TcpSocket> client, int packetNo, int fileId, int offset) | |||||
| { | |||||
| auto task = mModel.findTask(packetNo, fileId); | |||||
| if (task == nullptr) | |||||
| return; | |||||
| auto func = [task, offset](unique_ptr<TcpSocket> client){ | |||||
| FILE* is = fopen(task->getContent()->path.c_str(), "r"); | |||||
| if (is == nullptr) | |||||
| { | |||||
| task->setState(FileTaskState::Error, "无法读取文件"); | |||||
| } | |||||
| // Defer{ | |||||
| // [is](){ | |||||
| // fclose(is); | |||||
| // } | |||||
| // }; | |||||
| if (offset > 0) | |||||
| fseek(is, offset, SEEK_SET); | |||||
| const int unitSize = 2048;//一次发送2k | |||||
| std::array<char, unitSize> buf; | |||||
| auto total = task->getContent()->size; | |||||
| int sent = 0; | |||||
| task->setState(FileTaskState::Running); | |||||
| while (sent < total && !feof(is)) | |||||
| { | |||||
| auto left = total - sent; | |||||
| auto request = unitSize > left ? left : unitSize; | |||||
| int got = fread(buf.data(), 1, request, is); | |||||
| got = client->send(buf.data(), got); | |||||
| if (got < 0) | |||||
| { | |||||
| task->setState(FileTaskState::Error, "无法发送数据,可能是网络问题"); | |||||
| fclose(is); | |||||
| return; | |||||
| } | |||||
| sent+=got; | |||||
| task->setProcess(sent); | |||||
| } | |||||
| if (sent != total) | |||||
| { | |||||
| task->setState(FileTaskState::Error, "文件未完整发送,可能是发送期间文件被改动"); | |||||
| } | |||||
| else | |||||
| { | |||||
| task->setProcess(total); | |||||
| task->setState(FileTaskState::Finish); | |||||
| } | |||||
| fclose(is); | |||||
| }; | |||||
| thread thd(func, std::move(client)); | |||||
| thd.detach(); | |||||
| } | |||||
| shared_ptr<Fellow> FeiqEngine::addOrUpdateFellow(shared_ptr<Fellow> fellow) | |||||
| { | |||||
| auto f = mModel.getFullInfoOf(fellow); | |||||
| bool shouldApdate = false; | |||||
| if (f == nullptr) | |||||
| { | |||||
| mModel.addFellow(fellow); | |||||
| f = fellow; | |||||
| shouldApdate = true; | |||||
| } | |||||
| else | |||||
| { | |||||
| if (f->update(*fellow)) | |||||
| shouldApdate = true; | |||||
| } | |||||
| if (shouldApdate){ | |||||
| auto event = make_shared<FellowViewEvent>(); | |||||
| event->what = ViewEventType::FELLOW_UPDATE; | |||||
| event->fellow = f; | |||||
| event->when = Post::now(); | |||||
| mMsgThd.sendMessage(event); | |||||
| } | |||||
| return f; | |||||
| } | |||||
| void FeiqEngine::dispatchMsg(shared_ptr<ViewEvent> msg) | |||||
| { | |||||
| mView->onEvent(msg); | |||||
| } | |||||
| void FeiqEngine::broadcastToCurstomGroup(SendProtocol &protocol) | |||||
| { | |||||
| for (auto ip : mBroadcast) | |||||
| { | |||||
| if (!mStarted) | |||||
| break;//发送过程是一个耗时网络操作,如果已经stop,则中断 | |||||
| mCommu.send(ip, protocol); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,92 @@ | |||||
| #ifndef FEIQENGINE_H | |||||
| #define FEIQENGINE_H | |||||
| #include "content.h" | |||||
| #include "feiqcommu.h" | |||||
| #include <string> | |||||
| #include <tuple> | |||||
| #include <list> | |||||
| #include <unordered_map> | |||||
| #include "feiqmodel.h" | |||||
| #include "msgqueuethread.h" | |||||
| #include "ifeiqview.h" | |||||
| #include "asynwait.h" | |||||
| using namespace std; | |||||
| class Post; | |||||
| class ContentSender; | |||||
| /** | |||||
| * @brief The FeiqEngine class | |||||
| * feiq以mvc模式架构,FeiqEngine是control部分,负责逻辑控制(以及具体协议来往) | |||||
| */ | |||||
| class FeiqEngine | |||||
| { | |||||
| public: | |||||
| FeiqEngine(); | |||||
| public: | |||||
| pair<bool, string> send(shared_ptr<Fellow> fellow, shared_ptr<Content> content); | |||||
| pair<bool, string> sendFiles(shared_ptr<Fellow> fellow, list<shared_ptr<FileContent> > &files); | |||||
| bool downloadFile(FileTask* task); | |||||
| public: | |||||
| pair<bool, string> start(); | |||||
| void stop(); | |||||
| void addToBroadcast(const string& ip); | |||||
| void setMyHost(string host); | |||||
| void setMyName(string name); | |||||
| void setView(IFeiqView* view){mView = view;} | |||||
| void sendImOnLine(const string& ip = ""); | |||||
| /** | |||||
| * @brief enableIntervalDetect 当接入路由,被禁止发送广播包时, | |||||
| * 启用间隔检测可每隔一段时间发送一次上线通知到指定网段,以实现检测。 | |||||
| */ | |||||
| void enableIntervalDetect(int seconds); | |||||
| public: | |||||
| FeiqModel &getModel(); | |||||
| private://trigers | |||||
| void onAnsEntry(shared_ptr<Post> post); | |||||
| void onBrEntry(shared_ptr<Post> post); | |||||
| void onBrExit(shared_ptr<Post> post); | |||||
| void onMsg(shared_ptr<Post> post); | |||||
| void onSendCheck(shared_ptr<Post> post); | |||||
| void onReadCheck(shared_ptr<Post> post); | |||||
| void onSendTimeo(IdType packetId, const string &ip, shared_ptr<Content> content); | |||||
| void onReadMessage(shared_ptr<Post> post); | |||||
| private: | |||||
| void fileServerHandler(unique_ptr<TcpSocket> client, int packetNo, int fileId, int offset); | |||||
| private: | |||||
| shared_ptr<Fellow> addOrUpdateFellow(shared_ptr<Fellow> fellow); | |||||
| void dispatchMsg(shared_ptr<ViewEvent> msg); | |||||
| void broadcastToCurstomGroup(SendProtocol& protocol); | |||||
| private: | |||||
| FeiqCommu mCommu; | |||||
| vector<unique_ptr<RecvProtocol>> mRecvProtocols; | |||||
| FeiqModel mModel; | |||||
| string mHost; | |||||
| string mName; | |||||
| MsgQueueThread<ViewEvent> mMsgThd; | |||||
| IFeiqView* mView; | |||||
| vector<string> mBroadcast; | |||||
| bool mStarted=false; | |||||
| AsynWait mAsyncWait;//异步等待对方回包 | |||||
| struct EnumClassHash | |||||
| { | |||||
| template <typename T> | |||||
| std::size_t operator()(T t) const | |||||
| { | |||||
| return static_cast<std::size_t>(t); | |||||
| } | |||||
| }; | |||||
| //可以用unique_ptr,但是unique_ptr要求知道具体定义 | |||||
| unordered_map<ContentType, shared_ptr<ContentSender>, EnumClassHash> mContentSender; | |||||
| }; | |||||
| #endif // FEIQENGINE_H | |||||
| @@ -0,0 +1,119 @@ | |||||
| #include "feiqmodel.h" | |||||
| #include <functional> | |||||
| FeiqModel::FeiqModel() | |||||
| { | |||||
| } | |||||
| void FeiqModel::addFellow(shared_ptr<Fellow> fellow) | |||||
| { | |||||
| lock_guard<mutex> guard(mFellowLock); | |||||
| mFellows.push_back(fellow); | |||||
| } | |||||
| shared_ptr<Fellow> FeiqModel::getFullInfoOf(shared_ptr<Fellow> fellow) | |||||
| { | |||||
| lock_guard<mutex> guard(mFellowLock); | |||||
| auto predict = [&fellow](shared_ptr<Fellow> tmp){return fellow->isSame(*tmp);}; | |||||
| auto found = std::find_if(mFellows.begin(), mFellows.end(), predict); | |||||
| return found == mFellows.end() ? nullptr : *found; | |||||
| } | |||||
| shared_ptr<Fellow> FeiqModel::findFirstFellowOf(const string &ip) | |||||
| { | |||||
| lock_guard<mutex> guard(mFellowLock); | |||||
| auto predict = [&ip](shared_ptr<Fellow> tmp){return tmp->getIp() == ip;}; | |||||
| auto found = std::find_if(mFellows.begin(), mFellows.end(), predict); | |||||
| return found == mFellows.end() ? nullptr : *found; | |||||
| } | |||||
| list<shared_ptr<Fellow> > FeiqModel::searchFellow(const string &text) | |||||
| { | |||||
| lock_guard<mutex> guard(mFellowLock); | |||||
| list<shared_ptr<Fellow>> fellows; | |||||
| if (text.empty()) | |||||
| { | |||||
| fellows = mFellows; | |||||
| } | |||||
| else | |||||
| { | |||||
| for (shared_ptr<Fellow> fellow : mFellows) | |||||
| { | |||||
| if (fellow->getName().find(text) != string::npos | |||||
| || fellow->getHost().find(text) != string::npos | |||||
| || fellow->getIp().find(text) != string::npos) | |||||
| fellows.push_back(fellow); | |||||
| } | |||||
| } | |||||
| return fellows; | |||||
| } | |||||
| shared_ptr<Fellow> FeiqModel::getShared(const Fellow *fellow) | |||||
| { | |||||
| if (fellow == nullptr) | |||||
| return nullptr; | |||||
| lock_guard<mutex> guard(mFellowLock); | |||||
| for (shared_ptr<Fellow> f : mFellows) | |||||
| { | |||||
| if (f.get() == fellow) | |||||
| return f; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| shared_ptr<FileTask> FeiqModel::addDownloadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent) | |||||
| { | |||||
| lock_guard<mutex> guard(mFileTaskLock); | |||||
| auto task = make_shared<FileTask>(fileContent, FileTaskType::Download); | |||||
| task->setFellow(fellow); | |||||
| mFileTasks.push_back(task); | |||||
| return task; | |||||
| } | |||||
| shared_ptr<FileTask> FeiqModel::addUploadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent) | |||||
| { | |||||
| lock_guard<mutex> guard(mFileTaskLock); | |||||
| auto task = make_shared<FileTask>(fileContent, FileTaskType::Upload); | |||||
| task->setFellow(fellow); | |||||
| mFileTasks.push_back(task); | |||||
| return task; | |||||
| } | |||||
| void FeiqModel::removeFileTask(function<bool (const FileTask&)> predict) | |||||
| { | |||||
| lock_guard<mutex> g(mFileTaskLock); | |||||
| mFileTasks.remove_if([predict](shared_ptr<FileTask> t){ | |||||
| return predict(*t); | |||||
| }); | |||||
| } | |||||
| shared_ptr<FileTask> FeiqModel::findTask(IdType packetNo, IdType fileId, FileTaskType type) | |||||
| { | |||||
| lock_guard<mutex> g(mFileTaskLock); | |||||
| for (auto task : mFileTasks) { | |||||
| if (task->type() != type) | |||||
| continue; | |||||
| auto content = task->getContent(); | |||||
| if (content->fileId == fileId && content->packetNo == packetNo) | |||||
| return task; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| list<shared_ptr<FileTask> > FeiqModel::searchTask(function<bool (const FileTask &)> predict) | |||||
| { | |||||
| lock_guard<mutex> g(mFileTaskLock); | |||||
| list<shared_ptr<FileTask>> allTask; | |||||
| for (auto task : mFileTasks) | |||||
| if (predict(*(task.get()))) | |||||
| allTask.push_back(task); | |||||
| return allTask; | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| #ifndef FEIQMODEL_H | |||||
| #define FEIQMODEL_H | |||||
| #include "fellow.h" | |||||
| #include <memory> | |||||
| #include <list> | |||||
| #include <mutex> | |||||
| #include "filetask.h" | |||||
| #include "uniqueid.h" | |||||
| using namespace std; | |||||
| class FeiqModel | |||||
| { | |||||
| public: | |||||
| FeiqModel(); | |||||
| public: | |||||
| void addFellow(shared_ptr<Fellow> fellow); | |||||
| shared_ptr<Fellow> getFullInfoOf(shared_ptr<Fellow> fellow); | |||||
| shared_ptr<Fellow> findFirstFellowOf(const string& ip); | |||||
| list<shared_ptr<Fellow>> searchFellow(const string& text); | |||||
| shared_ptr<Fellow> getShared(const Fellow* fellow); | |||||
| public: | |||||
| shared_ptr<FileTask> addDownloadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent); | |||||
| shared_ptr<FileTask> addUploadTask(shared_ptr<Fellow> fellow, shared_ptr<FileContent> fileContent); | |||||
| shared_ptr<FileTask> findTask(IdType packetNo, IdType fileId, FileTaskType type = FileTaskType::Upload); | |||||
| list<shared_ptr<FileTask>> searchTask(function<bool(const FileTask&)> predict); | |||||
| void removeFileTask(function<bool (const FileTask&)> predict); | |||||
| private: | |||||
| list<shared_ptr<Fellow>> mFellows; | |||||
| list<shared_ptr<FileTask>> mFileTasks; | |||||
| mutex mFellowLock; | |||||
| mutex mFileTaskLock; | |||||
| }; | |||||
| #endif // FEIQMODEL_H | |||||
| @@ -0,0 +1,104 @@ | |||||
| #ifndef FELLOW_H | |||||
| #define FELLOW_H | |||||
| #include <string> | |||||
| #include <memory> | |||||
| #include <sstream> | |||||
| using namespace std; | |||||
| class Fellow | |||||
| { | |||||
| public: | |||||
| string getIp() const{return mIp;} | |||||
| string getName() const{return mName.empty() ? mPcName : mName;} | |||||
| string getHost() const{return mHost;} | |||||
| string getMac() const{return mMac;} | |||||
| bool isOnLine() const{return mOnLine;} | |||||
| string version() const{return mVersion;} | |||||
| void setIp(const string& value){ | |||||
| mIp = value; | |||||
| } | |||||
| void setName(const string& value){ | |||||
| mName = value; | |||||
| } | |||||
| void setHost(const string& value){ | |||||
| mHost = value; | |||||
| } | |||||
| void setMac(const string& value){ | |||||
| mMac = value; | |||||
| } | |||||
| void setOnLine(bool value){ | |||||
| mOnLine = value; | |||||
| } | |||||
| void setVersion(const string& value){ | |||||
| mVersion = value; | |||||
| } | |||||
| void setPcName(const string& value){ | |||||
| mPcName = value; | |||||
| } | |||||
| bool update(const Fellow& fellow) | |||||
| { | |||||
| bool changed = false; | |||||
| if (!fellow.mName.empty() && mName != fellow.mName){ | |||||
| mName = fellow.mName; | |||||
| changed=true; | |||||
| } | |||||
| if (!fellow.mMac.empty() && mMac != fellow.mMac){ | |||||
| mMac = fellow.mMac; | |||||
| changed=true; | |||||
| } | |||||
| if (mOnLine != fellow.mOnLine){ | |||||
| mOnLine = fellow.mOnLine; | |||||
| changed=true; | |||||
| } | |||||
| return changed; | |||||
| } | |||||
| bool operator == (const Fellow& fellow) | |||||
| { | |||||
| return isSame(fellow); | |||||
| } | |||||
| bool isSame(const Fellow& fellow) | |||||
| { | |||||
| return mIp == fellow.mIp || (!mMac.empty() && mMac == fellow.mMac); | |||||
| } | |||||
| string toString() const | |||||
| { | |||||
| ostringstream os; | |||||
| os<<"[" | |||||
| <<"ip="<<mIp | |||||
| <<",name="<<mName | |||||
| <<",host="<<mHost | |||||
| <<",pcname="<<mPcName | |||||
| <<",mac="<<mMac | |||||
| <<",online="<<mOnLine | |||||
| <<",version="<<mVersion | |||||
| <<"]"; | |||||
| return os.str(); | |||||
| } | |||||
| private: | |||||
| string mIp; | |||||
| string mPcName; | |||||
| string mName; | |||||
| string mHost; | |||||
| string mMac; | |||||
| bool mOnLine; | |||||
| string mVersion; | |||||
| }; | |||||
| #endif // FELLOW_H | |||||
| @@ -0,0 +1,82 @@ | |||||
| #include "filetask.h" | |||||
| FileTask::FileTask() | |||||
| { | |||||
| } | |||||
| FileTask::FileTask(shared_ptr<FileContent> fileContent, FileTaskType type) | |||||
| :mContent(fileContent), mType(type) | |||||
| { | |||||
| mNotifySize = fileContent->size/100;//每1%通知一次 | |||||
| const int minNotifySize = 102400;//至少变化了100k才通知 | |||||
| if (mNotifySize < minNotifySize) | |||||
| mNotifySize = minNotifySize; | |||||
| } | |||||
| void FileTask::setObserver(IFileTaskObserver *observer) | |||||
| { | |||||
| mObserver = observer; | |||||
| } | |||||
| void FileTask::setProcess(int val) | |||||
| { | |||||
| mProcess = val; | |||||
| if (mProcess - mLastProcess >= mNotifySize) | |||||
| { | |||||
| mLastProcess = mProcess; | |||||
| mObserver->onProgress(this); | |||||
| } | |||||
| } | |||||
| void FileTask::setState(FileTaskState val, const string &msg) | |||||
| { | |||||
| mState = val; | |||||
| mMsg = msg; | |||||
| mObserver->onStateChanged(this); | |||||
| } | |||||
| void FileTask::setFellow(shared_ptr<Fellow> fellow) | |||||
| { | |||||
| mFellow = fellow; | |||||
| } | |||||
| void FileTask::cancel() | |||||
| { | |||||
| mCancelPending=true; | |||||
| } | |||||
| bool FileTask::hasCancelPending() | |||||
| { | |||||
| return mCancelPending; | |||||
| } | |||||
| shared_ptr<Fellow> FileTask::fellow() const | |||||
| { | |||||
| return mFellow; | |||||
| } | |||||
| int FileTask::getProcess() const | |||||
| { | |||||
| return mProcess; | |||||
| } | |||||
| FileTaskState FileTask::getState() const | |||||
| { | |||||
| return mState; | |||||
| } | |||||
| string FileTask::getDetailInfo() const | |||||
| { | |||||
| return mMsg; | |||||
| } | |||||
| shared_ptr<FileContent> FileTask::getContent() const | |||||
| { | |||||
| return mContent; | |||||
| } | |||||
| FileTaskType FileTask::type() const | |||||
| { | |||||
| return mType; | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| #ifndef FILETASK_H | |||||
| #define FILETASK_H | |||||
| #include "content.h" | |||||
| #include <memory> | |||||
| #include <functional> | |||||
| #include "fellow.h" | |||||
| using namespace std; | |||||
| enum class FileTaskType{ | |||||
| Download, | |||||
| Upload | |||||
| }; | |||||
| enum class FileTaskState{ | |||||
| NotStart, | |||||
| Running, | |||||
| Finish, | |||||
| Error, | |||||
| Canceled | |||||
| }; | |||||
| class FileTask; | |||||
| class IFileTaskObserver | |||||
| { | |||||
| public: | |||||
| virtual void onStateChanged(FileTask* fileTask) = 0; | |||||
| virtual void onProgress(FileTask* fileTask) = 0; | |||||
| }; | |||||
| class FileTask | |||||
| { | |||||
| public: | |||||
| FileTask(); | |||||
| FileTask(shared_ptr<FileContent> fileContent, FileTaskType type); | |||||
| void setObserver(IFileTaskObserver* observer); | |||||
| public: | |||||
| void setProcess(int val); | |||||
| void setState(FileTaskState val, const string& msg=""); | |||||
| void setFellow(shared_ptr<Fellow> fellow); | |||||
| void cancel(); | |||||
| bool hasCancelPending(); | |||||
| public: | |||||
| shared_ptr<Fellow> fellow() const; | |||||
| int getProcess() const; | |||||
| FileTaskState getState() const; | |||||
| string getDetailInfo() const; | |||||
| shared_ptr<FileContent> getContent() const; | |||||
| FileTaskType type() const; | |||||
| private: | |||||
| shared_ptr<Fellow> mFellow;//要发送给的用户,或文件来自该用户 | |||||
| int mProcess=0; | |||||
| FileTaskState mState = FileTaskState::NotStart; | |||||
| shared_ptr<FileContent> mContent; | |||||
| IFileTaskObserver* mObserver; | |||||
| FileTaskType mType = FileTaskType::Upload; | |||||
| string mMsg; | |||||
| bool mCancelPending=false; | |||||
| int mNotifySize; | |||||
| int mLastProcess=0; | |||||
| }; | |||||
| #endif // FILETASK_H | |||||
| @@ -0,0 +1,199 @@ | |||||
| #include "history.h" | |||||
| #include <iostream> | |||||
| #include "defer.h" | |||||
| #include <unistd.h> | |||||
| #include <string.h> | |||||
| #include <errno.h> | |||||
| #include "defer.h" | |||||
| #define FELLOW_TABLE "fellow" | |||||
| #define MESSAGE_TABLE "message" | |||||
| #define CHECK_SQLITE_RET(ret, action, result)\ | |||||
| if (ret != SQLITE_OK)\ | |||||
| {\ | |||||
| cout<<"failed to"#action":"<<ret<<endl;\ | |||||
| return result;\ | |||||
| } | |||||
| #define CHECK_SQLITE_RET2(ret, action)\ | |||||
| if (ret != SQLITE_OK)\ | |||||
| {\ | |||||
| cout<<"failed to"#action":"<<ret<<endl;\ | |||||
| return;\ | |||||
| } | |||||
| History::History() | |||||
| { | |||||
| } | |||||
| bool History::init(const string &dbPath) | |||||
| { | |||||
| unInit(); | |||||
| //检查数据库文件是否存在,若不存在,需要创建表 | |||||
| bool needCreateTable=false; | |||||
| auto ret = access(dbPath.c_str(), F_OK); | |||||
| if (ret == -1) | |||||
| { | |||||
| cout<<"db may be new created:"<<strerror(errno)<<endl; | |||||
| needCreateTable=true; | |||||
| } | |||||
| //打开数据库 | |||||
| ret = sqlite3_open(dbPath.c_str(), &mDb); | |||||
| bool success = false; | |||||
| Defer closeDbIfErr{ | |||||
| [this, success]() | |||||
| { | |||||
| if (!success) | |||||
| { | |||||
| cerr<<"init failed, close db now"<<endl; | |||||
| sqlite3_close(mDb);//除非内存不够,否则open总是会分配mDb的内存,总是需要close | |||||
| mDb = nullptr; | |||||
| } | |||||
| } | |||||
| }; | |||||
| CHECK_SQLITE_RET(ret, "open sqlite", false); | |||||
| //创建表 | |||||
| if (needCreateTable) | |||||
| { | |||||
| string createFellowTable = "create table " FELLOW_TABLE "(id integer,ip text, name text, mac text, primary key(id));"; | |||||
| string createMessageTable = "create table " MESSAGE_TABLE " (id integer, fellow integer, when integer, content blob, primary key(id))"; | |||||
| ret = sqlite3_exec(mDb, createFellowTable.c_str(), nullptr, nullptr, nullptr); | |||||
| CHECK_SQLITE_RET(ret, "create fellow table", false); | |||||
| ret = sqlite3_exec(mDb, createMessageTable.c_str(), nullptr, nullptr, nullptr); | |||||
| CHECK_SQLITE_RET(ret, "create message table", false); | |||||
| } | |||||
| success=true; | |||||
| return true; | |||||
| } | |||||
| void History::unInit() | |||||
| { | |||||
| if (mDb != nullptr) | |||||
| { | |||||
| sqlite3_close(mDb); | |||||
| mDb = nullptr; | |||||
| } | |||||
| } | |||||
| void History::add(const HistoryRecord& record) | |||||
| { | |||||
| int ret; | |||||
| //先更新好友信息 | |||||
| auto fellowId = findFellowId(record.who->getIp()); | |||||
| if (fellowId < 0) | |||||
| { | |||||
| string sql = "insert into " FELLOW_TABLE " values(null," | |||||
| +record.who->getIp() | |||||
| +","+record.who->getName() | |||||
| +","+record.who->getMac() | |||||
| +");"; | |||||
| ret = sqlite3_exec(mDb, sql.c_str(), nullptr, nullptr, nullptr); | |||||
| CHECK_SQLITE_RET2(ret, "insert fellow to db"); | |||||
| fellowId = findFellowId(record.who->getIp()); | |||||
| } | |||||
| //再增加记录 | |||||
| Parcel parcel; | |||||
| record.what->writeTo(parcel); | |||||
| string sql = "insert into " MESSAGE_TABLE "values(null," | |||||
| +to_string(fellowId) | |||||
| +","+ to_string(record.when.time_since_epoch().count()) | |||||
| +",?);"; | |||||
| sqlite3_stmt* stmt = nullptr; | |||||
| ret = sqlite3_prepare_v2(mDb, sql.c_str(), sql.length(), &stmt, nullptr); | |||||
| CHECK_SQLITE_RET2(ret, "prepare to insert message"); | |||||
| Defer finalizeStmt{ | |||||
| [stmt](){ | |||||
| sqlite3_finalize(stmt); | |||||
| } | |||||
| }; | |||||
| auto buf = parcel.raw(); | |||||
| ret = sqlite3_bind_blob(stmt, 1, buf.data(), buf.size(), nullptr); | |||||
| CHECK_SQLITE_RET2(ret, "bind content to blob"); | |||||
| ret = sqlite3_step(stmt); | |||||
| CHECK_SQLITE_RET2(ret, "insert message"); | |||||
| } | |||||
| vector<HistoryRecord> History::query(const string& selection, const vector<string>& args) | |||||
| { | |||||
| vector<HistoryRecord> result; | |||||
| sqlite3_stmt* stmt; | |||||
| string sql = "select * from " MESSAGE_TABLE " where "+selection; | |||||
| auto ret = sqlite3_prepare_v2(mDb, sql.c_str(), sql.length(), &stmt, nullptr); | |||||
| CHECK_SQLITE_RET(ret, "prepare to query", result); | |||||
| Defer finalizeStmt{ | |||||
| [stmt](){ | |||||
| sqlite3_finalize(stmt); | |||||
| } | |||||
| }; | |||||
| int len = args.size(); | |||||
| for (auto i = 0; i < len; i++) | |||||
| { | |||||
| ret = sqlite3_bind_blob(stmt, i+1, args[i].c_str(), args[i].length(), nullptr); | |||||
| CHECK_SQLITE_RET(ret, "bind args", result); | |||||
| } | |||||
| while(true) | |||||
| { | |||||
| ret = sqlite3_step(stmt); | |||||
| if (ret == SQLITE_DONE) | |||||
| { | |||||
| return result; | |||||
| } | |||||
| else if (ret != SQLITE_ROW) | |||||
| { | |||||
| cerr<<"error occur while step query:"<<ret<<endl; | |||||
| return result; | |||||
| } | |||||
| else | |||||
| { | |||||
| auto fellowId = sqlite3_column_int(stmt, 1); | |||||
| auto when = sqlite3_column_int(stmt, 2); | |||||
| auto contentData = sqlite3_column_blob(stmt, 3); | |||||
| auto contentLen = sqlite3_column_bytes(stmt, 3); | |||||
| HistoryRecord record; | |||||
| auto fellow = getFellow(fellowId); | |||||
| record.who = shared_ptr<Fellow>(std::move(fellow)); | |||||
| record.when = time_point<steady_clock, milliseconds>(milliseconds(when)); | |||||
| Parcel parcel; | |||||
| parcel.fillWith(contentData, contentLen); | |||||
| parcel.resetForRead(); | |||||
| record.what = ContentParcelFactory::createFromParcel(parcel); | |||||
| result.push_back(record); | |||||
| } | |||||
| } | |||||
| return result; | |||||
| } | |||||
| unique_ptr<Fellow> History::getFellow(int id) | |||||
| { | |||||
| } | |||||
| int History::findFellowId(const string &ip) | |||||
| { | |||||
| } | |||||
| @@ -0,0 +1,50 @@ | |||||
| #ifndef HISTORY_H | |||||
| #define HISTORY_H | |||||
| #include "fellow.h" | |||||
| #include <memory> | |||||
| #include <vector> | |||||
| #include "content.h" | |||||
| #include "post.h" | |||||
| #include <sqlite3.h> | |||||
| #include <unordered_map> | |||||
| using namespace std; | |||||
| struct HistoryRecord{ | |||||
| time_point<steady_clock, milliseconds> when; | |||||
| shared_ptr<Fellow> who; | |||||
| shared_ptr<Content> what; | |||||
| }; | |||||
| //日志记录 | |||||
| //完成代码 | |||||
| //调试代码 | |||||
| //加入model,合并model功能 | |||||
| //加入engine自动记录 | |||||
| //按好友、日期查询最近记录 | |||||
| //更新文件path | |||||
| /** | |||||
| * @brief The History class 以Content为单位,记录和查询聊天记录 | |||||
| * 还只是个半成品~ | |||||
| */ | |||||
| class History | |||||
| { | |||||
| public: | |||||
| History(); | |||||
| public: | |||||
| bool init(const string& dbPath); | |||||
| void unInit(); | |||||
| public: | |||||
| void add(const HistoryRecord &record); | |||||
| vector<HistoryRecord> query(const string& selection, const vector<string> &args); | |||||
| private: | |||||
| unique_ptr<Fellow> getFellow(int id); | |||||
| int findFellowId(const string& ip); | |||||
| private: | |||||
| sqlite3* mDb = nullptr; | |||||
| }; | |||||
| #endif // HISTORY_H | |||||
| @@ -0,0 +1,66 @@ | |||||
| #ifndef IFEIQVIEW_H | |||||
| #define IFEIQVIEW_H | |||||
| #include <memory> | |||||
| #include <vector> | |||||
| #include <chrono> | |||||
| using namespace std; | |||||
| using namespace std::chrono; | |||||
| #include "fellow.h" | |||||
| #include "content.h" | |||||
| #include "filetask.h" | |||||
| #include "post.h" | |||||
| enum class ViewEventType { | |||||
| FELLOW_UPDATE, | |||||
| MESSAGE, | |||||
| SEND_TIMEO, | |||||
| }; | |||||
| class ViewEvent | |||||
| { | |||||
| public: | |||||
| virtual ~ViewEvent(){} | |||||
| public: | |||||
| decltype(Post::now()) when = Post::now(); | |||||
| ViewEventType what; | |||||
| }; | |||||
| class FellowViewEvent : public ViewEvent | |||||
| { | |||||
| public: | |||||
| FellowViewEvent(){ | |||||
| what = ViewEventType::FELLOW_UPDATE; | |||||
| } | |||||
| shared_ptr<Fellow> fellow; | |||||
| }; | |||||
| class SendTimeoEvent : public FellowViewEvent | |||||
| { | |||||
| public: | |||||
| SendTimeoEvent(){ | |||||
| what = ViewEventType::SEND_TIMEO; | |||||
| } | |||||
| shared_ptr<Content> content; | |||||
| }; | |||||
| class MessageViewEvent : public FellowViewEvent | |||||
| { | |||||
| public: | |||||
| MessageViewEvent(){ | |||||
| what = ViewEventType::MESSAGE; | |||||
| } | |||||
| vector<shared_ptr<Content>> contents; | |||||
| }; | |||||
| class IFeiqView : public IFileTaskObserver | |||||
| { | |||||
| public: | |||||
| virtual ~IFeiqView(){} | |||||
| virtual void onEvent(shared_ptr<ViewEvent> event) = 0; | |||||
| }; | |||||
| #endif // IFEIQVIEW_H | |||||
| @@ -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 | |||||
| @@ -0,0 +1,29 @@ | |||||
| #include "msgqueuethread.h" | |||||
| #include <thread> | |||||
| MsgQueueThread::MsgQueueThread() | |||||
| { | |||||
| } | |||||
| void MsgQueueThread::setHandler(MsgQueueThread::Handler handler) | |||||
| { | |||||
| } | |||||
| void MsgQueueThread::start() | |||||
| { | |||||
| if (mRun) | |||||
| return; | |||||
| mRun=true; | |||||
| thread thd(&MsgQueueThread::loop, this); | |||||
| thd.detach(); | |||||
| } | |||||
| void MsgQueueThread::stop() | |||||
| { | |||||
| mRun=false; | |||||
| unique_lock<mutex> lock(mQueueMutex); | |||||
| mQueueCnd.notify_all(); | |||||
| } | |||||
| @@ -0,0 +1,82 @@ | |||||
| #ifndef MSGQUEUETHREAD_H | |||||
| #define MSGQUEUETHREAD_H | |||||
| #include <mutex> | |||||
| #include <queue> | |||||
| #include <functional> | |||||
| #include <condition_variable> | |||||
| #include <thread> | |||||
| using namespace std; | |||||
| //TODO:实现移到cpp中 | |||||
| template<class Msg> | |||||
| class MsgQueueThread | |||||
| { | |||||
| typedef function<void (shared_ptr<Msg>)> Handler; | |||||
| public: | |||||
| void setHandler(Handler handler) | |||||
| { | |||||
| mHandler = handler; | |||||
| } | |||||
| void start() | |||||
| { | |||||
| if (mRun) | |||||
| return; | |||||
| mRun=true; | |||||
| thread thd(&MsgQueueThread::loop, this); | |||||
| mThread.swap(thd); | |||||
| } | |||||
| void stop() | |||||
| { | |||||
| if (!mRun) | |||||
| return; | |||||
| mRun=false; | |||||
| unique_lock<mutex> lock(mQueueMutex); | |||||
| while(!mQueue.empty()) | |||||
| mQueue.pop(); | |||||
| mQueueCnd.notify_all(); | |||||
| lock.unlock(); | |||||
| mThread.join(); | |||||
| } | |||||
| void sendMessage(shared_ptr<Msg> msg) | |||||
| { | |||||
| unique_lock<mutex> lock(mQueueMutex); | |||||
| mQueue.push(msg); | |||||
| mQueueCnd.notify_one(); | |||||
| } | |||||
| private: | |||||
| void loop() | |||||
| { | |||||
| while (mRun) { | |||||
| unique_lock<mutex> lock(mQueueMutex); | |||||
| if (mQueue.empty()) | |||||
| mQueueCnd.wait(lock); | |||||
| if (!mRun) | |||||
| return; | |||||
| auto msg = mQueue.front(); | |||||
| mQueue.pop(); | |||||
| lock.unlock();//队列已经没有利用价值了,放了它 | |||||
| if (mHandler) | |||||
| mHandler(msg); | |||||
| } | |||||
| } | |||||
| private: | |||||
| condition_variable mQueueCnd; | |||||
| mutex mQueueMutex; | |||||
| queue<shared_ptr<Msg>> mQueue; | |||||
| bool mRun=false; | |||||
| Handler mHandler; | |||||
| thread mThread; | |||||
| }; | |||||
| #endif // MSGQUEUETHREAD_H | |||||
| @@ -0,0 +1,145 @@ | |||||
| #ifndef PARCELABLE_H | |||||
| #define PARCELABLE_H | |||||
| #include <iostream> | |||||
| #include <sstream> | |||||
| #include <vector> | |||||
| using namespace std; | |||||
| class Parcel | |||||
| { | |||||
| private: | |||||
| class Head | |||||
| { | |||||
| public: | |||||
| Head(int size) | |||||
| { | |||||
| this->size = size; | |||||
| } | |||||
| Head(istream& ss) | |||||
| { | |||||
| read(ss); | |||||
| } | |||||
| int size; | |||||
| void myWriteInt(ostream& os, int val) | |||||
| { | |||||
| char buf[9] = {0}; | |||||
| snprintf(buf, sizeof(buf), "%08d", val); | |||||
| os<<buf; | |||||
| } | |||||
| int myReadInt(istream& is) | |||||
| { | |||||
| char buf[9] = {0}; | |||||
| is.read(buf, sizeof(buf)-1); | |||||
| return stoi(buf); | |||||
| } | |||||
| void write(ostream& os){ | |||||
| myWriteInt(os, size); | |||||
| } | |||||
| void read(istream& is){ | |||||
| size = myReadInt(is); | |||||
| } | |||||
| }; | |||||
| public: | |||||
| template<typename T> | |||||
| void write(const T& val){ | |||||
| writePtr(&val, 1); | |||||
| } | |||||
| template<typename T> | |||||
| void read(T& val){ | |||||
| readPtr(&val); | |||||
| } | |||||
| template<typename T> | |||||
| void writePtr(const T* ptr, int n){ | |||||
| Head head{(int)(n * sizeof(T))}; | |||||
| head.write(ss); | |||||
| ss.write((const char*)ptr, head.size); | |||||
| } | |||||
| template<typename T> | |||||
| void readPtr(T* ptr){ | |||||
| Head head(ss); | |||||
| unique_ptr<char[]> buf(new char[head.size]); | |||||
| ss.read(buf.get(), head.size); | |||||
| memcpy(ptr, buf.get(), head.size); | |||||
| } | |||||
| void writeString(const string& val) | |||||
| { | |||||
| writePtr(val.c_str(), val.length()); | |||||
| } | |||||
| void readString(string& val) | |||||
| { | |||||
| auto size = nextSize(); | |||||
| unique_ptr<char[]> buf(new char[size+1]); | |||||
| readPtr(buf.get()); | |||||
| buf[size]=0; | |||||
| val=buf.get(); | |||||
| } | |||||
| void resetForRead() | |||||
| { | |||||
| ss.seekg(0, ss.beg); | |||||
| } | |||||
| void fillWith(const void* data, int len) | |||||
| { | |||||
| ss.clear(); | |||||
| ss.write(static_cast<const char*>(data), len); | |||||
| } | |||||
| public: | |||||
| streampos mark() | |||||
| { | |||||
| return ss.tellg(); | |||||
| } | |||||
| void unmark(streampos markPos) | |||||
| { | |||||
| ss.seekg(markPos); | |||||
| } | |||||
| public: | |||||
| vector<char> raw() | |||||
| { | |||||
| auto size = ss.tellp(); | |||||
| resetForRead(); | |||||
| vector<char> buf(size); | |||||
| ss.read(buf.data(), size); | |||||
| return buf; | |||||
| } | |||||
| private: | |||||
| int nextSize() | |||||
| { | |||||
| auto pos = mark(); | |||||
| Head head(ss); | |||||
| unmark(pos); | |||||
| return head.size; | |||||
| } | |||||
| private: | |||||
| stringstream ss; | |||||
| }; | |||||
| class Parcelable | |||||
| { | |||||
| public: | |||||
| virtual void writeTo(Parcel& out) const =0; | |||||
| virtual void readFrom(Parcel& in) =0; | |||||
| }; | |||||
| #endif // PARCELABLE_H | |||||
| @@ -0,0 +1,47 @@ | |||||
| #ifndef POST_H | |||||
| #define POST_H | |||||
| #include <memory> | |||||
| #include <vector> | |||||
| #include <list> | |||||
| #include <string> | |||||
| #include "content.h" | |||||
| #include <chrono> | |||||
| using namespace std; | |||||
| using namespace std::chrono; | |||||
| class Fellow; | |||||
| /** | |||||
| * @brief The Post struct | |||||
| * 定义一次好友发来的请求/数据包 | |||||
| */ | |||||
| class Post | |||||
| { | |||||
| public: | |||||
| static time_point<system_clock, milliseconds> now(){ | |||||
| return time_point_cast<milliseconds>(system_clock::now()); | |||||
| } | |||||
| decltype(now()) when = Post::now(); | |||||
| vector<char> extra; | |||||
| string packetNo; | |||||
| IdType cmdId; | |||||
| shared_ptr<Fellow> from; | |||||
| vector<shared_ptr<Content>> contents; | |||||
| Post() | |||||
| { | |||||
| from = make_shared<Fellow>(); | |||||
| } | |||||
| void addContent(shared_ptr<Content> content) | |||||
| { | |||||
| content->setPacketNo(packetNo); | |||||
| contents.push_back(content); | |||||
| } | |||||
| }; | |||||
| #endif // POST_H | |||||
| @@ -0,0 +1,27 @@ | |||||
| #ifndef PROTOCOL_H | |||||
| #define PROTOCOL_H | |||||
| #include <ostream> | |||||
| using namespace std; | |||||
| class Post; | |||||
| class SendProtocol | |||||
| { | |||||
| public: | |||||
| virtual int cmdId() = 0; | |||||
| virtual void write(ostream& os) = 0; | |||||
| }; | |||||
| class RecvProtocol | |||||
| { | |||||
| public: | |||||
| /** | |||||
| * @brief read 解析收到的数据 | |||||
| * @param post 当前好友请求,整个解析链中被不断读写完善,在解析链最后得到一个完整的Post | |||||
| * @return true 已完成解析,无需后续的解析链,false 继续解析 | |||||
| */ | |||||
| virtual bool read(shared_ptr<Post> post) = 0; | |||||
| }; | |||||
| #endif // PROTOCOL_H | |||||
| @@ -0,0 +1,84 @@ | |||||
| #include "tcpserver.h" | |||||
| #include <sys/socket.h> | |||||
| #include <stdio.h> | |||||
| #include <string.h> | |||||
| #include <arpa/inet.h> | |||||
| #include <thread> | |||||
| #include <unistd.h> | |||||
| using namespace std; | |||||
| TcpServer::TcpServer() | |||||
| { | |||||
| } | |||||
| bool TcpServer::start(int port) | |||||
| { | |||||
| if (mStarted) | |||||
| return true; | |||||
| mSocket = socket(AF_INET, SOCK_STREAM, 0); | |||||
| if (mSocket == -1) | |||||
| { | |||||
| perror("create tcp socket failed: %s"); | |||||
| return false; | |||||
| } | |||||
| int val = 1; | |||||
| setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); | |||||
| sockaddr_in addr; | |||||
| addr.sin_family = AF_INET; | |||||
| addr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| addr.sin_port = htons(port); | |||||
| int ret = ::bind(mSocket, (sockaddr*)&addr, sizeof(addr)); | |||||
| if (ret == -1) | |||||
| { | |||||
| perror("bind failed"); | |||||
| return false; | |||||
| } | |||||
| ret = listen(mSocket, 5); | |||||
| if (ret == -1) | |||||
| { | |||||
| perror("listen failed"); | |||||
| return false; | |||||
| } | |||||
| mStarted=true; | |||||
| thread thd(&TcpServer::keepAccept, this); | |||||
| thd.detach(); | |||||
| return true; | |||||
| } | |||||
| void TcpServer::whenNewClient(TcpServer::ClientHandler onClientConnected) | |||||
| { | |||||
| mClientHandler = onClientConnected; | |||||
| } | |||||
| void TcpServer::stop() | |||||
| { | |||||
| mStarted = false; | |||||
| close(mSocket); | |||||
| } | |||||
| void TcpServer::keepAccept() | |||||
| { | |||||
| while (mStarted) { | |||||
| sockaddr_in addr; | |||||
| socklen_t len = sizeof(addr); | |||||
| int ret = ::accept(mSocket, (sockaddr*)&addr, &len); | |||||
| if (ret < 0) | |||||
| { | |||||
| perror("failed to accept"); | |||||
| break; | |||||
| } | |||||
| if (mClientHandler) | |||||
| { | |||||
| mClientHandler(ret); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| #ifndef TCPSERVER_H | |||||
| #define TCPSERVER_H | |||||
| #include <functional> | |||||
| class TcpServer | |||||
| { | |||||
| public: | |||||
| TcpServer(); | |||||
| typedef std::function<void (int socket)> ClientHandler; | |||||
| public: | |||||
| bool start(int port); | |||||
| void whenNewClient(ClientHandler onClientConnected); | |||||
| void stop(); | |||||
| private: | |||||
| void keepAccept(); | |||||
| private: | |||||
| ClientHandler mClientHandler; | |||||
| bool mStarted=false; | |||||
| int mSocket; | |||||
| }; | |||||
| #endif // TCPSERVER_H | |||||
| @@ -0,0 +1,113 @@ | |||||
| #include "tcpsocket.h" | |||||
| #include <sys/socket.h> | |||||
| #include <arpa/inet.h> | |||||
| #include <stdio.h> | |||||
| #include <sys/time.h> | |||||
| #include <errno.h> | |||||
| #include <unistd.h> | |||||
| TcpSocket::TcpSocket() | |||||
| { | |||||
| } | |||||
| TcpSocket::TcpSocket(int socket) | |||||
| { | |||||
| mSocket = socket; | |||||
| if (mSocket != -1) | |||||
| { | |||||
| sockaddr_in addr; | |||||
| socklen_t len = sizeof(addr); | |||||
| auto ret = getpeername(mSocket, (sockaddr*)&addr, &len); | |||||
| if (ret == 0) | |||||
| { | |||||
| mPeerIp = inet_ntoa(addr.sin_addr); | |||||
| } | |||||
| } | |||||
| } | |||||
| TcpSocket::~TcpSocket() | |||||
| { | |||||
| disconnect(); | |||||
| } | |||||
| bool TcpSocket::connect(const string &ip, int port) | |||||
| { | |||||
| if (mSocket != -1) | |||||
| return true; | |||||
| mSocket = socket(AF_INET, SOCK_STREAM, 0); | |||||
| if (mSocket == -1) | |||||
| { | |||||
| perror("faield to create socket"); | |||||
| return false; | |||||
| } | |||||
| sockaddr_in addr; | |||||
| addr.sin_addr.s_addr = inet_addr(ip.c_str()); | |||||
| addr.sin_family = AF_INET; | |||||
| addr.sin_port = htons(port); | |||||
| int ret = ::connect(mSocket, (sockaddr*)&addr, sizeof(addr)); | |||||
| if (ret == -1) | |||||
| { | |||||
| perror("failed to connect"); | |||||
| close(mSocket); | |||||
| return false; | |||||
| } | |||||
| mPeerIp = ip; | |||||
| return true; | |||||
| } | |||||
| void TcpSocket::disconnect() | |||||
| { | |||||
| if (mSocket != -1) | |||||
| { | |||||
| close(mSocket); | |||||
| mPeerIp=""; | |||||
| } | |||||
| } | |||||
| int TcpSocket::send(const void *data, int size) | |||||
| { | |||||
| int sent = 0; | |||||
| auto pdata = static_cast<const char*>(data); | |||||
| while (sent < size) | |||||
| { | |||||
| int ret = ::send(mSocket, pdata+sent, size-sent, 0); | |||||
| if (ret == -1 ) | |||||
| { | |||||
| if (errno != EAGAIN) | |||||
| { | |||||
| perror("tcp send failed"); | |||||
| return -1; | |||||
| } | |||||
| continue; | |||||
| } | |||||
| sent+=ret; | |||||
| } | |||||
| return sent; | |||||
| } | |||||
| int TcpSocket::recv(void *data, int size, int msTimeout) | |||||
| { | |||||
| timeval tv = {msTimeout/1000, (msTimeout%1000)*1000}; | |||||
| setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); | |||||
| int ret = ::recv(mSocket, data, size, 0); | |||||
| if (ret == -1) | |||||
| { | |||||
| if (errno == ETIMEDOUT || errno == EAGAIN) | |||||
| return -1; | |||||
| else | |||||
| { | |||||
| perror("recv failed"); | |||||
| return -2; | |||||
| } | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| #ifndef TCPSOCKET_H | |||||
| #define TCPSOCKET_H | |||||
| #include <string> | |||||
| using namespace std; | |||||
| class TcpSocket | |||||
| { | |||||
| public: | |||||
| TcpSocket(); | |||||
| TcpSocket(int socket); | |||||
| ~TcpSocket(); | |||||
| public: | |||||
| bool connect(const string& ip, int port); | |||||
| void disconnect(); | |||||
| public: | |||||
| int send(const void* data, int size); | |||||
| /** | |||||
| * @brief recv 阻塞等待数据 | |||||
| * @param data 接收缓冲区 | |||||
| * @param size 最大接收字节数 | |||||
| * @param msTimeout 接收超时(毫秒) | |||||
| * @return 接收到的字节数;超时返回-1,其他错误返回-2 | |||||
| */ | |||||
| int recv(void* data, int size, int msTimeout = 1000); | |||||
| private: | |||||
| int mSocket=-1; | |||||
| string mPeerIp; | |||||
| }; | |||||
| #endif // TCPSOCKET_H | |||||
| @@ -0,0 +1,189 @@ | |||||
| #include "udpcommu.h" | |||||
| #include <string.h> | |||||
| #include <errno.h> | |||||
| #include <sys/socket.h> | |||||
| #include <sys/types.h> | |||||
| #include <arpa/inet.h> | |||||
| #include <stdio.h> | |||||
| #include <unistd.h> | |||||
| #include <thread> | |||||
| #include <net/if.h> | |||||
| #include <sys/ioctl.h> | |||||
| #include <netinet/in.h> | |||||
| #include <net/if_dl.h> | |||||
| #include <sys/sysctl.h> | |||||
| #include <array> | |||||
| #define setFailedMsgAndReturnFalse(msg) \ | |||||
| {mErrMsg = msg;\ | |||||
| return false;} | |||||
| #define setErrnoMsgAndReturnFalse()\ | |||||
| {mErrMsg = strerror(errno);\ | |||||
| return false;} | |||||
| #define setErrnoMsg() mErrMsg = strerror(errno); | |||||
| UdpCommu::UdpCommu(){} | |||||
| bool UdpCommu::bindTo(int port) | |||||
| { | |||||
| if (mSocket != -1) | |||||
| setFailedMsgAndReturnFalse("已经初始化"); | |||||
| //创建socket | |||||
| mSocket = socket(PF_INET, SOCK_DGRAM, 0); | |||||
| if (mSocket == -1) | |||||
| setErrnoMsgAndReturnFalse(); | |||||
| auto ret = -1; | |||||
| //允许广播 | |||||
| auto broadcast = 1; | |||||
| ret = setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(int)); | |||||
| if (ret == -1) | |||||
| setErrnoMsgAndReturnFalse(); | |||||
| //地址复用 | |||||
| auto reuse = 1; | |||||
| ret = setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)); | |||||
| if (ret == -1) | |||||
| setErrnoMsgAndReturnFalse(); | |||||
| //绑定 | |||||
| sockaddr_in addr; | |||||
| addr.sin_family = AF_INET; | |||||
| addr.sin_addr.s_addr = htonl(INADDR_ANY); | |||||
| addr.sin_port = htons(port); | |||||
| ret = ::bind(mSocket, (sockaddr*)&addr, sizeof(addr)); | |||||
| if (ret == -1) | |||||
| setErrnoMsgAndReturnFalse(); | |||||
| return true; | |||||
| } | |||||
| int UdpCommu::sentTo(const string& ip, int port, const void *data, int size) | |||||
| { | |||||
| sockaddr_in addr; | |||||
| addr.sin_family = AF_INET; | |||||
| addr.sin_addr.s_addr = inet_addr(ip.c_str()); | |||||
| addr.sin_port = htons(port); | |||||
| auto ret = ::sendto(mSocket, data, size, 0, (sockaddr*)&addr, sizeof(addr)); | |||||
| if (ret == -1) | |||||
| setErrnoMsg(); | |||||
| return ret; | |||||
| } | |||||
| bool UdpCommu::startAsyncRecv(UdpRecvHandler handler) | |||||
| { | |||||
| if (handler == nullptr) | |||||
| setFailedMsgAndReturnFalse("handler不能为空") | |||||
| if (mSocket == -1) | |||||
| setFailedMsgAndReturnFalse("请先初始化socket"); | |||||
| mRecvHandler = handler; | |||||
| if (!mAsyncMode) | |||||
| { | |||||
| mAsyncMode = true; | |||||
| std::thread t(&UdpCommu::recvThread, this); | |||||
| t.detach(); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| void UdpCommu::close() | |||||
| { | |||||
| if (mSocket == -1) | |||||
| return; | |||||
| ::close(mSocket); | |||||
| mSocket = -1; | |||||
| mAsyncMode=false; | |||||
| } | |||||
| string UdpCommu::getBoundMac() | |||||
| { | |||||
| //osx未定义SIOCGIFHWADDR,写死获取en0 | |||||
| int mib[6]; | |||||
| size_t len=0; | |||||
| unsigned char *ptr; | |||||
| struct if_msghdr *ifm; | |||||
| struct sockaddr_dl *sdl; | |||||
| mib[0] = CTL_NET; | |||||
| mib[1] = AF_ROUTE; | |||||
| mib[2] = 0; | |||||
| mib[3] = AF_LINK; | |||||
| mib[4] = NET_RT_IFLIST; | |||||
| if ((mib[5] = if_nametoindex("en0")) == 0) { | |||||
| perror("if_nametoindex error"); | |||||
| return ""; | |||||
| } | |||||
| if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { | |||||
| perror("sysctl 1 error"); | |||||
| return ""; | |||||
| } | |||||
| unique_ptr<char[]> buf(new char[len]); | |||||
| if (sysctl(mib, 6, buf.get(), &len, NULL, 0) < 0) { | |||||
| perror("sysctl 2 error"); | |||||
| return ""; | |||||
| } | |||||
| ifm = (struct if_msghdr *)buf.get(); | |||||
| sdl = (struct sockaddr_dl *)(ifm + 1); | |||||
| ptr = (unsigned char *)LLADDR(sdl); | |||||
| char macStr[20]={0}; | |||||
| snprintf(macStr, sizeof(macStr), "%02x%02x%02x%02x%02x%02x", *ptr, *(ptr+1), *(ptr+2), | |||||
| *(ptr+3), *(ptr+4), *(ptr+5)); | |||||
| return macStr; | |||||
| } | |||||
| string UdpCommu::getErrMsg() | |||||
| { | |||||
| return mErrMsg; | |||||
| } | |||||
| void UdpCommu::recvThread() | |||||
| { | |||||
| timeval timeo = {3,0}; | |||||
| auto ret = setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeval)); | |||||
| if (ret != 0){ | |||||
| printf("faield to set recv timeo\n"); | |||||
| mAsyncMode=false; | |||||
| return; | |||||
| } | |||||
| std::array<char,MAX_RCV_SIZE> buf; | |||||
| sockaddr_in addr; | |||||
| socklen_t len = sizeof(addr); | |||||
| while (mSocket != -1) { | |||||
| buf.fill(0); | |||||
| memset(&addr, 0, len); | |||||
| auto size = recvfrom(mSocket, buf.data(), MAX_RCV_SIZE, 0, (sockaddr*)&addr, &len); | |||||
| if (size < 0) | |||||
| { | |||||
| if (errno == EAGAIN || errno == ETIMEDOUT) | |||||
| continue; | |||||
| printf("error occur:%s\n", strerror(errno)); | |||||
| break; | |||||
| } | |||||
| auto ip = inet_ntoa(addr.sin_addr); | |||||
| vector<char> data(std::begin(buf), std::begin(buf)+size); | |||||
| mRecvHandler(ip, data); | |||||
| } | |||||
| printf("end recv thread\n"); | |||||
| mAsyncMode=false; | |||||
| } | |||||
| @@ -0,0 +1,68 @@ | |||||
| #ifndef UDPCOMMU_H | |||||
| #define UDPCOMMU_H | |||||
| #include <string> | |||||
| #include <functional> | |||||
| #include <vector> | |||||
| using namespace std; | |||||
| #define MAX_RCV_SIZE 4096 | |||||
| typedef function<void (const string& ip, vector<char> &data)> UdpRecvHandler; | |||||
| class UdpCommu | |||||
| { | |||||
| public: | |||||
| UdpCommu(); | |||||
| public: | |||||
| /** | |||||
| * @brief bindTo 绑定到本地端口 | |||||
| * @param port 端口号 | |||||
| * @return 是否绑定成功 | |||||
| */ | |||||
| bool bindTo(int port); | |||||
| /** | |||||
| * @brief sentTo 往目标地址发送数据 | |||||
| * @param ip 目标ip | |||||
| * @param port 目标端口 | |||||
| * @param data 数据指针 | |||||
| * @param size 数据大小 | |||||
| * @return >=0发送成功的字节数,-1发送失败 | |||||
| */ | |||||
| int sentTo(const string &ip, int port, const void *data, int size); | |||||
| /** | |||||
| * @brief startAsyncRecv 开始异步接收数据 | |||||
| * @param handler 处理函数 | |||||
| * @return 是否启动成功 | |||||
| */ | |||||
| bool startAsyncRecv(UdpRecvHandler handler); | |||||
| /** | |||||
| * @brief close 关闭udp通信 | |||||
| */ | |||||
| void close(); | |||||
| /** | |||||
| * @brief getBoundMac 获取udp通信绑定的网卡 | |||||
| * @return 绑定的网卡地址 | |||||
| */ | |||||
| string getBoundMac(); | |||||
| public: | |||||
| /** | |||||
| * @brief getErrMsg 获取最近一次错误的错误信息 | |||||
| * @return 最近一次的错误信息 | |||||
| */ | |||||
| string getErrMsg(); | |||||
| private: | |||||
| void recvThread(); | |||||
| bool mAsyncMode=false; | |||||
| private: | |||||
| string mErrMsg=""; | |||||
| int mSocket=-1; | |||||
| UdpRecvHandler mRecvHandler=nullptr; | |||||
| }; | |||||
| #endif // UDPCOMMU_H | |||||
| @@ -0,0 +1,16 @@ | |||||
| #include "uniqueid.h" | |||||
| #include <limits.h> | |||||
| UniqueId::UniqueId() | |||||
| { | |||||
| mId = 0; | |||||
| } | |||||
| IdType UniqueId::get() | |||||
| { | |||||
| auto id = ++mId; | |||||
| if (id >= INT_MAX || id < 0) | |||||
| mId=1; | |||||
| return mId; | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| #ifndef UNIQUEID_H | |||||
| #define UNIQUEID_H | |||||
| #include <atomic> | |||||
| using namespace std; | |||||
| typedef unsigned long long IdType; | |||||
| /** | |||||
| * @brief The UniqueId class 返回大于0小于MAX_ULONG的数,多线程安全,自动归0 | |||||
| */ | |||||
| class UniqueId | |||||
| { | |||||
| public: | |||||
| UniqueId(); | |||||
| public: | |||||
| IdType get(); | |||||
| private: | |||||
| atomic_ullong mId; | |||||
| }; | |||||
| #endif // UNIQUEID_H | |||||
| @@ -0,0 +1,84 @@ | |||||
| #include "utils.h" | |||||
| vector<string> splitAllowSeperator(vector<char>::iterator from, vector<char>::iterator to, char sep) | |||||
| { | |||||
| vector<string> values; | |||||
| vector<char> buf; | |||||
| while(from < to) | |||||
| { | |||||
| if (*from == sep) | |||||
| { | |||||
| if (from+1 != to && *(from+1) == sep) | |||||
| { | |||||
| ++from; | |||||
| } | |||||
| else | |||||
| { | |||||
| char* value = new char[buf.size()+1]; | |||||
| memcpy(value, buf.data(), buf.size()); | |||||
| value[buf.size()]=0; | |||||
| values.push_back(value); | |||||
| delete[] value; | |||||
| buf.clear(); | |||||
| ++from; | |||||
| continue; | |||||
| } | |||||
| } | |||||
| buf.push_back(*from); | |||||
| ++from; | |||||
| } | |||||
| return values; | |||||
| } | |||||
| void stringReplace(string& target,const string& pattern,const string& candidate) | |||||
| { | |||||
| auto pos=0; | |||||
| auto ps=pattern.size(); | |||||
| auto cs=candidate.size(); | |||||
| while((pos=target.find(pattern,pos)) != string::npos) | |||||
| { | |||||
| target.replace(pos,ps,candidate); | |||||
| pos+=cs; | |||||
| } | |||||
| } | |||||
| string getFileNameFromPath(const string &path) | |||||
| { | |||||
| auto sep = '/'; | |||||
| auto result = path; | |||||
| if (result.at(result.length()-1) == sep) | |||||
| result = result.substr(0, result.length()-1); | |||||
| auto pos = result.find_last_of('/'); | |||||
| if (pos == string::npos) | |||||
| return result; | |||||
| return result.substr(pos+1); | |||||
| } | |||||
| bool startsWith(const string &str, const string &patten) | |||||
| { | |||||
| if (str.length() < patten.length()) | |||||
| return false; | |||||
| return std::equal(patten.begin(), patten.end(), str.begin()); | |||||
| } | |||||
| bool endsWith(const string &str, const string &patten) | |||||
| { | |||||
| if (str.length() < patten.length()) | |||||
| return false; | |||||
| return std::equal(patten.rbegin(), patten.rend(), str.rbegin()); | |||||
| } | |||||
| string toString(const vector<char> &buf) | |||||
| { | |||||
| auto len = buf.size(); | |||||
| char* value = new char[len+1]; | |||||
| memcpy(value, buf.data(), len); | |||||
| value[len]=0; | |||||
| string result = value; | |||||
| delete[] value; | |||||
| return result; | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| #ifndef UTILS_H | |||||
| #define UTILS_H | |||||
| #include <vector> | |||||
| #include <string> | |||||
| using namespace std; | |||||
| vector<string> splitAllowSeperator(vector<char>::iterator from, vector<char>::iterator to, char sep); | |||||
| void stringReplace(string& target,const string& pattern,const string& candidate); | |||||
| string getFileNameFromPath(const string& path); | |||||
| bool startsWith(const string& str, const string& patten); | |||||
| bool endsWith(const string& str, const string& patten); | |||||
| string toString(const vector<char>& buf); | |||||
| #endif // UTILS_H | |||||
| @@ -0,0 +1,106 @@ | |||||
| #include "fellowlistwidget.h" | |||||
| #include <QDebug> | |||||
| FellowListWidget::FellowListWidget() | |||||
| { | |||||
| } | |||||
| void FellowListWidget::bindTo(QListWidget *widget) | |||||
| { | |||||
| mWidget = widget; | |||||
| connect(mWidget, &QListWidget::itemClicked, this, &FellowListWidget::itemChosen); | |||||
| } | |||||
| void FellowListWidget::update(const Fellow &fellow) | |||||
| { | |||||
| auto item = findFirstItem(fellow); | |||||
| if (item == nullptr) | |||||
| { | |||||
| mWidget->addItem(fellowText(fellow)); | |||||
| item = mWidget->item(mWidget->count()-1); | |||||
| } | |||||
| else | |||||
| { | |||||
| item->setText(fellowText(fellow)); | |||||
| } | |||||
| item->setData(Qt::UserRole, QVariant::fromValue((void*)&fellow)); | |||||
| } | |||||
| void FellowListWidget::top(const Fellow &fellow) | |||||
| { | |||||
| auto item = findFirstItem(fellow); | |||||
| if (item != nullptr) | |||||
| { | |||||
| mWidget->takeItem(mWidget->row(item)); | |||||
| mWidget->insertItem(0, item); | |||||
| mWidget->scrollToItem(item); | |||||
| mWidget->setCurrentItem(item); | |||||
| } | |||||
| } | |||||
| //TODO:take->insert的方式或导致如果item是当前焦点,则移动后焦点丢失 | |||||
| void FellowListWidget::topSecond(const Fellow &fellow) | |||||
| { | |||||
| auto item = findFirstItem(fellow); | |||||
| if (item != nullptr) | |||||
| { | |||||
| mWidget->takeItem(mWidget->row(item)); | |||||
| mWidget->insertItem(1, item); | |||||
| } | |||||
| } | |||||
| void FellowListWidget::mark(const Fellow &fellow, const QString &info) | |||||
| { | |||||
| auto item = findFirstItem(fellow); | |||||
| if (item != nullptr) | |||||
| { | |||||
| if (info.isEmpty()) | |||||
| item->setText(fellowText(fellow)); | |||||
| else | |||||
| { | |||||
| item->setText("("+info+")"+fellowText(fellow)); | |||||
| if (mWidget->row(item)!=0) | |||||
| { | |||||
| if (mWidget->currentRow() == 0) | |||||
| topSecond(fellow); | |||||
| else | |||||
| top(fellow); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| void FellowListWidget::itemChosen(QListWidgetItem *item) | |||||
| { | |||||
| if (item == nullptr) | |||||
| return; | |||||
| auto fellow = static_cast<const Fellow*>(item->data(Qt::UserRole).value<void*>()); | |||||
| emit select(fellow); | |||||
| } | |||||
| QString FellowListWidget::fellowText(const Fellow &fellow) | |||||
| { | |||||
| auto text = fellow.getName()+","+fellow.getIp(); | |||||
| if (!fellow.isOnLine()) | |||||
| { | |||||
| text = "[离线]"+text; | |||||
| } | |||||
| return QString(text.c_str()); | |||||
| } | |||||
| QListWidgetItem *FellowListWidget::findFirstItem(const Fellow &fellow) | |||||
| { | |||||
| auto count = mWidget->count(); | |||||
| for (int i = 0; i < count; i++) | |||||
| { | |||||
| auto widget = mWidget->item(i); | |||||
| auto f = static_cast<const Fellow*>(widget->data(Qt::UserRole).value<void*>()); | |||||
| if (f->getIp() == fellow.getIp()) | |||||
| return widget; | |||||
| } | |||||
| return nullptr; | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| #ifndef FELLOWLISTWIDGET_H | |||||
| #define FELLOWLISTWIDGET_H | |||||
| #include "feiqlib/feiqmodel.h" | |||||
| #include <QListWidget> | |||||
| //1.按“会话中”,“有新消息”、“在线”、“离线”优先级罗列好友信息 | |||||
| //2.支持查询好友 | |||||
| class FellowListWidget : public QObject | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| FellowListWidget(); | |||||
| void bindTo(QListWidget* widget); | |||||
| public: | |||||
| void update(const Fellow& fellow); | |||||
| void top(const Fellow& fellow); | |||||
| void topSecond(const Fellow& fellow); | |||||
| void mark(const Fellow& fellow, const QString &info); | |||||
| signals: | |||||
| void select(const Fellow* fellow); | |||||
| private slots: | |||||
| void itemChosen(QListWidgetItem *item); | |||||
| private: | |||||
| QString fellowText(const Fellow& fellow); | |||||
| QListWidgetItem* findFirstItem(const Fellow& fellow); | |||||
| private: | |||||
| QListWidget* mWidget; | |||||
| }; | |||||
| #endif // FELLOWLISTWIDGET_H | |||||
| @@ -0,0 +1,230 @@ | |||||
| #include "filemanagerdlg.h" | |||||
| #include "ui_downloadfiledlg.h" | |||||
| #include <sstream> | |||||
| #include <QMessageBox> | |||||
| #include <QFileDialog> | |||||
| #define TYPE_COL 0 | |||||
| #define STATE_COL 1 | |||||
| #define FELLOW_COL 2 | |||||
| #define FILENAME_COL 3 | |||||
| #define PROGRESS_COL 4 | |||||
| FileManagerDlg::FileManagerDlg(QWidget *parent) : | |||||
| QDialog(parent), | |||||
| ui(new Ui::DownloadFileDlg) | |||||
| { | |||||
| ui->setupUi(this); | |||||
| connect(ui->clearBtn, SIGNAL(clicked(bool)), this, SLOT(clear())); | |||||
| connect(ui->delBtn, SIGNAL(clicked(bool)), this, SLOT(delSelTask())); | |||||
| connect(ui->saveBtn, SIGNAL(clicked(bool)), this, SLOT(saveSelTask())); | |||||
| connect(ui->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(refresh())); | |||||
| } | |||||
| FileManagerDlg::~FileManagerDlg() | |||||
| { | |||||
| delete ui; | |||||
| } | |||||
| void FileManagerDlg::setEngine(FeiqEngine *engine) | |||||
| { | |||||
| mEngine = engine; | |||||
| } | |||||
| void FileManagerDlg::select(FileTask *task) | |||||
| { | |||||
| refresh(); | |||||
| auto row = findRowByTask(task); | |||||
| if (row == -1) | |||||
| return; | |||||
| ui->taskTable->setCurrentCell(row, TYPE_COL); | |||||
| } | |||||
| void FileManagerDlg::statChanged(FileTask *task) | |||||
| { | |||||
| auto row = findRowByTask(task); | |||||
| if (row == -1) | |||||
| return; | |||||
| auto str = stateString(task); | |||||
| auto item = ui->taskTable->item(row, STATE_COL); | |||||
| item->setText(str); | |||||
| } | |||||
| void FileManagerDlg::progressChanged(FileTask *task) | |||||
| { | |||||
| auto row = findRowByTask(task); | |||||
| if (row == -1) | |||||
| return; | |||||
| auto str = progressString(task); | |||||
| auto item = ui->taskTable->item(row, PROGRESS_COL); | |||||
| item->setText(str); | |||||
| } | |||||
| void FileManagerDlg::delSelTask() | |||||
| { | |||||
| auto task = getTaskOfCurrentRow(); | |||||
| if (task) | |||||
| { | |||||
| if (task->getState() == FileTaskState::Running) | |||||
| { | |||||
| QMessageBox::warning(this, "无法删除", "不能删除正上传或下载中的任务", QMessageBox::Ok); | |||||
| } | |||||
| else | |||||
| { | |||||
| mEngine->getModel().removeFileTask([task](const FileTask& t){ | |||||
| return &t == task; | |||||
| }); | |||||
| refresh(); | |||||
| } | |||||
| } | |||||
| } | |||||
| void FileManagerDlg::saveSelTask() | |||||
| { | |||||
| auto task = getTaskOfCurrentRow(); | |||||
| if (task && task->type() == FileTaskType::Download) | |||||
| { | |||||
| if (task->getContent()->path.empty()) | |||||
| { | |||||
| QString path = QFileDialog::getExistingDirectory(this, "选择保存到……"); | |||||
| if (!path.isEmpty()) | |||||
| { | |||||
| task->getContent()->path = path.toStdString()+"/"+task->getContent()->filename; | |||||
| } | |||||
| } | |||||
| if (!task->getContent()->path.empty()) | |||||
| mEngine->downloadFile(task); | |||||
| } | |||||
| } | |||||
| void FileManagerDlg::clear() | |||||
| { | |||||
| mEngine->getModel().removeFileTask([](const FileTask& task){ | |||||
| return task.getState() != FileTaskState::Running && task.getState() != FileTaskState::NotStart; | |||||
| }); | |||||
| refresh(); | |||||
| } | |||||
| void FileManagerDlg::refresh() | |||||
| { | |||||
| auto filter = ui->searchEdit->text().toStdString(); | |||||
| reloadWith([filter](const FileTask& task){ | |||||
| if (!filter.empty()) | |||||
| { | |||||
| return task.getContent()->filename.find(filter)!=string::npos | |||||
| ||task.fellow()->getName().find(filter)!=string::npos; | |||||
| } | |||||
| return true; | |||||
| }); | |||||
| } | |||||
| void FileManagerDlg::reloadWith(FileManagerDlg::SearchPredict predict) | |||||
| { | |||||
| if (!mEngine) | |||||
| return; | |||||
| auto table = ui->taskTable; | |||||
| // table->clearContents(); | |||||
| auto rowCount = table->rowCount(); | |||||
| for (int i = 0; i < rowCount; i++) | |||||
| table->removeRow(0); | |||||
| auto tasks = mEngine->getModel().searchTask(predict); | |||||
| for (shared_ptr<FileTask> task : tasks) | |||||
| { | |||||
| auto row = table->rowCount(); | |||||
| table->insertRow(row); | |||||
| auto item = new QTableWidgetItem(typeString(task->type())); | |||||
| item->setData(Qt::UserRole, QVariant::fromValue((void*)(task.get())));//把task保存到UserRole中 | |||||
| table->setItem(row, TYPE_COL, item); | |||||
| table->setItem(row, STATE_COL, new QTableWidgetItem(stateString(task.get()))); | |||||
| table->setItem(row, FELLOW_COL, new QTableWidgetItem(task->fellow()->getName().c_str())); | |||||
| table->setItem(row, FILENAME_COL, new QTableWidgetItem(task->getContent()->filename.c_str())); | |||||
| table->setItem(row, PROGRESS_COL, new QTableWidgetItem(progressString(task.get()))); | |||||
| } | |||||
| table->resizeColumnToContents(TYPE_COL); | |||||
| table->resizeColumnToContents(TYPE_COL); | |||||
| table->resizeColumnToContents(FELLOW_COL); | |||||
| table->resizeColumnToContents(FILENAME_COL); | |||||
| table->setCurrentCell(0,0); | |||||
| } | |||||
| QString FileManagerDlg::typeString(FileTaskType type) | |||||
| { | |||||
| switch (type) | |||||
| { | |||||
| case FileTaskType::Download: | |||||
| return "下载"; | |||||
| case FileTaskType::Upload: | |||||
| return "上传"; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| QString FileManagerDlg::stateString(const FileTask* task) | |||||
| { | |||||
| switch (task->getState()) { | |||||
| case FileTaskState::Canceled: | |||||
| return "已取消"; | |||||
| case FileTaskState::Error: | |||||
| return task->getDetailInfo().c_str(); | |||||
| case FileTaskState::Finish: | |||||
| return "已完成"; | |||||
| case FileTaskState::NotStart: | |||||
| return "还未开始"; | |||||
| case FileTaskState::Running: | |||||
| if (task->type()==FileTaskType::Upload) | |||||
| return "正在上传"; | |||||
| else | |||||
| return "正在下载"; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| QString FileManagerDlg::progressString(const FileTask *task) | |||||
| { | |||||
| stringstream ss; | |||||
| float percent = task->getProcess()*100.0f/task->getContent()->size; | |||||
| ss<<task->getProcess()<<"/"<<task->getContent()->size<<"("<<percent<<"%)"; | |||||
| return ss.str().c_str(); | |||||
| } | |||||
| FileTask *FileManagerDlg::getTaskOfCurrentRow() | |||||
| { | |||||
| auto row = ui->taskTable->currentRow(); | |||||
| auto widget = ui->taskTable->item(row, TYPE_COL); | |||||
| if (widget == nullptr) | |||||
| return nullptr; | |||||
| auto task = widget->data(Qt::UserRole); | |||||
| return static_cast<FileTask*>(task.value<void*>()); | |||||
| } | |||||
| int FileManagerDlg::findRowByTask(const FileTask *task) | |||||
| { | |||||
| int rowCount = ui->taskTable->rowCount(); | |||||
| for (int i = 0; i < rowCount; i++) | |||||
| { | |||||
| auto widget = ui->taskTable->item(i, TYPE_COL); | |||||
| auto itemTask = static_cast<FileTask*>(widget->data(Qt::UserRole).value<void*>()); | |||||
| if (itemTask == task) | |||||
| return i; | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| void FileManagerDlg::showEvent(QShowEvent *) | |||||
| { | |||||
| refresh(); | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| #ifndef DOWNLOADFILEDLG_H | |||||
| #define DOWNLOADFILEDLG_H | |||||
| #include <QDialog> | |||||
| #include <memory> | |||||
| #include "feiqlib/filetask.h" | |||||
| #include "feiqlib/feiqengine.h" | |||||
| using namespace std; | |||||
| namespace Ui { | |||||
| class DownloadFileDlg; | |||||
| } | |||||
| class FileManagerDlg : public QDialog | |||||
| { | |||||
| Q_OBJECT | |||||
| typedef function<bool (const FileTask &)> SearchPredict; | |||||
| public: | |||||
| explicit FileManagerDlg(QWidget *parent = 0); | |||||
| ~FileManagerDlg(); | |||||
| public: | |||||
| void setEngine(FeiqEngine* engine); | |||||
| void select(FileTask* task); | |||||
| public slots: | |||||
| void statChanged(FileTask* task); | |||||
| void progressChanged(FileTask* task); | |||||
| private slots: | |||||
| void delSelTask(); | |||||
| void saveSelTask(); | |||||
| void clear(); | |||||
| void refresh(); | |||||
| private: | |||||
| void reloadWith(SearchPredict predict); | |||||
| QString typeString(FileTaskType type); | |||||
| QString stateString(const FileTask *task); | |||||
| QString progressString(const FileTask* task); | |||||
| FileTask* getTaskOfCurrentRow(); | |||||
| int findRowByTask(const FileTask* task); | |||||
| protected: | |||||
| virtual void showEvent(QShowEvent *) override; | |||||
| private: | |||||
| Ui::DownloadFileDlg *ui; | |||||
| FeiqEngine* mEngine; | |||||
| }; | |||||
| #endif // DOWNLOADFILEDLG_H | |||||
| @@ -0,0 +1,21 @@ | |||||
| #include "mainwindow.h" | |||||
| #include <QApplication> | |||||
| #include <QWindow> | |||||
| //待测: | |||||
| //*无当前交谈用户,且有未读消息时,未读消息能否正确置顶? | |||||
| //应用图标 | |||||
| int main(int argc, char *argv[]) | |||||
| { | |||||
| QApplication a(argc, argv); | |||||
| a.setWindowIcon(QIcon(":/default/res/icon.png")); | |||||
| MainWindow w; | |||||
| w.show(); | |||||
| return a.exec(); | |||||
| } | |||||
| //表情->html | |||||
| //查通讯录功能 | |||||
| //查ip占用功能 | |||||
| //美化用户列表 | |||||
| @@ -0,0 +1,526 @@ | |||||
| #include "mainwindow.h" | |||||
| #include "ui_mainwindow.h" | |||||
| #include <thread> | |||||
| #include <QDir> | |||||
| #include <QDebug> | |||||
| #include <QMessageBox> | |||||
| #include <QTextCodec> | |||||
| #include <QFileDialog> | |||||
| #include <QDateTime> | |||||
| #include <QtMac> | |||||
| #include "addfellowdialog.h" | |||||
| #include <QProcess> | |||||
| #include "platformdepend.h" | |||||
| MainWindow::MainWindow(QWidget *parent) : | |||||
| QMainWindow(parent), | |||||
| ui(new Ui::MainWindow) | |||||
| { | |||||
| ui->setupUi(this); | |||||
| qRegisterMetaType<shared_ptr<ViewEvent>>("ViewEventSharedPtr"); | |||||
| connect(this, SIGNAL(showErrorAndQuit(QString)), this, SLOT(onShowErrorAndQuit(QString))); | |||||
| //初始化搜索对话框 | |||||
| mSearchFellowDlg = new SearchFellowDlg(this); | |||||
| connect(mSearchFellowDlg, SIGNAL(onFellowSelected(const Fellow*)), | |||||
| this, SLOT(finishSearch(const Fellow*))); | |||||
| connect(ui->actionRefreshFellows, SIGNAL(triggered(bool)), this, SLOT(refreshFellowList())); | |||||
| connect(ui->actionAddFellow, SIGNAL(triggered(bool)), this, SLOT(addFellow())); | |||||
| mSearchFellowDlg->setSearchDriver(std::bind(&MainWindow::fellowSearchDriver, this, placeholders::_1)); | |||||
| //初始化文件管理对话框 | |||||
| mDownloadFileDlg = new FileManagerDlg(this); | |||||
| mDownloadFileDlg->setEngine(&mFeiq); | |||||
| connect(this, SIGNAL(statChanged(FileTask*)), mDownloadFileDlg, SLOT(statChanged(FileTask*))); | |||||
| connect(this, SIGNAL(progressChanged(FileTask*)), mDownloadFileDlg, SLOT(progressChanged(FileTask*))); | |||||
| //初始化好友列表 | |||||
| mFellowList.bindTo(ui->fellowListWidget); | |||||
| connect(&mFellowList, SIGNAL(select(const Fellow*)), this, SLOT(openChartTo(const Fellow*))); | |||||
| //初始化接收文本框 | |||||
| mRecvTextEdit = ui->recvEdit; | |||||
| connect(mRecvTextEdit, SIGNAL(navigateToFileTask(IdType,IdType,bool)), this, SLOT(navigateToFileTask(IdType,IdType,bool))); | |||||
| //初始化发送文本框 | |||||
| mSendTextEdit = ui->sendEdit; | |||||
| connect(mSendTextEdit, SIGNAL(acceptDropFiles(QList<QFileInfo>)), this, SLOT(sendFiles(QList<QFileInfo>))); | |||||
| //初始化Emoji对话框 | |||||
| mChooseEmojiDlg = new ChooseEmojiDlg(this); | |||||
| connect(ui->actionInsertEmoji, SIGNAL(triggered(bool)), this, SLOT(openChooseEmojiDlg())); | |||||
| connect(mChooseEmojiDlg, SIGNAL(choose(QString)),mSendTextEdit, SLOT(insertPlainText(QString))); | |||||
| //初始化菜单 | |||||
| connect(ui->actionSearchFellow, SIGNAL(triggered(bool)), this, SLOT(openSearchDlg())); | |||||
| connect(ui->actionSettings, SIGNAL(triggered(bool)), this, SLOT(openSettings())); | |||||
| connect(ui->actionOpendl, SIGNAL(triggered(bool)), this, SLOT(openDownloadDlg())); | |||||
| connect(ui->actionSendText, SIGNAL(triggered(bool)), this, SLOT(sendText())); | |||||
| connect(ui->actionSendKnock, SIGNAL(triggered(bool)), this, SLOT(sendKnock())); | |||||
| connect(ui->actionSendFile, SIGNAL(triggered(bool)), this, SLOT(sendFile())); | |||||
| //加载配置 | |||||
| auto settingFilePath = QDir::home().filePath(".feiq_setting.ini"); | |||||
| mSettings = new QSettings(settingFilePath, QSettings::IniFormat); | |||||
| mSettings->setIniCodec(QTextCodec::codecForName("UTF-8")); | |||||
| mTitle = mSettings->value("app/title", "mac飞秋").toString(); | |||||
| setWindowTitle(mTitle); | |||||
| mUnreadTimerInterval = mSettings->value("app/unread_timer", "0").toInt(); | |||||
| if (mUnreadTimerInterval > 0) | |||||
| mUnreadTimerId = startTimer(mUnreadTimerInterval*1000, Qt::VeryCoarseTimer); | |||||
| //初始化飞秋引擎 | |||||
| connect(this, SIGNAL(feiqViewEvent(shared_ptr<ViewEvent>)), this, SLOT(handleFeiqViewEvent(shared_ptr<ViewEvent>))); | |||||
| //后台初始化通信 | |||||
| std::thread thd(&MainWindow::initFeiq, this); | |||||
| thd.detach(); | |||||
| } | |||||
| MainWindow::~MainWindow() | |||||
| { | |||||
| mFeiq.stop(); | |||||
| delete mSettings; | |||||
| delete mSearchFellowDlg; | |||||
| delete mDownloadFileDlg; | |||||
| delete mChooseEmojiDlg; | |||||
| delete ui; | |||||
| } | |||||
| void MainWindow::enterEvent(QEvent *event) | |||||
| { | |||||
| auto fellow = mRecvTextEdit->curFellow(); | |||||
| if (fellow) | |||||
| { | |||||
| flushUnread(fellow); | |||||
| updateUnread(fellow); | |||||
| } | |||||
| PlatformDepend::instance().hideAllNotify(); | |||||
| } | |||||
| void MainWindow::timerEvent(QTimerEvent *event) | |||||
| { | |||||
| if (event->timerId() == mUnreadTimerId) | |||||
| { | |||||
| auto count = getUnreadCount(); | |||||
| if (count > 0) | |||||
| PlatformDepend::instance().showNotify("未读提醒", QString("还有%1条未读消息").arg(count)); | |||||
| } | |||||
| } | |||||
| void MainWindow::openChartTo(const Fellow *fellow) | |||||
| { | |||||
| mFellowList.top(*fellow); | |||||
| mRecvTextEdit->setCurFellow(fellow); | |||||
| setWindowTitle(mTitle + " - 与"+fellow->getName().c_str()+"会话中"); | |||||
| flushUnread(fellow); | |||||
| updateUnread(fellow); | |||||
| } | |||||
| shared_ptr<Fellow> MainWindow::checkCurFellow() | |||||
| { | |||||
| auto fellow = mFeiq.getModel().getShared(mRecvTextEdit->curFellow()); | |||||
| if (fellow == nullptr) | |||||
| { | |||||
| mRecvTextEdit->addWarning("这是要发给谁?"); | |||||
| } | |||||
| return fellow; | |||||
| } | |||||
| void MainWindow::showResult(pair<bool, string> ret, const Content* content) | |||||
| { | |||||
| if (ret.first) | |||||
| mRecvTextEdit->addMyContent(content, QDateTime::currentDateTime().currentMSecsSinceEpoch()); | |||||
| else | |||||
| mRecvTextEdit->addWarning(ret.second.c_str()); | |||||
| } | |||||
| void MainWindow::onStateChanged(FileTask *fileTask) | |||||
| { | |||||
| if (mDownloadFileDlg->isVisible()) | |||||
| { | |||||
| emit statChanged(fileTask); | |||||
| } | |||||
| } | |||||
| void MainWindow::onProgress(FileTask *fileTask) | |||||
| { | |||||
| if (mDownloadFileDlg->isVisible()) | |||||
| { | |||||
| emit progressChanged(fileTask); | |||||
| } | |||||
| } | |||||
| void MainWindow::onEvent(shared_ptr<ViewEvent> event) | |||||
| { | |||||
| emit feiqViewEvent(event); | |||||
| } | |||||
| void MainWindow::onShowErrorAndQuit(const QString &text) | |||||
| { | |||||
| QMessageBox::warning(this, "出错了,为什么?你猜!", text, "退出应用"); | |||||
| QApplication::exit(-1); | |||||
| } | |||||
| void MainWindow::handleFeiqViewEvent(shared_ptr<ViewEvent> event) | |||||
| { | |||||
| if (event->what == ViewEventType::FELLOW_UPDATE) | |||||
| { | |||||
| auto e = static_cast<FellowViewEvent*>(event.get()); | |||||
| mFellowList.update(*(e->fellow.get())); | |||||
| } | |||||
| else if (event->what == ViewEventType::SEND_TIMEO || event->what == ViewEventType::MESSAGE) | |||||
| { | |||||
| //地球人都知道这个分支中的ViewEvent集成自FellowViewEvent | |||||
| auto e = static_cast<FellowViewEvent*>(event.get()); | |||||
| auto fellow = e->fellow.get(); | |||||
| if (isActiveWindow()) | |||||
| {//窗口可见,处理当前用户消息,其他用户消息则放入通知队列 | |||||
| if (fellow == mRecvTextEdit->curFellow()) | |||||
| { | |||||
| readEvent(event.get()); | |||||
| } | |||||
| else | |||||
| { | |||||
| mUnreadEvents[fellow].push_back(event); | |||||
| updateUnread(fellow); | |||||
| } | |||||
| } | |||||
| else | |||||
| {//窗口不可见,放入未读队列并通知 | |||||
| mUnreadEvents[fellow].push_back(event); | |||||
| updateUnread(fellow); | |||||
| notifyUnread(event.get()); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MainWindow::refreshFellowList() | |||||
| { | |||||
| mFeiq.sendImOnLine(); | |||||
| } | |||||
| void MainWindow::addFellow() | |||||
| { | |||||
| AddFellowDialog dlg(this); | |||||
| if (dlg.exec() == QDialog::Accepted) | |||||
| { | |||||
| auto ip = dlg.getIp(); | |||||
| userAddFellow(ip); | |||||
| } | |||||
| } | |||||
| void MainWindow::openChooseEmojiDlg() | |||||
| { | |||||
| mChooseEmojiDlg->exec(); | |||||
| } | |||||
| void MainWindow::sendFiles(QList<QFileInfo> files) | |||||
| { | |||||
| auto fellow = checkCurFellow(); | |||||
| if (!fellow) | |||||
| return; | |||||
| for (auto file : files) | |||||
| { | |||||
| if (file.isFile()) | |||||
| { | |||||
| sendFile(file.absoluteFilePath().toStdString()); | |||||
| } | |||||
| else | |||||
| { | |||||
| mRecvTextEdit->addWarning("不支持发送:"+file.absoluteFilePath()); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MainWindow::userAddFellow(QString ip) | |||||
| { | |||||
| //创建好友 | |||||
| auto fellow = make_shared<Fellow>(); | |||||
| fellow->setIp(ip.toStdString()); | |||||
| fellow->setOnLine(true); | |||||
| mFeiq.getModel().addFellow(fellow); | |||||
| //添加到列表 | |||||
| auto& ref = *(fellow.get()); | |||||
| mFellowList.update(ref); | |||||
| mFellowList.top(ref); | |||||
| //发送在线 | |||||
| mFeiq.sendImOnLine(fellow->getIp()); | |||||
| } | |||||
| void MainWindow::notifyUnread(const ViewEvent *event) | |||||
| { | |||||
| if (event->what == ViewEventType::SEND_TIMEO) | |||||
| { | |||||
| auto e = static_cast<const SendTimeoEvent*>(event); | |||||
| auto fellow = e->fellow.get(); | |||||
| showNotification(fellow, "发送超时:"+simpleTextOf(e->content.get())); | |||||
| } | |||||
| else if (event->what == ViewEventType::MESSAGE) | |||||
| { | |||||
| auto e = static_cast<const MessageViewEvent*>(event); | |||||
| auto fellow = e->fellow.get(); | |||||
| for (auto content : e->contents) | |||||
| { | |||||
| showNotification(fellow, simpleTextOf(content.get())); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MainWindow::showNotification(const Fellow *fellow, const QString &text) | |||||
| { | |||||
| QString content(text); | |||||
| if (content.length()>20) | |||||
| content = content.left(20)+"..."; | |||||
| PlatformDepend::instance().showNotify(QString(fellow->getName().c_str())+":", content); | |||||
| } | |||||
| void MainWindow::navigateToFileTask(IdType packetNo, IdType fileId, bool upload) | |||||
| { | |||||
| auto task = mFeiq.getModel().findTask(packetNo, fileId, upload ? FileTaskType::Upload : FileTaskType::Download); | |||||
| openDownloadDlg(); | |||||
| mDownloadFileDlg->select(task.get()); | |||||
| } | |||||
| void MainWindow::sendFile(std::string filepath) | |||||
| { | |||||
| auto content = FileContent::createFileContentToSend(filepath); | |||||
| auto fellow = checkCurFellow(); | |||||
| if (!fellow) | |||||
| return; | |||||
| if (content == nullptr) | |||||
| { | |||||
| mRecvTextEdit->addWarning("获取文件"+QString(filepath.c_str())+"的信息失败,不发送"); | |||||
| } | |||||
| else | |||||
| { | |||||
| auto fileContent = shared_ptr<FileContent>(std::move(content)); | |||||
| auto ret = mFeiq.send(fellow, fileContent); | |||||
| showResult(ret, fileContent.get()); | |||||
| } | |||||
| } | |||||
| void MainWindow::sendFile() | |||||
| { | |||||
| auto fellow = checkCurFellow(); | |||||
| if (!fellow) | |||||
| return; | |||||
| //文件多选 | |||||
| QFileDialog fdlg(this, "选择要发送的文件"); | |||||
| fdlg.setFileMode(QFileDialog::ExistingFiles); | |||||
| if (fdlg.exec() == QDialog::Accepted) | |||||
| { | |||||
| auto list = fdlg.selectedFiles(); | |||||
| auto count = list.count(); | |||||
| for (int i = 0; i < count; i++) | |||||
| { | |||||
| auto path = list.at(i); | |||||
| sendFile(path.toStdString()); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MainWindow::sendKnock() | |||||
| { | |||||
| auto fellow = checkCurFellow(); | |||||
| if (fellow) | |||||
| { | |||||
| auto content = make_shared<KnockContent>(); | |||||
| auto ret = mFeiq.send(fellow, content); | |||||
| showResult(ret, content.get()); | |||||
| } | |||||
| } | |||||
| void MainWindow::sendText() | |||||
| { | |||||
| auto text = mSendTextEdit->toPlainText(); | |||||
| if (text.isEmpty()) | |||||
| { | |||||
| mRecvTextEdit->addWarning("发送空文本是不科学的,驳回"); | |||||
| return; | |||||
| } | |||||
| auto fellow = checkCurFellow(); | |||||
| if (fellow) | |||||
| { | |||||
| auto content = make_shared<TextContent>(); | |||||
| content->text = text.toStdString(); | |||||
| auto ret = mFeiq.send(fellow, content); | |||||
| showResult(ret, content.get()); | |||||
| mSendTextEdit->clear(); | |||||
| } | |||||
| } | |||||
| void MainWindow::finishSearch(const Fellow *fellow) | |||||
| { | |||||
| mFellowList.top(*fellow); | |||||
| openChartTo(fellow); | |||||
| } | |||||
| void MainWindow::openSettings() | |||||
| { | |||||
| QMessageBox::information(this, "设置", "设置文件在:"+mSettings->fileName()+"\n重启后生效", | |||||
| QMessageBox::Ok); | |||||
| } | |||||
| void MainWindow::openSearchDlg() | |||||
| { | |||||
| mSearchFellowDlg->exec(); | |||||
| } | |||||
| void MainWindow::openDownloadDlg() | |||||
| { | |||||
| mDownloadFileDlg->show(); | |||||
| mDownloadFileDlg->raise(); | |||||
| } | |||||
| vector<const Fellow *> MainWindow::fellowSearchDriver(const QString &text) | |||||
| { | |||||
| auto fellows = mFeiq.getModel().searchFellow(text.toStdString()); | |||||
| vector<const Fellow*> result; | |||||
| for (auto fellow : fellows) | |||||
| { | |||||
| result.push_back(fellow.get()); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| void MainWindow::initFeiq() | |||||
| { | |||||
| //配置飞秋 | |||||
| mFeiq.setMyName(mSettings->value("user/name").toString().toStdString()); | |||||
| mFeiq.setMyHost(mSettings->value("user/host","feiq by cy").toString().toStdString()); | |||||
| auto customGrroup = mSettings->value("network/custom_group", "").toString(); | |||||
| if (!customGrroup.isEmpty()) | |||||
| { | |||||
| auto list = customGrroup.split("|"); | |||||
| for (int i = 0; i < list.size(); i++) | |||||
| { | |||||
| QString ipPrefix = list[i]; | |||||
| if (ipPrefix.endsWith(".")) | |||||
| { | |||||
| for (int j = 2; j < 254; j++) | |||||
| { | |||||
| auto ip = ipPrefix+QString::number(j); | |||||
| mFeiq.addToBroadcast(ip.toStdString()); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| mFeiq.setView(this); | |||||
| mFeiq.enableIntervalDetect(60); | |||||
| //启动飞秋 | |||||
| auto ret = mFeiq.start(); | |||||
| if (!ret.first) | |||||
| { | |||||
| emit showErrorAndQuit(ret.second.c_str()); | |||||
| } | |||||
| qDebug()<<"feiq started"; | |||||
| } | |||||
| void MainWindow::updateUnread(const Fellow *fellow) | |||||
| { | |||||
| auto it = mUnreadEvents.find(fellow); | |||||
| if (it != mUnreadEvents.end()) | |||||
| { | |||||
| auto count = (*it).second.size(); | |||||
| if (count == 0) | |||||
| { | |||||
| mFellowList.mark(*fellow, ""); | |||||
| } | |||||
| else | |||||
| { | |||||
| mFellowList.mark(*fellow, QString::number(count)); | |||||
| } | |||||
| } | |||||
| setBadgeNumber(getUnreadCount()); | |||||
| } | |||||
| int MainWindow::getUnreadCount() | |||||
| { | |||||
| auto begin = mUnreadEvents.begin(); | |||||
| auto end = mUnreadEvents.end(); | |||||
| auto count = 0; | |||||
| for (auto it = begin; it != end; it++) | |||||
| { | |||||
| count += it->second.size(); | |||||
| } | |||||
| return count; | |||||
| } | |||||
| void MainWindow::flushUnread(const Fellow *fellow) | |||||
| { | |||||
| auto it = mUnreadEvents.find(fellow); | |||||
| if (it != mUnreadEvents.end()) | |||||
| { | |||||
| auto& list = (*it).second; | |||||
| while (!list.empty()) | |||||
| { | |||||
| auto event = list.front(); | |||||
| readEvent(event.get()); | |||||
| list.pop_front(); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MainWindow::readEvent(const ViewEvent *event) | |||||
| { | |||||
| if (event->what == ViewEventType::SEND_TIMEO) | |||||
| { | |||||
| auto e = static_cast<const SendTimeoEvent*>(event); | |||||
| auto simpleText = simpleTextOf(e->content.get()); | |||||
| if (simpleText.length()>20){ | |||||
| simpleText = simpleText.left(20)+"..."; | |||||
| } | |||||
| mRecvTextEdit->addWarning("发送超时:"+simpleText); | |||||
| } | |||||
| else if (event->what == ViewEventType::MESSAGE) | |||||
| { | |||||
| auto e = static_cast<const MessageViewEvent*>(event); | |||||
| auto time = e->when.time_since_epoch().count(); | |||||
| for (auto content : e->contents) | |||||
| { | |||||
| mRecvTextEdit->addFellowContent(content.get(), time); | |||||
| } | |||||
| } | |||||
| } | |||||
| void MainWindow::setBadgeNumber(int number) | |||||
| { | |||||
| PlatformDepend::instance().setBadgeNumber(number); | |||||
| } | |||||
| QString MainWindow::simpleTextOf(const Content *content) | |||||
| { | |||||
| switch (content->type()) { | |||||
| case ContentType::Text: | |||||
| return static_cast<const TextContent*>(content)->text.c_str(); | |||||
| break; | |||||
| case ContentType::File: | |||||
| return static_cast<const FileContent*>(content)->filename.c_str(); | |||||
| case ContentType::Knock: | |||||
| return "窗口抖动"; | |||||
| default: | |||||
| return "***"; | |||||
| break; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| #ifndef MAINWINDOW_H | |||||
| #define MAINWINDOW_H | |||||
| #include <QMainWindow> | |||||
| #include "fellowlistwidget.h" | |||||
| #include "searchfellowdlg.h" | |||||
| #include "recvtextedit.h" | |||||
| #include "feiqlib/feiqengine.h" | |||||
| #include "filemanagerdlg.h" | |||||
| #include <QSettings> | |||||
| #include <unordered_map> | |||||
| #include "chooseemojidlg.h" | |||||
| #include <QFileInfo> | |||||
| #include "sendtextedit.h" | |||||
| using namespace std; | |||||
| namespace Ui { | |||||
| class MainWindow; | |||||
| } | |||||
| Q_DECLARE_METATYPE(shared_ptr<ViewEvent>) | |||||
| class MainWindow : public QMainWindow, IFeiqView | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| explicit MainWindow(QWidget *parent = 0); | |||||
| ~MainWindow(); | |||||
| protected: | |||||
| void enterEvent(QEvent *event); | |||||
| void timerEvent(QTimerEvent *event); | |||||
| signals: | |||||
| void showErrorAndQuit(const QString& text); | |||||
| void statChanged(FileTask* fileTask); | |||||
| void progressChanged(FileTask* fileTask); | |||||
| void feiqViewEvent(shared_ptr<ViewEvent> event); | |||||
| private slots: | |||||
| void finishSearch(const Fellow* fellow); | |||||
| void openSettings(); | |||||
| void openSearchDlg(); | |||||
| void openDownloadDlg(); | |||||
| void onShowErrorAndQuit(const QString& text); | |||||
| void navigateToFileTask(IdType packetNo, IdType fileId, bool upload); | |||||
| void sendKnock(); | |||||
| void sendText(); | |||||
| void openChartTo(const Fellow* fellow); | |||||
| void handleFeiqViewEvent(shared_ptr<ViewEvent> event); | |||||
| void refreshFellowList(); | |||||
| void addFellow(); | |||||
| void openChooseEmojiDlg(); | |||||
| void sendFile(); | |||||
| void sendFile(string filepath); | |||||
| void sendFiles(QList<QFileInfo> files); | |||||
| private: | |||||
| void userAddFellow(QString ip); | |||||
| void notifyUnread(const ViewEvent* event); | |||||
| void showNotification(const Fellow* fellow, const QString& text); | |||||
| shared_ptr<Fellow> checkCurFellow(); | |||||
| void showResult(pair<bool, string> ret, const Content *content); | |||||
| vector<const Fellow*> fellowSearchDriver(const QString& text); | |||||
| void initFeiq(); | |||||
| void updateUnread(const Fellow* fellow); | |||||
| int getUnreadCount(); | |||||
| void flushUnread(const Fellow* fellow); | |||||
| void readEvent(const ViewEvent* event); | |||||
| void setBadgeNumber(int number); | |||||
| QString simpleTextOf(const Content* content); | |||||
| // IFileTaskObserver interface | |||||
| public: | |||||
| void onStateChanged(FileTask *fileTask); | |||||
| void onProgress(FileTask *fileTask); | |||||
| // IFeiqView interface | |||||
| public: | |||||
| void onEvent(shared_ptr<ViewEvent> event); | |||||
| private: | |||||
| Ui::MainWindow *ui; | |||||
| FellowListWidget mFellowList; | |||||
| SearchFellowDlg* mSearchFellowDlg; | |||||
| FileManagerDlg* mDownloadFileDlg; | |||||
| ChooseEmojiDlg* mChooseEmojiDlg; | |||||
| QSettings* mSettings; | |||||
| FeiqEngine mFeiq; | |||||
| RecvTextEdit* mRecvTextEdit; | |||||
| SendTextEdit* mSendTextEdit; | |||||
| QString mTitle; | |||||
| unordered_map<const Fellow*, list<shared_ptr<ViewEvent>>> mUnreadEvents; | |||||
| int mUnreadTimerInterval; | |||||
| int mUnreadTimerId; | |||||
| }; | |||||
| #endif // MAINWINDOW_H | |||||
| @@ -0,0 +1,209 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <ui version="4.0"> | |||||
| <class>MainWindow</class> | |||||
| <widget class="QMainWindow" name="MainWindow"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>0</y> | |||||
| <width>744</width> | |||||
| <height>576</height> | |||||
| </rect> | |||||
| </property> | |||||
| <property name="font"> | |||||
| <font> | |||||
| <family>Monaco</family> | |||||
| </font> | |||||
| </property> | |||||
| <property name="acceptDrops"> | |||||
| <bool>true</bool> | |||||
| </property> | |||||
| <property name="windowTitle"> | |||||
| <string>feiq</string> | |||||
| </property> | |||||
| <widget class="QWidget" name="centralWidget"> | |||||
| <layout class="QHBoxLayout" name="horizontalLayout"> | |||||
| <property name="spacing"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <item> | |||||
| <layout class="QVBoxLayout" name="verticalLayout"> | |||||
| <item> | |||||
| <widget class="QListWidget" name="fellowListWidget"> | |||||
| <property name="sizePolicy"> | |||||
| <sizepolicy hsizetype="Fixed" vsizetype="Expanding"> | |||||
| <horstretch>0</horstretch> | |||||
| <verstretch>0</verstretch> | |||||
| </sizepolicy> | |||||
| </property> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>50</width> | |||||
| <height>0</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="sizeIncrement"> | |||||
| <size> | |||||
| <width>0</width> | |||||
| <height>0</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="baseSize"> | |||||
| <size> | |||||
| <width>0</width> | |||||
| <height>0</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="focusPolicy"> | |||||
| <enum>Qt::ClickFocus</enum> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </item> | |||||
| <item> | |||||
| <layout class="QVBoxLayout" name="verticalLayout_2"> | |||||
| <property name="spacing"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <item> | |||||
| <widget class="RecvTextEdit" name="recvEdit"> | |||||
| <property name="focusPolicy"> | |||||
| <enum>Qt::ClickFocus</enum> | |||||
| </property> | |||||
| <property name="readOnly"> | |||||
| <bool>true</bool> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="SendTextEdit" name="sendEdit"> | |||||
| <property name="sizePolicy"> | |||||
| <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> | |||||
| <horstretch>0</horstretch> | |||||
| <verstretch>0</verstretch> | |||||
| </sizepolicy> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| <widget class="QMenuBar" name="menuBar"> | |||||
| <property name="geometry"> | |||||
| <rect> | |||||
| <x>0</x> | |||||
| <y>0</y> | |||||
| <width>744</width> | |||||
| <height>22</height> | |||||
| </rect> | |||||
| </property> | |||||
| <widget class="QMenu" name="searchMenu"> | |||||
| <property name="title"> | |||||
| <string>好友</string> | |||||
| </property> | |||||
| <addaction name="actionSearchFellow"/> | |||||
| <addaction name="actionRefreshFellows"/> | |||||
| <addaction name="actionAddFellow"/> | |||||
| </widget> | |||||
| <widget class="QMenu" name="settingMenu"> | |||||
| <property name="title"> | |||||
| <string>设置</string> | |||||
| </property> | |||||
| <addaction name="actionSettings"/> | |||||
| </widget> | |||||
| <widget class="QMenu" name="menuDownload"> | |||||
| <property name="title"> | |||||
| <string>下载</string> | |||||
| </property> | |||||
| <addaction name="actionOpendl"/> | |||||
| </widget> | |||||
| <widget class="QMenu" name="menuEdit"> | |||||
| <property name="title"> | |||||
| <string>编辑</string> | |||||
| </property> | |||||
| <addaction name="actionSendText"/> | |||||
| <addaction name="actionSendKnock"/> | |||||
| <addaction name="actionSendFile"/> | |||||
| <addaction name="actionInsertEmoji"/> | |||||
| </widget> | |||||
| <addaction name="searchMenu"/> | |||||
| <addaction name="settingMenu"/> | |||||
| <addaction name="menuDownload"/> | |||||
| <addaction name="menuEdit"/> | |||||
| </widget> | |||||
| <action name="actionSearchFellow"> | |||||
| <property name="text"> | |||||
| <string>查找</string> | |||||
| </property> | |||||
| <property name="shortcut"> | |||||
| <string>Ctrl+S</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionSettings"> | |||||
| <property name="text"> | |||||
| <string>打开设置文件</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionOpendl"> | |||||
| <property name="text"> | |||||
| <string>打开文件管理对话框</string> | |||||
| </property> | |||||
| <property name="shortcut"> | |||||
| <string>Ctrl+D</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionSendText"> | |||||
| <property name="text"> | |||||
| <string>发送文本消息</string> | |||||
| </property> | |||||
| <property name="shortcut"> | |||||
| <string>Ctrl+Return</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionSendKnock"> | |||||
| <property name="text"> | |||||
| <string>发送窗口抖动</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionSendFile"> | |||||
| <property name="text"> | |||||
| <string>发送文件</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionRefreshFellows"> | |||||
| <property name="text"> | |||||
| <string>刷新</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionAddFellow"> | |||||
| <property name="text"> | |||||
| <string>手动添加</string> | |||||
| </property> | |||||
| </action> | |||||
| <action name="actionInsertEmoji"> | |||||
| <property name="text"> | |||||
| <string>插入表情</string> | |||||
| </property> | |||||
| <property name="shortcut"> | |||||
| <string>Ctrl+E</string> | |||||
| </property> | |||||
| </action> | |||||
| </widget> | |||||
| <layoutdefault spacing="6" margin="11"/> | |||||
| <customwidgets> | |||||
| <customwidget> | |||||
| <class>RecvTextEdit</class> | |||||
| <extends>QTextEdit</extends> | |||||
| <header>recvtextedit.h</header> | |||||
| </customwidget> | |||||
| <customwidget> | |||||
| <class>SendTextEdit</class> | |||||
| <extends>QTextEdit</extends> | |||||
| <header>sendtextedit.h</header> | |||||
| </customwidget> | |||||
| </customwidgets> | |||||
| <resources/> | |||||
| <connections/> | |||||
| </ui> | |||||
| @@ -0,0 +1,14 @@ | |||||
| #ifndef OSXNOTIFICATION_H | |||||
| #define OSXNOTIFICATION_H | |||||
| #include <QString> | |||||
| class Notification | |||||
| { | |||||
| public: | |||||
| explicit Notification(); | |||||
| void show(const QString& title, const QString& content); | |||||
| void hideAll(); | |||||
| }; | |||||
| #endif // OSXNOTIFICATION_H | |||||
| @@ -0,0 +1,22 @@ | |||||
| #include "notification.h" | |||||
| #import <cocoa/Cocoa.h> | |||||
| Notification::Notification() | |||||
| { | |||||
| } | |||||
| void Notification::show(const QString &title, const QString &content) | |||||
| { | |||||
| NSUserNotification *userNotification = [[[NSUserNotification alloc] init] autorelease]; | |||||
| userNotification.title = title.toNSString(); | |||||
| userNotification.informativeText = content.toNSString(); | |||||
| NSUserNotificationCenter* center = [NSUserNotificationCenter defaultUserNotificationCenter]; | |||||
| [center deliverNotification:userNotification]; | |||||
| } | |||||
| void Notification::hideAll() | |||||
| { | |||||
| [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications]; | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| #include "osxplatform.h" | |||||
| #include <QtMac> | |||||
| OsxPlatform::OsxPlatform() | |||||
| { | |||||
| } | |||||
| void OsxPlatform::showNotify(const QString &title, const QString &content) | |||||
| { | |||||
| mNotify.show(title, content); | |||||
| } | |||||
| void OsxPlatform::hideAllNotify() | |||||
| { | |||||
| mNotify.hideAll(); | |||||
| } | |||||
| void OsxPlatform::setBadgeNumber(int number) | |||||
| { | |||||
| if (number == 0) | |||||
| QtMac::setBadgeLabelText(""); | |||||
| else | |||||
| QtMac::setBadgeLabelText(QString::number(number)); | |||||
| } | |||||
| @@ -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 | |||||
| @@ -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); | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| #ifndef PLATFORMDEPEND_H | |||||
| #define PLATFORMDEPEND_H | |||||
| #include <QString> | |||||
| class IPlatform | |||||
| { | |||||
| public: | |||||
| virtual ~IPlatform(){} | |||||
| virtual void showNotify(const QString& title, const QString& content) = 0; | |||||
| virtual void hideAllNotify() = 0; | |||||
| virtual void setBadgeNumber(int number) = 0; | |||||
| }; | |||||
| class PlatformDepend : public IPlatform | |||||
| { | |||||
| private: | |||||
| PlatformDepend(); | |||||
| ~PlatformDepend(); | |||||
| public: | |||||
| static PlatformDepend& instance(); | |||||
| public: | |||||
| void showNotify(const QString& title, const QString& content) override; | |||||
| void hideAllNotify() override; | |||||
| void setBadgeNumber(int number) override; | |||||
| private: | |||||
| IPlatform* mImpl; | |||||
| }; | |||||
| #endif // PLATFORMDEPEND_H | |||||
| @@ -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 通知中心的通知消息 | |||||
| 引用代码,请注明代码出处。 | |||||
| @@ -0,0 +1,216 @@ | |||||
| #include "recvtextedit.h" | |||||
| #include <QDate> | |||||
| #include "emoji.h" | |||||
| #include <QMouseEvent> | |||||
| RecvTextEdit::RecvTextEdit(QWidget *parent) | |||||
| :QTextEdit(parent) | |||||
| { | |||||
| setTextInteractionFlags(Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); | |||||
| } | |||||
| void RecvTextEdit::mousePressEvent(QMouseEvent *e) | |||||
| { | |||||
| mPressedAnchor = (e->button() & Qt::LeftButton) ? anchorAt(e->pos()) : ""; | |||||
| QTextEdit::mousePressEvent(e); | |||||
| } | |||||
| void RecvTextEdit::mouseReleaseEvent(QMouseEvent *e) | |||||
| { | |||||
| if (e->button() & Qt::LeftButton) | |||||
| { | |||||
| if (anchorAt(e->pos()) == mPressedAnchor && !mPressedAnchor.isEmpty()) | |||||
| parseLink(mPressedAnchor); | |||||
| } | |||||
| QTextEdit::mouseReleaseEvent(e); | |||||
| } | |||||
| void RecvTextEdit::addFellowContent(const Content *content, long long msSinceEpoch) | |||||
| { | |||||
| drawDaySeperatorIfNewDay(msSinceEpoch); | |||||
| QString hint = ""; | |||||
| if (mFellow) | |||||
| hint = mFellow->getName().c_str(); | |||||
| else | |||||
| hint = "匿名"; | |||||
| hint = hint+" <font color=gray>"+ timeStr(msSinceEpoch)+"</font>"; | |||||
| moveCursor(QTextCursor::End); | |||||
| insertHtml(hint); | |||||
| append(""); | |||||
| showContent(content, false); | |||||
| append("\n"); | |||||
| moveCursor(QTextCursor::End); | |||||
| } | |||||
| void RecvTextEdit::addMyContent(const Content *content, long long msSinceEpoch) | |||||
| { | |||||
| drawDaySeperatorIfNewDay(msSinceEpoch); | |||||
| QString hint = "我 <font color=gray>"+timeStr(msSinceEpoch)+"</font>"; | |||||
| moveCursor(QTextCursor::End); | |||||
| insertHtml(hint); | |||||
| append(""); | |||||
| showContent(content, true); | |||||
| append("\n"); | |||||
| moveCursor(QTextCursor::End); | |||||
| } | |||||
| void RecvTextEdit::setCurFellow(const Fellow *fellow) | |||||
| { | |||||
| if (mFellow) | |||||
| mDocs[mFellow] = document()->clone();//document将被清除或删除了,需clone | |||||
| auto it = mDocs.find(fellow); | |||||
| if (it != mDocs.end()) | |||||
| { | |||||
| setDocument((*it).second); | |||||
| moveCursor(QTextCursor::End); | |||||
| } | |||||
| else | |||||
| { | |||||
| clear(); | |||||
| } | |||||
| mFellow = fellow; | |||||
| } | |||||
| void RecvTextEdit::addWarning(const QString &warning) | |||||
| { | |||||
| auto align = alignment(); | |||||
| setAlignment(Qt::AlignCenter); | |||||
| auto color = textColor(); | |||||
| setTextColor(QColor(128,128,128)); | |||||
| append(warning); | |||||
| append("");//结束当前段落,否则下一行恢复对齐方式时会将刚append的内容左对齐 | |||||
| setAlignment(align); | |||||
| setTextColor(color); | |||||
| } | |||||
| const Fellow *RecvTextEdit::curFellow() | |||||
| { | |||||
| return mFellow; | |||||
| } | |||||
| void RecvTextEdit::parseLink(const QString &link) | |||||
| { | |||||
| QStringList parts = link.split("_"); | |||||
| if (parts.count()<3) | |||||
| return; | |||||
| auto packetNo = parts.at(0).toLongLong(); | |||||
| auto fileId = parts.at(1).toLongLong(); | |||||
| bool upload = parts.at(2) == "up"; | |||||
| emit navigateToFileTask(packetNo, fileId, upload); | |||||
| } | |||||
| QString RecvTextEdit::timeStr(long long msSinceEpoch) | |||||
| { | |||||
| QDateTime time; | |||||
| time.setMSecsSinceEpoch(msSinceEpoch); | |||||
| return time.toString("MM-dd HH:mm:ss"); | |||||
| } | |||||
| void RecvTextEdit::showContent(const Content *content, bool mySelf) | |||||
| { | |||||
| switch (content->type()) | |||||
| { | |||||
| case ContentType::File: | |||||
| showFile(static_cast<const FileContent*>(content), mySelf); | |||||
| break; | |||||
| case ContentType::Knock: | |||||
| showKnock(static_cast<const KnockContent*>(content), mySelf); | |||||
| break; | |||||
| case ContentType::Image: | |||||
| showImage(static_cast<const ImageContent*>(content)); | |||||
| break; | |||||
| case ContentType::Text: | |||||
| showText(static_cast<const TextContent*>(content)); | |||||
| break; | |||||
| default: | |||||
| showUnSupport(); | |||||
| break; | |||||
| } | |||||
| } | |||||
| void RecvTextEdit::showFile(const FileContent *content, bool fromMySelf) | |||||
| { | |||||
| if (content->fileType == IPMSG_FILE_REGULAR) | |||||
| { | |||||
| stringstream ss; | |||||
| ss<<"<a href="<<content->packetNo<<"_"<<content->fileId<<"_"<<(fromMySelf?"up":"down")<<">" | |||||
| <<content->filename<<"("<<content->size<<")" | |||||
| <<"</a>"; | |||||
| insertHtml(ss.str().c_str()); | |||||
| } | |||||
| else | |||||
| { | |||||
| showUnSupport("对方发来非普通文件(可能是文件夹),收不来……"); | |||||
| } | |||||
| } | |||||
| void RecvTextEdit::showImage(const ImageContent *content) | |||||
| { | |||||
| showUnSupport("对方发来图片,来图片,图片,片……额~还不支持!"); | |||||
| } | |||||
| void RecvTextEdit::showText(const TextContent *content) | |||||
| { | |||||
| insertHtml(textHtmlStr(content)); | |||||
| } | |||||
| void RecvTextEdit::showKnock(const KnockContent *content, bool mySelf) | |||||
| { | |||||
| if (mySelf) | |||||
| insertHtml("[发送了一个窗口抖动]"); | |||||
| else | |||||
| insertHtml("[发来窗口抖动]"); | |||||
| } | |||||
| void RecvTextEdit::showUnSupport(const QString& text) | |||||
| { | |||||
| QString t = text; | |||||
| if (t.isEmpty()) | |||||
| t = "对方发来尚未支持的内容,无法显示"; | |||||
| insertHtml("<font color=\"red\">"+t+"</font>"); | |||||
| } | |||||
| void RecvTextEdit::drawDaySeperatorIfNewDay(long long sinceEpoch) | |||||
| { | |||||
| QDateTime cur; | |||||
| cur.setMSecsSinceEpoch(sinceEpoch); | |||||
| if (mLastEdit > 0) | |||||
| { | |||||
| QDateTime last; | |||||
| last.setMSecsSinceEpoch(mLastEdit); | |||||
| if (last.daysTo(cur)>0) | |||||
| { | |||||
| addWarning("-----------------------------"); | |||||
| } | |||||
| } | |||||
| mLastEdit = sinceEpoch; | |||||
| } | |||||
| QString RecvTextEdit::textHtmlStr(const TextContent *content) | |||||
| { | |||||
| auto str = QString(content->text.c_str()); | |||||
| auto htmlStr = str.toHtmlEscaped(); | |||||
| htmlStr.replace("\r\n", "<br>"); | |||||
| htmlStr.replace("\r", "<br>"); | |||||
| htmlStr.replace("\n", "<br>"); | |||||
| for (auto i = 0; i < EMOJI_LEN; i++) | |||||
| { | |||||
| auto resName = QString(":/default/res/face/")+QString::number(i+1)+".gif"; | |||||
| auto emojiStr = g_emojis[i]; | |||||
| QString imgTag = "<img src=\""+resName+"\"/>"; | |||||
| htmlStr.replace(emojiStr, imgTag); | |||||
| } | |||||
| return htmlStr; | |||||
| } | |||||
| @@ -0,0 +1,52 @@ | |||||
| #ifndef RECVTEXTEDIT_H | |||||
| #define RECVTEXTEDIT_H | |||||
| #include "feiqlib/content.h" | |||||
| #include "feiqlib/fellow.h" | |||||
| #include <QObject> | |||||
| #include <unordered_map> | |||||
| #include <QTextEdit> | |||||
| using namespace std; | |||||
| class RecvTextEdit: public QTextEdit | |||||
| { | |||||
| Q_OBJECT | |||||
| public: | |||||
| RecvTextEdit(QWidget* parent = 0); | |||||
| protected: | |||||
| virtual void mousePressEvent(QMouseEvent *e) override; | |||||
| virtual void mouseReleaseEvent(QMouseEvent *e) override; | |||||
| public: | |||||
| void addFellowContent(const Content* content, long long msSinceEpoch); | |||||
| void addMyContent(const Content* content, long long msSinceEpoch); | |||||
| void setCurFellow(const Fellow* fellow); | |||||
| void addWarning(const QString& warning); | |||||
| const Fellow* curFellow(); | |||||
| signals: | |||||
| void navigateToFileTask(IdType packetNo, IdType fileId, bool upload); | |||||
| private: | |||||
| QString timeStr(long long msSinceEpoch); | |||||
| void showContent(const Content* content, bool mySelf); | |||||
| void showFile(const FileContent* content, bool fromMySelf); | |||||
| void showImage(const ImageContent* content); | |||||
| void showText(const TextContent* content); | |||||
| void showKnock(const KnockContent* content, bool mySelf); | |||||
| void showUnSupport(const QString &text = ""); | |||||
| void drawDaySeperatorIfNewDay(long long sinceEpoch); | |||||
| QString textHtmlStr(const TextContent* content); | |||||
| void parseLink(const QString& link); | |||||
| private: | |||||
| const Fellow* mFellow = nullptr; | |||||
| unordered_map<const Fellow*, QTextDocument*> mDocs; | |||||
| long long mLastEdit=0; | |||||
| QString mPressedAnchor; | |||||
| }; | |||||
| #endif // RECVTEXTEDIT_H | |||||