| @@ -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 | |||