[or-cvs] r21646: {arm} Refactoring goodness and bug fixes. change: revised curses u (in arm/trunk: . init interface util)
Damian Johnson
atagar1 at gmail.com
Mon Feb 15 01:01:35 UTC 2010
Author: atagar
Date: 2010-02-15 01:01:34 +0000 (Mon, 15 Feb 2010)
New Revision: 21646
Added:
arm/trunk/LICENSE
arm/trunk/init/
arm/trunk/init/__init__.py
arm/trunk/init/starter.py
arm/trunk/init/versionCheck.py
arm/trunk/util/
arm/trunk/util/__init__.py
arm/trunk/util/panel.py
arm/trunk/util/uiTools.py
Removed:
arm/trunk/arm.py
arm/trunk/interface/util.py
arm/trunk/versionCheck.py
Modified:
arm/trunk/ChangeLog
arm/trunk/arm
arm/trunk/interface/bandwidthMonitor.py
arm/trunk/interface/confPanel.py
arm/trunk/interface/connPanel.py
arm/trunk/interface/controller.py
arm/trunk/interface/cpuMemMonitor.py
arm/trunk/interface/descriptorPopup.py
arm/trunk/interface/fileDescriptorPopup.py
arm/trunk/interface/graphPanel.py
arm/trunk/interface/headerPanel.py
arm/trunk/interface/logPanel.py
Log:
Refactoring goodness and bug fixes.
change: revised curses utilities to further simplify interface implementations
change: substantial layout changes (adding util and init packages) and including a copy of the gpl
fix: bug with handing of DST for accounting's 'Time to reset' (patch provided by waltman)
fix: header and connection panels weren't accounting for having ORListenAddress set (caught by waltman)
fix: crashing bug when shrank too much for scrollbars to be drawn
fix: couple system commands weren't redirecting their stderr to /dev/null
Modified: arm/trunk/ChangeLog
===================================================================
--- arm/trunk/ChangeLog 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/ChangeLog 2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,6 +1,16 @@
CHANGE LOG
-2/7/10 - version 1.3.1
+2/14/10 - version 1.3.2
+Refactoring goodness and bug fixes.
+
+ * change: revised curses utilities to further simplify interfaces
+ * change: substantial layout changes (separating into util and init packages) and including a copy of the gpl
+ * fix: bug with handing of DST for accounting's 'Time to reset' (patch provided by waltman)
+ * fix: header and connection panels weren't accounting for having ORListenAddress set (caught by waltman)
+ * fix: crashing bug when shrank too much for scrollbars to be drawn
+ * fix: couple system commands weren't redirecting their stderr to /dev/null
+
+2/7/10 - version 1.3.1 (r21580)
Had enough of a siesta - getting back into development beginning with a rewrite if the starter.
* added: made authentication a little smarter, using PROTOCOLINFO to autodetect authentication type and cookie location
Added: arm/trunk/LICENSE
===================================================================
--- arm/trunk/LICENSE (rev 0)
+++ arm/trunk/LICENSE 2010-02-15 01:01:34 UTC (rev 21646)
@@ -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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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>.
Modified: arm/trunk/arm
===================================================================
--- arm/trunk/arm 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/arm 2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,8 +1,8 @@
#!/bin/sh
-python versionCheck.py
+python init/versionCheck.py
if [ $? = 0 ]
then
- python -W ignore::DeprecationWarning arm.py $*
+ python -W ignore::DeprecationWarning init/starter.py $*
fi
Deleted: arm/trunk/arm.py
===================================================================
--- arm/trunk/arm.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/arm.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,194 +0,0 @@
-#!/usr/bin/env python
-# arm.py -- Terminal status monitor for Tor relays.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-"""
-Command line application for monitoring Tor relays, providing real time status
-information. This is the starter for the application, handling and validating
-command line parameters.
-"""
-
-import sys
-import socket
-import getopt
-import getpass
-
-from TorCtl import TorCtl
-from TorCtl import TorUtil
-
-from interface import controller
-from interface import logPanel
-
-VERSION = "1.3.1"
-LAST_MODIFIED = "Feb 7, 2010"
-
-DEFAULT_CONTROL_ADDR = "127.0.0.1"
-DEFAULT_CONTROL_PORT = 9051
-DEFAULT_LOGGED_EVENTS = "N3" # tor and arm NOTICE, WARN, and ERR events
-
-OPT = "i:p:be:vh"
-OPT_EXPANDED = ["interface=", "password=", "blind", "event=", "version", "help"]
-HELP_TEXT = """Usage arm [OPTION]
-Terminal status monitor for Tor relays.
-
- -i, --interface [ADDRESS:]PORT change control interface from %s:%i
- -p, --password PASSWORD authenticate using password (skip prompt)
- -b, --blind disable connection lookups
- -e, --event EVENT_FLAGS event types in message log (default: %s)
-%s
- -v, --version provides version information
- -h, --help presents this help
-
-Example:
-arm -b -i 1643 hide connection data, attaching to control port 1643
-arm -e=we -p=nemesis use password 'nemesis' with 'WARN'/'ERR' events
-""" % (DEFAULT_CONTROL_ADDR, DEFAULT_CONTROL_PORT, DEFAULT_LOGGED_EVENTS, logPanel.EVENT_LISTING)
-
-def isValidIpAddr(ipStr):
- """
- Returns true if input is a valid IPv4 address, false otherwise.
- """
-
- for i in range(4):
- if i < 3:
- divIndex = ipStr.find(".")
- if divIndex == -1: return False # expected a period to be valid
- octetStr = ipStr[:divIndex]
- ipStr = ipStr[divIndex + 1:]
- else:
- octetStr = ipStr
-
- try:
- octet = int(octetStr)
- if not octet >= 0 or not octet <= 255: return False
- except ValueError:
- # address value isn't an integer
- return False
-
- return True
-
-if __name__ == '__main__':
- controlAddr = DEFAULT_CONTROL_ADDR # controller interface IP address
- controlPort = DEFAULT_CONTROL_PORT # controller interface port
- authPassword = "" # authentication password (prompts if unset and needed)
- isBlindMode = False # allows connection lookups to be disabled
- loggedEvents = DEFAULT_LOGGED_EVENTS # flags for event types in message log
-
- # parses user input, noting any issues
- try:
- opts, args = getopt.getopt(sys.argv[1:], OPT, OPT_EXPANDED)
- except getopt.GetoptError, exc:
- print str(exc) + " (for usage provide --help)"
- sys.exit()
-
- for opt, arg in opts:
- if opt in ("-i", "--interface"):
- # defines control interface address/port
- try:
- divIndex = arg.find(":")
-
- if divIndex == -1:
- controlPort = int(arg)
- else:
- controlAddr = arg[0:divIndex]
- controlPort = int(arg[divIndex + 1:])
-
- # validates that input is a valid ip address and port
- if divIndex != -1 and not isValidIpAddr(controlAddr):
- raise AssertionError("'%s' isn't a valid IP address" % controlAddr)
- elif controlPort < 0 or controlPort > 65535:
- raise AssertionError("'%s' isn't a valid port number (ports range 0-65535)" % controlPort)
- except ValueError:
- print "'%s' isn't a valid port number" % arg
- sys.exit()
- except AssertionError, exc:
- print exc
- sys.exit()
- elif opt in ("-p", "--password"): authPassword = arg # sets authentication password
- elif opt in ("-b", "--blind"): isBlindMode = True # prevents connection lookups
- elif opt in ("-e", "--event"): loggedEvents = arg # set event flags
- elif opt in ("-v", "--version"):
- print "arm version %s (released %s)\n" % (VERSION, LAST_MODIFIED)
- sys.exit()
- elif opt in ("-h", "--help"):
- print HELP_TEXT
- sys.exit()
-
- # validates and expands log event flags
- try:
- expandedEvents = logPanel.expandEvents(loggedEvents)
- except ValueError, exc:
- for flag in str(exc):
- print "Unrecognized event flag: %s" % flag
- sys.exit()
-
- # temporarily disables TorCtl logging to prevent issues from going to stdout while starting
- TorUtil.loglevel = "NONE"
-
- # attempts to open a socket to the tor server
- try:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect((controlAddr, controlPort))
- conn = TorCtl.Connection(s)
- except socket.error, exc:
- if str(exc) == "[Errno 111] Connection refused":
- # most common case - tor control port isn't available
- print "Connection refused. Is the ControlPort enabled?"
- else:
- # less common issue - provide exc message
- print "Failed to establish socket: %s" % exc
-
- sys.exit()
-
- # check PROTOCOLINFO for authentication type
- try:
- authInfo = conn.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
- except TorCtl.ErrorReply, exc:
- print "Unable to query PROTOCOLINFO for authentication type: %s" % exc
- sys.exit()
-
- try:
- if authInfo.startswith("AUTH METHODS=NULL"):
- # no authentication required
- conn.authenticate("")
- elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
- # password authentication, promts for password if it wasn't provided
- if not authPassword: authPassword = getpass.getpass()
- conn.authenticate(authPassword)
- elif authInfo.startswith("AUTH METHODS=COOKIE"):
- # cookie authtication, parses path to authentication cookie
- start = authInfo.find("COOKIEFILE=\"") + 12
- end = authInfo[start:].find("\"")
- authCookiePath = authInfo[start:start + end]
-
- try:
- authCookie = open(authCookiePath, "r")
- conn.authenticate_cookie(authCookie)
- authCookie.close()
- except IOError, exc:
- # cleaner message for common errors
- issue = None
- if str(exc).startswith("[Errno 13] Permission denied"): issue = "permission denied"
- elif str(exc).startswith("[Errno 2] No such file or directory"): issue = "file doesn't exist"
-
- # if problem's recognized give concise message, otherwise print exception string
- if issue: print "Failed to read authentication cookie (%s): %s" % (issue, authCookiePath)
- else: print "Failed to read authentication cookie: %s" % exc
-
- sys.exit()
- else:
- # authentication type unrecognized (probably a new addition to the controlSpec)
- print "Unrecognized authentication type: %s" % authInfo
- sys.exit()
- except TorCtl.ErrorReply, exc:
- # authentication failed
- issue = str(exc)
- if str(exc).startswith("515 Authentication failed: Password did not match"): issue = "password incorrect"
- if str(exc) == "515 Authentication failed: Wrong length on authentication cookie.": issue = "cookie value incorrect"
-
- print "Unable to authenticate: %s" % issue
- sys.exit()
-
- controller.startTorMonitor(conn, expandedEvents, isBlindMode)
- conn.close()
-
Added: arm/trunk/init/__init__.py
===================================================================
--- arm/trunk/init/__init__.py (rev 0)
+++ arm/trunk/init/__init__.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,6 @@
+"""
+Scripts involved in validating user input, system state, and initializing arm.
+"""
+
+__all__ = ["starter", "versionCheck"]
+
Copied: arm/trunk/init/starter.py (from rev 21580, arm/trunk/arm.py)
===================================================================
--- arm/trunk/init/starter.py (rev 0)
+++ arm/trunk/init/starter.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+
+"""
+Command line application for monitoring Tor relays, providing real time status
+information. This is the starter for the application, handling and validating
+command line parameters.
+"""
+
+import sys
+import socket
+import getopt
+import getpass
+
+# includes parent directory rather than init in path (so sibling modules are included)
+sys.path[0] = sys.path[0][:-5]
+
+from TorCtl import TorCtl, TorUtil
+from interface import controller, logPanel
+
+VERSION = "1.3.2"
+LAST_MODIFIED = "Feb 14, 2010"
+
+DEFAULT_CONTROL_ADDR = "127.0.0.1"
+DEFAULT_CONTROL_PORT = 9051
+DEFAULT_LOGGED_EVENTS = "N3" # tor and arm NOTICE, WARN, and ERR events
+
+OPT = "i:p:be:vh"
+OPT_EXPANDED = ["interface=", "password=", "blind", "event=", "version", "help"]
+HELP_MSG = """Usage arm [OPTION]
+Terminal status monitor for Tor relays.
+
+ -i, --interface [ADDRESS:]PORT change control interface from %s:%i
+ -p, --password PASSWORD authenticate using password (skip prompt)
+ -b, --blind disable connection lookups
+ -e, --event EVENT_FLAGS event types in message log (default: %s)
+%s
+ -v, --version provides version information
+ -h, --help presents this help
+
+Example:
+arm -b -i 1643 hide connection data, attaching to control port 1643
+arm -e=we -p=nemesis use password 'nemesis' with 'WARN'/'ERR' events
+""" % (DEFAULT_CONTROL_ADDR, DEFAULT_CONTROL_PORT, DEFAULT_LOGGED_EVENTS, logPanel.EVENT_LISTING)
+
+def isValidIpAddr(ipStr):
+ """
+ Returns true if input is a valid IPv4 address, false otherwise.
+ """
+
+ for i in range(4):
+ if i < 3:
+ divIndex = ipStr.find(".")
+ if divIndex == -1: return False # expected a period to be valid
+ octetStr = ipStr[:divIndex]
+ ipStr = ipStr[divIndex + 1:]
+ else:
+ octetStr = ipStr
+
+ try:
+ octet = int(octetStr)
+ if not octet >= 0 or not octet <= 255: return False
+ except ValueError:
+ # address value isn't an integer
+ return False
+
+ return True
+
+if __name__ == '__main__':
+ controlAddr = DEFAULT_CONTROL_ADDR # controller interface IP address
+ controlPort = DEFAULT_CONTROL_PORT # controller interface port
+ authPassword = "" # authentication password (prompts if unset and needed)
+ isBlindMode = False # allows connection lookups to be disabled
+ loggedEvents = DEFAULT_LOGGED_EVENTS # flags for event types in message log
+
+ # parses user input, noting any issues
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], OPT, OPT_EXPANDED)
+ except getopt.GetoptError, exc:
+ print str(exc) + " (for usage provide --help)"
+ sys.exit()
+
+ for opt, arg in opts:
+ if opt in ("-i", "--interface"):
+ # defines control interface address/port
+ try:
+ divIndex = arg.find(":")
+
+ if divIndex == -1:
+ controlPort = int(arg)
+ else:
+ controlAddr = arg[0:divIndex]
+ controlPort = int(arg[divIndex + 1:])
+
+ # validates that input is a valid ip address and port
+ if divIndex != -1 and not isValidIpAddr(controlAddr):
+ raise AssertionError("'%s' isn't a valid IP address" % controlAddr)
+ elif controlPort < 0 or controlPort > 65535:
+ raise AssertionError("'%s' isn't a valid port number (ports range 0-65535)" % controlPort)
+ except ValueError:
+ print "'%s' isn't a valid port number" % arg
+ sys.exit()
+ except AssertionError, exc:
+ print exc
+ sys.exit()
+ elif opt in ("-p", "--password"): authPassword = arg # sets authentication password
+ elif opt in ("-b", "--blind"): isBlindMode = True # prevents connection lookups
+ elif opt in ("-e", "--event"): loggedEvents = arg # set event flags
+ elif opt in ("-v", "--version"):
+ print "arm version %s (released %s)\n" % (VERSION, LAST_MODIFIED)
+ sys.exit()
+ elif opt in ("-h", "--help"):
+ print HELP_MSG
+ sys.exit()
+
+ # validates and expands log event flags
+ try:
+ expandedEvents = logPanel.expandEvents(loggedEvents)
+ except ValueError, exc:
+ for flag in str(exc):
+ print "Unrecognized event flag: %s" % flag
+ sys.exit()
+
+ # temporarily disables TorCtl logging to prevent issues from going to stdout while starting
+ TorUtil.loglevel = "NONE"
+
+ # attempts to open a socket to the tor server
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((controlAddr, controlPort))
+ conn = TorCtl.Connection(s)
+ except socket.error, exc:
+ if str(exc) == "[Errno 111] Connection refused":
+ # most common case - tor control port isn't available
+ print "Connection refused. Is the ControlPort enabled?"
+ else:
+ # less common issue - provide exc message
+ print "Failed to establish socket: %s" % exc
+
+ sys.exit()
+
+ # check PROTOCOLINFO for authentication type
+ try:
+ authInfo = conn.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
+ except TorCtl.ErrorReply, exc:
+ print "Unable to query PROTOCOLINFO for authentication type: %s" % exc
+ sys.exit()
+
+ try:
+ if authInfo.startswith("AUTH METHODS=NULL"):
+ # no authentication required
+ conn.authenticate("")
+ elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
+ # password authentication, promts for password if it wasn't provided
+ if not authPassword: authPassword = getpass.getpass()
+ conn.authenticate(authPassword)
+ elif authInfo.startswith("AUTH METHODS=COOKIE"):
+ # cookie authtication, parses path to authentication cookie
+ start = authInfo.find("COOKIEFILE=\"") + 12
+ end = authInfo[start:].find("\"")
+ authCookiePath = authInfo[start:start + end]
+
+ try:
+ authCookie = open(authCookiePath, "r")
+ conn.authenticate_cookie(authCookie)
+ authCookie.close()
+ except IOError, exc:
+ # cleaner message for common errors
+ issue = None
+ if str(exc).startswith("[Errno 13] Permission denied"): issue = "permission denied"
+ elif str(exc).startswith("[Errno 2] No such file or directory"): issue = "file doesn't exist"
+
+ # if problem's recognized give concise message, otherwise print exception string
+ if issue: print "Failed to read authentication cookie (%s): %s" % (issue, authCookiePath)
+ else: print "Failed to read authentication cookie: %s" % exc
+
+ sys.exit()
+ else:
+ # authentication type unrecognized (probably a new addition to the controlSpec)
+ print "Unrecognized authentication type: %s" % authInfo
+ sys.exit()
+ except TorCtl.ErrorReply, exc:
+ # authentication failed
+ issue = str(exc)
+ if str(exc).startswith("515 Authentication failed: Password did not match"): issue = "password incorrect"
+ if str(exc) == "515 Authentication failed: Wrong length on authentication cookie.": issue = "cookie value incorrect"
+
+ print "Unable to authenticate: %s" % issue
+ sys.exit()
+
+ controller.startTorMonitor(conn, expandedEvents, isBlindMode)
+ conn.close()
+
Copied: arm/trunk/init/versionCheck.py (from rev 21469, arm/trunk/versionCheck.py)
===================================================================
--- arm/trunk/init/versionCheck.py (rev 0)
+++ arm/trunk/init/versionCheck.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,17 @@
+"""
+Provides a warning and error code if python version isn't compatible.
+"""
+
+import sys
+
+if __name__ == '__main__':
+ majorVersion = sys.version_info[0]
+ minorVersion = sys.version_info[1]
+
+ if majorVersion > 2:
+ print("arm isn't compatible beyond the python 2.x series\n")
+ sys.exit(1)
+ elif majorVersion < 2 or minorVersion < 5:
+ print("arm requires python version 2.5 or greater\n")
+ sys.exit(1)
+
Modified: arm/trunk/interface/bandwidthMonitor.py
===================================================================
--- arm/trunk/interface/bandwidthMonitor.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/bandwidthMonitor.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -7,7 +7,7 @@
from TorCtl import TorCtl
import graphPanel
-import util
+from util import uiTools
DL_COLOR = "green" # download section color
UL_COLOR = "cyan" # upload section color
@@ -47,8 +47,8 @@
bwStats = self.conn.get_option(['BandwidthRate', 'BandwidthBurst'])
relayStats = self.conn.get_option(['RelayBandwidthRate', 'RelayBandwidthBurst'])
- self.bwRate = util.getSizeLabel(int(bwStats[0][1] if relayStats[0][1] == "0" else relayStats[0][1]))
- self.bwBurst = util.getSizeLabel(int(bwStats[1][1] if relayStats[1][1] == "0" else relayStats[1][1]))
+ self.bwRate = uiTools.getSizeLabel(int(bwStats[0][1] if relayStats[0][1] == "0" else relayStats[0][1]))
+ self.bwBurst = uiTools.getSizeLabel(int(bwStats[1][1] if relayStats[1][1] == "0" else relayStats[1][1]))
except (ValueError, socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
pass # keep old values
@@ -59,7 +59,7 @@
def bandwidth_event(self, event):
self._processEvent(event.read / 1024.0, event.written / 1024.0)
- def redraw(self, panel):
+ def draw(self, panel):
# if display is narrow, overwrites x-axis labels with avg / total stats
if panel.maxX <= COLLAPSE_WIDTH:
# clears line
@@ -69,8 +69,8 @@
primaryFooter = "%s, %s" % (self._getAvgLabel(True), self._getTotalLabel(True))
secondaryFooter = "%s, %s" % (self._getAvgLabel(False), self._getTotalLabel(False))
- panel.addstr(8, 1, primaryFooter, util.getColor(self.primaryColor))
- panel.addstr(8, graphCol + 6, secondaryFooter, util.getColor(self.secondaryColor))
+ panel.addstr(8, 1, primaryFooter, uiTools.getColor(self.primaryColor))
+ panel.addstr(8, graphCol + 6, secondaryFooter, uiTools.getColor(self.secondaryColor))
# provides accounting stats if enabled
if self.isAccounting:
@@ -84,8 +84,8 @@
panel.addfstr(10, 0, "<b>Accounting (<%s>%s</%s>)" % (hibernateColor, status, hibernateColor))
panel.addstr(10, 35, "Time to reset: %s" % self.accountingInfo["resetTime"])
- panel.addstr(11, 2, "%s / %s" % (self.accountingInfo["read"], self.accountingInfo["readLimit"]), util.getColor(self.primaryColor))
- panel.addstr(11, 37, "%s / %s" % (self.accountingInfo["written"], self.accountingInfo["writtenLimit"]), util.getColor(self.secondaryColor))
+ panel.addstr(11, 2, "%s / %s" % (self.accountingInfo["read"], self.accountingInfo["readLimit"]), uiTools.getColor(self.primaryColor))
+ panel.addstr(11, 37, "%s / %s" % (self.accountingInfo["written"], self.accountingInfo["writtenLimit"]), uiTools.getColor(self.secondaryColor))
else:
panel.addfstr(10, 0, "<b>Accounting:</b> Shutting Down...")
@@ -108,7 +108,7 @@
stats[1] = "- %s" % self._getAvgLabel(isPrimary)
stats[2] = ", %s" % self._getTotalLabel(isPrimary)
- stats[0] = "%-14s" % ("%s/sec" % util.getSizeLabel((self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1))
+ stats[0] = "%-14s" % ("%s/sec" % uiTools.getSizeLabel((self.lastPrimary if isPrimary else self.lastSecondary) * 1024, 1))
labeling = graphType + " (" + "".join(stats).strip() + "):"
while (len(labeling) >= width):
@@ -123,11 +123,11 @@
def _getAvgLabel(self, isPrimary):
total = self.primaryTotal if isPrimary else self.secondaryTotal
- return "avg: %s/sec" % util.getSizeLabel((total / max(1, self.tick)) * 1024, 1)
+ return "avg: %s/sec" % uiTools.getSizeLabel((total / max(1, self.tick)) * 1024, 1)
def _getTotalLabel(self, isPrimary):
total = self.primaryTotal if isPrimary else self.secondaryTotal
- return "total: %s" % util.getSizeLabel(total * 1024, 1)
+ return "total: %s" % uiTools.getSizeLabel(total * 1024, 1)
def _updateAccountingInfo(self):
"""
@@ -143,8 +143,11 @@
accountingParams = self.conn.get_info(["accounting/hibernating", "accounting/bytes", "accounting/bytes-left", "accounting/interval-end"])
self.accountingInfo["status"] = accountingParams["accounting/hibernating"]
- # altzone subtraction converts from gmt to local with respect to DST
- sec = time.mktime(time.strptime(accountingParams["accounting/interval-end"], "%Y-%m-%d %H:%M:%S")) - time.time() - time.altzone
+ # converts from gmt to local with respect to DST
+ if time.localtime()[8]: tz_offset = time.altzone
+ else: tz_offset = time.timezone
+
+ sec = time.mktime(time.strptime(accountingParams["accounting/interval-end"], "%Y-%m-%d %H:%M:%S")) - time.time() - tz_offset
resetHours = sec / 3600
sec %= 3600
resetMin = sec / 60
@@ -156,10 +159,10 @@
readLeft = int(accountingParams["accounting/bytes-left"].split(" ")[0])
writtenLeft = int(accountingParams["accounting/bytes-left"].split(" ")[1])
- self.accountingInfo["read"] = util.getSizeLabel(read)
- self.accountingInfo["written"] = util.getSizeLabel(written)
- self.accountingInfo["readLimit"] = util.getSizeLabel(read + readLeft)
- self.accountingInfo["writtenLimit"] = util.getSizeLabel(written + writtenLeft)
+ self.accountingInfo["read"] = uiTools.getSizeLabel(read)
+ self.accountingInfo["written"] = uiTools.getSizeLabel(written)
+ self.accountingInfo["readLimit"] = uiTools.getSizeLabel(read + readLeft)
+ self.accountingInfo["writtenLimit"] = uiTools.getSizeLabel(written + writtenLeft)
except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
self.accountingInfo = None
Modified: arm/trunk/interface/confPanel.py
===================================================================
--- arm/trunk/interface/confPanel.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/confPanel.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -5,7 +5,7 @@
import math
import curses
-import util
+from util import panel, uiTools
# torrc parameters that can be defined multiple times without overwriting
# from src/or/config.c (entries with LINELIST or LINELIST_S)
@@ -24,13 +24,13 @@
LABEL_DAY = ["day", "days"]
LABEL_WEEK = ["week", "weeks"]
-class ConfPanel(util.Panel):
+class ConfPanel(panel.Panel):
"""
Presents torrc with syntax highlighting in a scroll-able area.
"""
- def __init__(self, lock, confLocation, conn, logPanel):
- util.Panel.__init__(self, lock, -1)
+ def __init__(self, confLocation, conn, logPanel):
+ panel.Panel.__init__(self, -1)
self.confLocation = confLocation
self.showLineNum = True
self.stripComments = False
@@ -133,64 +133,56 @@
self.scroll = 0
self.redraw()
- def redraw(self):
- if self.win:
- if not self.lock.acquire(False): return
- try:
- self.clear()
- self.addstr(0, 0, "Tor Config (%s):" % self.confLocation, util.LABEL_ATTR)
+ def draw(self):
+ self.addstr(0, 0, "Tor Config (%s):" % self.confLocation, uiTools.LABEL_ATTR)
+
+ pageHeight = self.maxY - 1
+ numFieldWidth = int(math.log10(len(self.confContents))) + 1
+ lineNum, displayLineNum = self.scroll + 1, 1 # lineNum corresponds to torrc, displayLineNum concerns what's presented
+
+ for i in range(self.scroll, min(len(self.confContents), self.scroll + pageHeight)):
+ lineText = self.confContents[i].strip()
+ skipLine = False # true if we're not presenting line due to stripping
+
+ command, argument, correction, comment = "", "", "", ""
+ commandColor, argumentColor, correctionColor, commentColor = "green", "cyan", "cyan", "white"
+
+ if not lineText:
+ # no text
+ if self.stripComments: skipLine = True
+ elif lineText[0] == "#":
+ # whole line is commented out
+ comment = lineText
+ if self.stripComments: skipLine = True
+ else:
+ # parse out command, argument, and possible comment
+ ctlEnd = lineText.find(" ") # end of command
+ argEnd = lineText.find("#") # end of argument (start of comment or end of line)
+ if argEnd == -1: argEnd = len(lineText)
- pageHeight = self.maxY - 1
- numFieldWidth = int(math.log10(len(self.confContents))) + 1
- lineNum, displayLineNum = self.scroll + 1, 1 # lineNum corresponds to torrc, displayLineNum concerns what's presented
+ command, argument, comment = lineText[:ctlEnd], lineText[ctlEnd:argEnd], lineText[argEnd:]
- for i in range(self.scroll, min(len(self.confContents), self.scroll + pageHeight)):
- lineText = self.confContents[i].strip()
- skipLine = False # true if we're not presenting line due to stripping
-
- command, argument, correction, comment = "", "", "", ""
- commandColor, argumentColor, correctionColor, commentColor = "green", "cyan", "cyan", "white"
-
- if not lineText:
- # no text
- if self.stripComments: skipLine = True
- elif lineText[0] == "#":
- # whole line is commented out
- comment = lineText
- if self.stripComments: skipLine = True
- else:
- # parse out command, argument, and possible comment
- ctlEnd = lineText.find(" ") # end of command
- argEnd = lineText.find("#") # end of argument (start of comment or end of line)
- if argEnd == -1: argEnd = len(lineText)
-
- command, argument, comment = lineText[:ctlEnd], lineText[ctlEnd:argEnd], lineText[argEnd:]
-
- # changes presentation if value's incorrect or irrelevant
- if lineNum in self.corrections.keys():
- argumentColor = "red"
- correction = " (%s)" % self.corrections[lineNum]
- elif lineNum in self.irrelevantLines:
- commandColor = "blue"
- argumentColor = "blue"
-
- if not skipLine:
- numOffset = 0 # offset for line numbering
- if self.showLineNum:
- self.addstr(displayLineNum, 0, ("%%%ii" % numFieldWidth) % lineNum, curses.A_BOLD | util.getColor("yellow"))
- numOffset = numFieldWidth + 1
-
- xLoc = 0
- displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, command, curses.A_BOLD | util.getColor(commandColor), numOffset)
- displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, argument, curses.A_BOLD | util.getColor(argumentColor), numOffset)
- displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, correction, curses.A_BOLD | util.getColor(correctionColor), numOffset)
- displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, comment, util.getColor(commentColor), numOffset)
-
- displayLineNum += 1
-
- lineNum += 1
-
- self.refresh()
- finally:
- self.lock.release()
+ # changes presentation if value's incorrect or irrelevant
+ if lineNum in self.corrections.keys():
+ argumentColor = "red"
+ correction = " (%s)" % self.corrections[lineNum]
+ elif lineNum in self.irrelevantLines:
+ commandColor = "blue"
+ argumentColor = "blue"
+
+ if not skipLine:
+ numOffset = 0 # offset for line numbering
+ if self.showLineNum:
+ self.addstr(displayLineNum, 0, ("%%%ii" % numFieldWidth) % lineNum, curses.A_BOLD | uiTools.getColor("yellow"))
+ numOffset = numFieldWidth + 1
+
+ xLoc = 0
+ displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, command, curses.A_BOLD | uiTools.getColor(commandColor), numOffset)
+ displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, argument, curses.A_BOLD | uiTools.getColor(argumentColor), numOffset)
+ displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, correction, curses.A_BOLD | uiTools.getColor(correctionColor), numOffset)
+ displayLineNum, xLoc = self.addstr_wrap(displayLineNum, xLoc, comment, uiTools.getColor(commentColor), numOffset)
+
+ displayLineNum += 1
+
+ lineNum += 1
Modified: arm/trunk/interface/connPanel.py
===================================================================
--- arm/trunk/interface/connPanel.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/connPanel.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -9,7 +9,7 @@
from TorCtl import TorCtl
import hostnameResolver
-import util
+from util import panel, uiTools
# directory servers (IP, port) for tor version 0.2.2.1-alpha-dev
DIR_SERVERS = [("86.59.21.38", "80"), # tor26
@@ -92,14 +92,14 @@
if sortLabel == label: return type
raise ValueError(sortLabel)
-class ConnPanel(TorCtl.PostEventListener, util.Panel):
+class ConnPanel(TorCtl.PostEventListener, panel.Panel):
"""
Lists netstat provided network data of tor.
"""
- def __init__(self, lock, conn, connResolver, logger):
+ def __init__(self, conn, connResolver, logger):
TorCtl.PostEventListener.__init__(self)
- util.Panel.__init__(self, lock, -1)
+ panel.Panel.__init__(self, -1)
self.scroll = 0
self.conn = conn # tor connection for querrying country codes
self.connResolver = connResolver # thread performing netstat queries
@@ -138,6 +138,7 @@
self.familyResolutions = {}
self.nickname = ""
+ self.listenPort = "0" # port used to identify inbound/outbound connections (from ORListenAddress if defined, otherwise ORPort)
self.orPort = "0"
self.dirPort = "0"
self.controlPort = "0"
@@ -161,17 +162,23 @@
try:
self.nickname = self.conn.get_option("Nickname")[0][1]
- # uses ports to identify type of connections
self.orPort = self.conn.get_option("ORPort")[0][1]
self.dirPort = self.conn.get_option("DirPort")[0][1]
self.controlPort = self.conn.get_option("ControlPort")[0][1]
+ # uses ports to identify type of connections (ORListenAddress port overwrites ORPort if set)
+ listenAddr = self.conn.get_option("ORListenAddress")[0][1]
+ if listenAddr and ":" in listenAddr:
+ self.listenPort = listenAddr[listenAddr.find(":") + 1:]
+ else: self.listenPort = self.orPort
+
# entry is None if not set, otherwise of the format "$<fingerprint>,$<fingerprint>"
familyEntry = self.conn.get_option("MyFamily")[0][1]
if familyEntry: self.family = [entry[1:] for entry in familyEntry.split(",")]
else: self.family = []
except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed):
self.nickname = ""
+ self.listenPort = None
self.orPort = "0"
self.dirPort = "0"
self.controlPort = "0"
@@ -262,7 +269,7 @@
localIP, foreignIP = local[:local.find(":")], foreign[:foreign.find(":")]
localPort, foreignPort = local[len(localIP) + 1:], foreign[len(foreignIP) + 1:]
- if localPort in (self.orPort, self.dirPort):
+ if localPort in (self.listenPort, self.dirPort):
type = "inbound"
connectionCountTmp[0] += 1
elif localPort == self.controlPort:
@@ -341,7 +348,7 @@
familyResolutionsTmp[(familyAddress, familyPort)] = fingerprint
connectionsTmp.append(("family", familyAddress, familyPort, familyAddress, familyPort, familyCountryCode, connTime))
except (socket.error, TorCtl.ErrorReply):
- # use dummy entry for sorting - the redraw function notes that entries are unknown
+ # use dummy entry for sorting - the draw function notes that entries are unknown
portIdentifier = str(65536 + tmpCounter)
familyResolutionsTmp[("256.255.255.255", portIdentifier)] = fingerprint
connectionsTmp.append(("family", "256.255.255.255", portIdentifier, "256.255.255.255", portIdentifier, "??", time.time()))
@@ -361,7 +368,7 @@
self.connectionCount = connectionCountTmp
self.familyResolutions = familyResolutionsTmp
- # hostnames are sorted at redraw - otherwise now's a good time
+ # hostnames are sorted at draw - otherwise now's a good time
if self.listingType != LIST_HOSTNAME: self.sortConnections()
self.lastNetstatResults = results
finally:
@@ -407,188 +414,182 @@
else: return # skip following redraw
self.redraw()
- def redraw(self):
- if self.win:
- if not self.lock.acquire(False): return
- self.connectionsLock.acquire()
- try:
- # hostnames frequently get updated so frequent sorting needed
- if self.listingType == LIST_HOSTNAME: self.sortConnections()
+ def draw(self):
+ self.connectionsLock.acquire()
+ try:
+ # hostnames frequently get updated so frequent sorting needed
+ if self.listingType == LIST_HOSTNAME: self.sortConnections()
+
+ if self.showLabel:
+ # notes the number of connections for each type if above zero
+ countLabel = ""
+ for i in range(len(self.connectionCount)):
+ if self.connectionCount[i] > 0: countLabel += "%i %s, " % (self.connectionCount[i], CONN_COUNT_LABELS[i])
+ if countLabel: countLabel = " (%s)" % countLabel[:-2] # strips ending ", " and encases in parentheses
+ self.addstr(0, 0, "Connections%s:" % countLabel, uiTools.LABEL_ATTR)
+
+ if self.connections:
+ listingHeight = self.maxY - 1
+ currentTime = time.time() if not self.isPaused else self.pauseTime
- self.clear()
- if self.showLabel:
- # notes the number of connections for each type if above zero
- countLabel = ""
- for i in range(len(self.connectionCount)):
- if self.connectionCount[i] > 0: countLabel += "%i %s, " % (self.connectionCount[i], CONN_COUNT_LABELS[i])
- if countLabel: countLabel = " (%s)" % countLabel[:-2] # strips ending ", " and encases in parentheses
- self.addstr(0, 0, "Connections%s:" % countLabel, util.LABEL_ATTR)
+ if self.showingDetails:
+ listingHeight -= 8
+ isScrollBarVisible = len(self.connections) > self.maxY - 9
+ if self.maxX > 80: self.win.hline(8, 80, curses.ACS_HLINE, self.maxX - 81)
+ else:
+ isScrollBarVisible = len(self.connections) > self.maxY - 1
+ xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
- if self.connections:
- listingHeight = self.maxY - 1
- currentTime = time.time() if not self.isPaused else self.pauseTime
+ # ensure cursor location and scroll top are within bounds
+ self.cursorLoc = max(min(self.cursorLoc, len(self.connections) - 1), 0)
+ self.scroll = max(min(self.scroll, len(self.connections) - listingHeight), 0)
+
+ if self.isCursorEnabled:
+ # update cursorLoc with selection (or vice versa if selection not found)
+ if self.cursorSelection not in self.connections:
+ self.cursorSelection = self.connections[self.cursorLoc]
+ else: self.cursorLoc = self.connections.index(self.cursorSelection)
- if self.showingDetails:
- listingHeight -= 8
- isScrollBarVisible = len(self.connections) > self.maxY - 9
- if self.maxX > 80: self.win.hline(8, 80, curses.ACS_HLINE, self.maxX - 81)
- else:
- isScrollBarVisible = len(self.connections) > self.maxY - 1
- xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
-
- # ensure cursor location and scroll top are within bounds
- self.cursorLoc = max(min(self.cursorLoc, len(self.connections) - 1), 0)
- self.scroll = max(min(self.scroll, len(self.connections) - listingHeight), 0)
-
- if self.isCursorEnabled:
- # update cursorLoc with selection (or vice versa if selection not found)
- if self.cursorSelection not in self.connections:
- self.cursorSelection = self.connections[self.cursorLoc]
- else: self.cursorLoc = self.connections.index(self.cursorSelection)
+ # shift scroll if necessary for cursor to be visible
+ if self.cursorLoc < self.scroll: self.scroll = self.cursorLoc
+ elif self.cursorLoc - listingHeight + 1 > self.scroll: self.scroll = self.cursorLoc - listingHeight + 1
+
+ lineNum = (-1 * self.scroll) + 1
+ for entry in self.connections:
+ if lineNum >= 1:
+ type = entry[CONN_TYPE]
+ color = TYPE_COLORS[type]
- # shift scroll if necessary for cursor to be visible
- if self.cursorLoc < self.scroll: self.scroll = self.cursorLoc
- elif self.cursorLoc - listingHeight + 1 > self.scroll: self.scroll = self.cursorLoc - listingHeight + 1
-
- lineNum = (-1 * self.scroll) + 1
- for entry in self.connections:
- if lineNum >= 1:
- type = entry[CONN_TYPE]
- color = TYPE_COLORS[type]
+ # adjustments to measurements for 'xOffset' are to account for scroll bar
+ if self.listingType == LIST_IP:
+ # base data requires 73 characters
+ src = "%s:%s" % (entry[CONN_L_IP], entry[CONN_L_PORT])
+ dst = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
+ src, dst = "%-21s" % src, "%-26s" % dst
- # adjustments to measurements for 'xOffset' are to account for scroll bar
- if self.listingType == LIST_IP:
- # base data requires 73 characters
- src = "%s:%s" % (entry[CONN_L_IP], entry[CONN_L_PORT])
- dst = "%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY])
- src, dst = "%-21s" % src, "%-26s" % dst
+ etc = ""
+ if self.maxX > 115 + xOffset:
+ # show fingerprint (column width: 42 characters)
+ etc += "%-40s " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
- etc = ""
- if self.maxX > 115 + xOffset:
- # show fingerprint (column width: 42 characters)
- etc += "%-40s " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-
- if self.maxX > 127 + xOffset:
- # show nickname (column width: remainder)
- nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
- nicknameSpace = self.maxX - 118 - xOffset
-
- # truncates if too long
- if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
-
- etc += ("%%-%is " % nicknameSpace) % nickname
- elif self.listingType == LIST_HOSTNAME:
- # base data requires 80 characters
- src = "localhost:%-5s" % entry[CONN_L_PORT]
+ if self.maxX > 127 + xOffset:
+ # show nickname (column width: remainder)
+ nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+ nicknameSpace = self.maxX - 118 - xOffset
- # space available for foreign hostname (stretched to claim any free space)
- foreignHostnameSpace = self.maxX - 42 - xOffset
+ # truncates if too long
+ if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
- etc = ""
- if self.maxX > 102 + xOffset:
- # shows ip/locale (column width: 22 characters)
- foreignHostnameSpace -= 22
- etc += "%-20s " % ("%s %s" % (entry[CONN_F_IP], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
-
- if self.maxX > 134 + xOffset:
- # show fingerprint (column width: 42 characters)
- foreignHostnameSpace -= 42
- etc += "%-40s " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-
- if self.maxX > 151 + xOffset:
- # show nickname (column width: min 17 characters, uses half of the remainder)
- nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
- nicknameSpace = 15 + (self.maxX - xOffset - 151) / 2
- foreignHostnameSpace -= (nicknameSpace + 2)
-
- if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
- etc += ("%%-%is " % nicknameSpace) % nickname
-
- hostname = self.resolver.resolve(entry[CONN_F_IP])
-
- # truncates long hostnames
- portDigits = len(str(entry[CONN_F_PORT]))
- if hostname and (len(hostname) + portDigits) > foreignHostnameSpace - 1:
- hostname = hostname[:(foreignHostnameSpace - portDigits - 4)] + "..."
-
- dst = "%s:%s" % (hostname if hostname else entry[CONN_F_IP], entry[CONN_F_PORT])
- dst = ("%%-%is" % foreignHostnameSpace) % dst
- elif self.listingType == LIST_FINGERPRINT:
- # base data requires 75 characters
- src = "localhost"
- if entry[CONN_TYPE] == "control": dst = "localhost"
- else: dst = self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
- dst = "%-40s" % dst
-
- etc = ""
- if self.maxX > 92 + xOffset:
- # show nickname (column width: min 17 characters, uses remainder if extra room's available)
- nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
- nicknameSpace = self.maxX - 78 - xOffset if self.maxX < 126 else self.maxX - 106 - xOffset
- if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
- etc += ("%%-%is " % nicknameSpace) % nickname
-
- if self.maxX > 125 + xOffset:
- # shows ip/port/locale (column width: 28 characters)
- etc += "%-26s " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
- else:
- # base data uses whatever extra room's available (using minimun of 50 characters)
- src = self.nickname
- if entry[CONN_TYPE] == "control": dst = self.nickname
- else: dst = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
-
- # space available for foreign nickname
- foreignNicknameSpace = self.maxX - len(self.nickname) - 27 - xOffset
-
- etc = ""
- if self.maxX > 92 + xOffset:
- # show fingerprint (column width: 42 characters)
- foreignNicknameSpace -= 42
- etc += "%-40s " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
-
- if self.maxX > 120 + xOffset:
- # shows ip/port/locale (column width: 28 characters)
- foreignNicknameSpace -= 28
- etc += "%-26s " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
-
- dst = ("%%-%is" % foreignNicknameSpace) % dst
+ etc += ("%%-%is " % nicknameSpace) % nickname
+ elif self.listingType == LIST_HOSTNAME:
+ # base data requires 80 characters
+ src = "localhost:%-5s" % entry[CONN_L_PORT]
- timeLabel = util.getTimeLabel(currentTime - entry[CONN_TIME], 1)
- if type == "inbound": src, dst = dst, src
- elif type == "family" and int(entry[CONN_L_PORT]) > 65535:
- # this belongs to an unresolved family entry - replaces invalid data with "UNKNOWN"
- timeLabel = "---"
+ # space available for foreign hostname (stretched to claim any free space)
+ foreignHostnameSpace = self.maxX - 42 - xOffset
+
+ etc = ""
+ if self.maxX > 102 + xOffset:
+ # shows ip/locale (column width: 22 characters)
+ foreignHostnameSpace -= 22
+ etc += "%-20s " % ("%s %s" % (entry[CONN_F_IP], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
+
+ if self.maxX > 134 + xOffset:
+ # show fingerprint (column width: 42 characters)
+ foreignHostnameSpace -= 42
+ etc += "%-40s " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
+
+ if self.maxX > 151 + xOffset:
+ # show nickname (column width: min 17 characters, uses half of the remainder)
+ nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+ nicknameSpace = 15 + (self.maxX - xOffset - 151) / 2
+ foreignHostnameSpace -= (nicknameSpace + 2)
- if self.listingType == LIST_IP:
- src = "%-21s" % "UNKNOWN"
- dst = "%-26s" % "UNKNOWN"
- elif self.listingType == LIST_HOSTNAME:
- src = "%-15s" % "UNKNOWN"
- dst = ("%%-%is" % len(dst)) % "UNKNOWN"
- if len(etc) > 0: etc = etc.replace("256.255.255.255 (??)", "UNKNOWN" + " " * 13)
- else:
- ipStart = etc.find("256")
- if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
+ if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
+ etc += ("%%-%is " % nicknameSpace) % nickname
- padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) - xOffset # padding needed to fill full line
- lineEntry = "<%s>%s --> %s %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
+ hostname = self.resolver.resolve(entry[CONN_F_IP])
- if self.isCursorEnabled and entry == self.cursorSelection:
- lineEntry = "<h>%s</h>" % lineEntry
+ # truncates long hostnames
+ portDigits = len(str(entry[CONN_F_PORT]))
+ if hostname and (len(hostname) + portDigits) > foreignHostnameSpace - 1:
+ hostname = hostname[:(foreignHostnameSpace - portDigits - 4)] + "..."
- yOffset = 0 if not self.showingDetails else 8
- self.addfstr(lineNum + yOffset, xOffset, lineEntry)
- lineNum += 1
-
- if isScrollBarVisible:
- topY = 9 if self.showingDetails else 1
- bottomEntry = self.scroll + self.maxY - 9 if self.showingDetails else self.scroll + self.maxY - 1
- util.drawScrollBar(self, topY, self.maxY - 1, self.scroll, bottomEntry, len(self.connections))
+ dst = "%s:%s" % (hostname if hostname else entry[CONN_F_IP], entry[CONN_F_PORT])
+ dst = ("%%-%is" % foreignHostnameSpace) % dst
+ elif self.listingType == LIST_FINGERPRINT:
+ # base data requires 75 characters
+ src = "localhost"
+ if entry[CONN_TYPE] == "control": dst = "localhost"
+ else: dst = self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
+ dst = "%-40s" % dst
+
+ etc = ""
+ if self.maxX > 92 + xOffset:
+ # show nickname (column width: min 17 characters, uses remainder if extra room's available)
+ nickname = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+ nicknameSpace = self.maxX - 78 - xOffset if self.maxX < 126 else self.maxX - 106 - xOffset
+ if len(nickname) > nicknameSpace: nickname = "%s..." % nickname[:nicknameSpace - 3]
+ etc += ("%%-%is " % nicknameSpace) % nickname
+
+ if self.maxX > 125 + xOffset:
+ # shows ip/port/locale (column width: 28 characters)
+ etc += "%-26s " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
+ else:
+ # base data uses whatever extra room's available (using minimun of 50 characters)
+ src = self.nickname
+ if entry[CONN_TYPE] == "control": dst = self.nickname
+ else: dst = self.getNickname(entry[CONN_F_IP], entry[CONN_F_PORT])
+
+ # space available for foreign nickname
+ foreignNicknameSpace = self.maxX - len(self.nickname) - 27 - xOffset
+
+ etc = ""
+ if self.maxX > 92 + xOffset:
+ # show fingerprint (column width: 42 characters)
+ foreignNicknameSpace -= 42
+ etc += "%-40s " % self.getFingerprint(entry[CONN_F_IP], entry[CONN_F_PORT])
+
+ if self.maxX > 120 + xOffset:
+ # shows ip/port/locale (column width: 28 characters)
+ foreignNicknameSpace -= 28
+ etc += "%-26s " % ("%s:%s %s" % (entry[CONN_F_IP], entry[CONN_F_PORT], "" if type == "control" else "(%s)" % entry[CONN_COUNTRY]))
+
+ dst = ("%%-%is" % foreignNicknameSpace) % dst
+
+ timeLabel = uiTools.getTimeLabel(currentTime - entry[CONN_TIME], 1)
+ if type == "inbound": src, dst = dst, src
+ elif type == "family" and int(entry[CONN_L_PORT]) > 65535:
+ # this belongs to an unresolved family entry - replaces invalid data with "UNKNOWN"
+ timeLabel = "---"
+
+ if self.listingType == LIST_IP:
+ src = "%-21s" % "UNKNOWN"
+ dst = "%-26s" % "UNKNOWN"
+ elif self.listingType == LIST_HOSTNAME:
+ src = "%-15s" % "UNKNOWN"
+ dst = ("%%-%is" % len(dst)) % "UNKNOWN"
+ if len(etc) > 0: etc = etc.replace("256.255.255.255 (??)", "UNKNOWN" + " " * 13)
+ else:
+ ipStart = etc.find("256")
+ if ipStart > -1: etc = etc[:ipStart] + ("%%-%is" % len(etc[ipStart:])) % "UNKNOWN"
+
+ padding = self.maxX - (len(src) + len(dst) + len(etc) + 27) - xOffset # padding needed to fill full line
+ lineEntry = "<%s>%s --> %s %s%s%5s (<b>%s</b>)%s</%s>" % (color, src, dst, etc, " " * padding, timeLabel, type.upper(), " " * (9 - len(type)), color)
+
+ if self.isCursorEnabled and entry == self.cursorSelection:
+ lineEntry = "<h>%s</h>" % lineEntry
+
+ yOffset = 0 if not self.showingDetails else 8
+ self.addfstr(lineNum + yOffset, xOffset, lineEntry)
+ lineNum += 1
- self.refresh()
- finally:
- self.lock.release()
- self.connectionsLock.release()
+ if isScrollBarVisible:
+ topY = 9 if self.showingDetails else 1
+ bottomEntry = self.scroll + self.maxY - 9 if self.showingDetails else self.scroll + self.maxY - 1
+ uiTools.drawScrollBar(self, topY, self.maxY - 1, self.scroll, bottomEntry, len(self.connections))
+ finally:
+ self.connectionsLock.release()
def getFingerprint(self, ipAddr, port):
"""
Modified: arm/trunk/interface/controller.py
===================================================================
--- arm/trunk/interface/controller.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/controller.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -12,7 +12,6 @@
import time
import curses
import socket
-from threading import RLock
from TorCtl import TorCtl
from TorCtl import TorUtil
@@ -24,7 +23,7 @@
import descriptorPopup
import fileDescriptorPopup
-import util
+from util import panel, uiTools
import connResolver
import bandwidthMonitor
import cpuMemMonitor
@@ -32,8 +31,6 @@
CONFIRM_QUIT = True
REFRESH_RATE = 5 # seconds between redrawing screen
-cursesLock = RLock() # global curses lock (curses isn't thread safe and
- # concurrency bugs produce especially sinister glitches)
MAX_REGEX_FILTERS = 5 # maximum number of previous regex filters that'll be remembered
# enums for message in control label
@@ -50,11 +47,11 @@
# events needed for panels other than the event log
REQ_EVENTS = ["BW", "NEWDESC", "NEWCONSENSUS", "CIRC"]
-class ControlPanel(util.Panel):
+class ControlPanel(panel.Panel):
""" Draws single line label for interface controls. """
- def __init__(self, lock, resolver, isBlindMode):
- util.Panel.__init__(self, lock, 1)
+ def __init__(self, resolver, isBlindMode):
+ panel.Panel.__init__(self, 1)
self.msgText = CTL_HELP # message text to be displyed
self.msgAttr = curses.A_NORMAL # formatting attributes
self.page = 1 # page number currently being displayed
@@ -71,61 +68,56 @@
self.msgText = msgText
self.msgAttr = msgAttr
- def redraw(self):
- if self.win:
- self.clear()
+ def draw(self):
+ msgText = self.msgText
+ msgAttr = self.msgAttr
+ barTab = 2 # space between msgText and progress bar
+ barWidthMax = 40 # max width to progress bar
+ barWidth = -1 # space between "[ ]" in progress bar (not visible if -1)
+ barProgress = 0 # cells to fill
+
+ if msgText == CTL_HELP:
+ msgAttr = curses.A_NORMAL
- msgText = self.msgText
- msgAttr = self.msgAttr
- barTab = 2 # space between msgText and progress bar
- barWidthMax = 40 # max width to progress bar
- barWidth = -1 # space between "[ ]" in progress bar (not visible if -1)
- barProgress = 0 # cells to fill
+ if self.resolvingCounter != -1:
+ if self.resolver.unresolvedQueue.empty() or self.resolver.isPaused:
+ # done resolving dns batch
+ self.resolvingCounter = -1
+ curses.halfdelay(REFRESH_RATE * 10) # revert to normal refresh rate
+ else:
+ batchSize = self.resolver.totalResolves - self.resolvingCounter
+ entryCount = batchSize - self.resolver.unresolvedQueue.qsize()
+ if batchSize > 0: progress = 100 * entryCount / batchSize
+ else: progress = 0
+
+ additive = "or l " if self.page == 2 else ""
+ batchSizeDigits = int(math.log10(batchSize)) + 1
+ entryCountLabel = ("%%%ii" % batchSizeDigits) % entryCount
+ #msgText = "Resolving hostnames (%i / %i, %i%%) - press esc %sto cancel" % (entryCount, batchSize, progress, additive)
+ msgText = "Resolving hostnames (press esc %sto cancel) - %s / %i, %2i%%" % (additive, entryCountLabel, batchSize, progress)
+
+ barWidth = min(barWidthMax, self.maxX - len(msgText) - 3 - barTab)
+ barProgress = barWidth * entryCount / batchSize
- if msgText == CTL_HELP:
- msgAttr = curses.A_NORMAL
+ if self.resolvingCounter == -1:
+ currentPage = self.page
+ pageCount = len(PAGES)
- if self.resolvingCounter != -1:
- if self.resolver.unresolvedQueue.empty() or self.resolver.isPaused:
- # done resolving dns batch
- self.resolvingCounter = -1
- curses.halfdelay(REFRESH_RATE * 10) # revert to normal refresh rate
- else:
- batchSize = self.resolver.totalResolves - self.resolvingCounter
- entryCount = batchSize - self.resolver.unresolvedQueue.qsize()
- if batchSize > 0: progress = 100 * entryCount / batchSize
- else: progress = 0
-
- additive = "or l " if self.page == 2 else ""
- batchSizeDigits = int(math.log10(batchSize)) + 1
- entryCountLabel = ("%%%ii" % batchSizeDigits) % entryCount
- #msgText = "Resolving hostnames (%i / %i, %i%%) - press esc %sto cancel" % (entryCount, batchSize, progress, additive)
- msgText = "Resolving hostnames (press esc %sto cancel) - %s / %i, %2i%%" % (additive, entryCountLabel, batchSize, progress)
-
- barWidth = min(barWidthMax, self.maxX - len(msgText) - 3 - barTab)
- barProgress = barWidth * entryCount / batchSize
+ if self.isBlindMode:
+ if currentPage >= 2: currentPage -= 1
+ pageCount -= 1
- if self.resolvingCounter == -1:
- currentPage = self.page
- pageCount = len(PAGES)
-
- if self.isBlindMode:
- if currentPage >= 2: currentPage -= 1
- pageCount -= 1
-
- msgText = "page %i / %i - q: quit, p: pause, h: page help" % (currentPage, pageCount)
- elif msgText == CTL_PAUSED:
- msgText = "Paused"
- msgAttr = curses.A_STANDOUT
-
- self.addstr(0, 0, msgText, msgAttr)
- if barWidth > -1:
- xLoc = len(msgText) + barTab
- self.addstr(0, xLoc, "[", curses.A_BOLD)
- self.addstr(0, xLoc + 1, " " * barProgress, curses.A_STANDOUT | util.getColor("red"))
- self.addstr(0, xLoc + barWidth + 1, "]", curses.A_BOLD)
-
- self.refresh()
+ msgText = "page %i / %i - q: quit, p: pause, h: page help" % (currentPage, pageCount)
+ elif msgText == CTL_PAUSED:
+ msgText = "Paused"
+ msgAttr = curses.A_STANDOUT
+
+ self.addstr(0, 0, msgText, msgAttr)
+ if barWidth > -1:
+ xLoc = len(msgText) + barTab
+ self.addstr(0, xLoc, "[", curses.A_BOLD)
+ self.addstr(0, xLoc + 1, " " * barProgress, curses.A_STANDOUT | uiTools.getColor("red"))
+ self.addstr(0, xLoc + barWidth + 1, "]", curses.A_BOLD)
class sighupListener(TorCtl.PostEventListener):
"""
@@ -160,7 +152,7 @@
selection = initialSelection if initialSelection != -1 else 0
if popup.win:
- if not popup.lock.acquire(False): return -1
+ if not panel.CURSES_LOCK.acquire(False): return -1
try:
curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -168,13 +160,13 @@
popup.height = len(options) + 2
newWidth = max([len(label) for label in options]) + 9
- popup.recreate(stdscr, popup.startY, newWidth)
+ popup.recreate(stdscr, newWidth)
key = 0
while key not in (curses.KEY_ENTER, 10, ord(' ')):
popup.clear()
popup.win.box()
- popup.addstr(0, 0, title, util.LABEL_ATTR)
+ popup.addstr(0, 0, title, uiTools.LABEL_ATTR)
for i in range(len(options)):
label = options[i]
@@ -191,11 +183,11 @@
# reverts popup dimensions and conn panel label
popup.height = 9
- popup.recreate(stdscr, popup.startY, 80)
+ popup.recreate(stdscr, 80)
curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
return selection
@@ -257,7 +249,7 @@
"""
curses.use_default_colors() # allows things like semi-transparent backgrounds
- util.initColors() # initalizes color pairs for colored text
+ uiTools.initColors() # initalizes color pairs for colored text
curses.halfdelay(REFRESH_RATE * 10) # uses getch call as timer for REFRESH_RATE seconds
# attempts to make the cursor invisible (not supported in all terminals)
@@ -279,7 +271,7 @@
try:
# uses netstat to identify process with open control port (might not
# work if tor's being run as a different user due to permissions)
- netstatCall = os.popen("netstat -npl 2> /dev/null | grep 127.0.0.1:%s" % conn.get_option("ControlPort")[0][1])
+ netstatCall = os.popen("netstat -npl 2> /dev/null | grep 127.0.0.1:%s 2> /dev/null" % conn.get_option("ControlPort")[0][1])
results = netstatCall.readlines()
if len(results) == 1:
@@ -311,18 +303,18 @@
confLocation = ""
panels = {
- "header": headerPanel.HeaderPanel(cursesLock, conn, torPid),
- "popup": util.Panel(cursesLock, 9),
- "graph": graphPanel.GraphPanel(cursesLock),
- "log": logPanel.LogMonitor(cursesLock, conn, loggedEvents)}
+ "header": headerPanel.HeaderPanel(conn, torPid),
+ "popup": panel.Panel(9),
+ "graph": graphPanel.GraphPanel(),
+ "log": logPanel.LogMonitor(conn, loggedEvents)}
# starts thread for processing netstat queries
connResolutionThread = connResolver.ConnResolver(conn, torPid, panels["log"])
if not isBlindMode: connResolutionThread.start()
- panels["conn"] = connPanel.ConnPanel(cursesLock, conn, connResolutionThread, panels["log"])
- panels["control"] = ControlPanel(cursesLock, panels["conn"].resolver, isBlindMode)
- panels["torrc"] = confPanel.ConfPanel(cursesLock, confLocation, conn, panels["log"])
+ panels["conn"] = connPanel.ConnPanel(conn, connResolutionThread, panels["log"])
+ panels["control"] = ControlPanel(panels["conn"].resolver, isBlindMode)
+ panels["torrc"] = confPanel.ConfPanel(confLocation, conn, panels["log"])
# prevents netstat calls by connPanel if not being used
if isBlindMode: panels["conn"].isDisabled = True
@@ -374,7 +366,7 @@
# noticeable lag when resizing and didn't have an appreciable effect
# on system usage
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
# if sighup received then reload related information
if sighupTracker.isReset:
@@ -398,16 +390,16 @@
# resilient in case of funky changes (such as resizing during popups)
startY = 0
for panelKey in PAGE_S[:2]:
- panels[panelKey].recreate(stdscr, startY)
+ panels[panelKey].recreate(stdscr, -1, startY)
startY += panels[panelKey].height
- panels["popup"].recreate(stdscr, startY, 80)
+ panels["popup"].recreate(stdscr, 80, startY)
for panelSet in PAGES:
tmpStartY = startY
for panelKey in panelSet:
- panels[panelKey].recreate(stdscr, tmpStartY)
+ panels[panelKey].recreate(stdscr, -1, tmpStartY)
tmpStartY += panels[panelKey].height
# if it's been at least ten seconds since the last BW event Tor's probably done
@@ -422,10 +414,18 @@
panels["conn"].reset()
# I haven't the foggiest why, but doesn't work if redrawn out of order...
+
+ # TODO: temporary hack to prevent popup redraw until a valid replacement is implemented (ick!)
+ tmpSubwin = panels["popup"].win
+ panels["popup"].win = None
+
for panelKey in (PAGE_S + PAGES[page]): panels[panelKey].redraw()
+
+ panels["popup"].win = tmpSubwin
+
stdscr.refresh()
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
# wait for user keyboard input until timeout (unless an override was set)
if overrideKey:
@@ -439,7 +439,7 @@
# provides prompt to confirm that arm should exit
if CONFIRM_QUIT:
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
@@ -455,7 +455,7 @@
panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
setPauseState(panels, isPaused, page)
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
if quitConfirmed:
# quits arm
@@ -490,16 +490,16 @@
panels["control"].refresh()
elif key == ord('p') or key == ord('P'):
# toggles update freezing
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
isPaused = not isPaused
setPauseState(panels, isPaused, page)
panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif key == ord('h') or key == ord('H'):
# displays popup for current page's controls
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
@@ -507,7 +507,7 @@
popup = panels["popup"]
popup.clear()
popup.win.box()
- popup.addstr(0, 0, "Page %i Commands:" % (page + 1), util.LABEL_ATTR)
+ popup.addstr(0, 0, "Page %i Commands:" % (page + 1), uiTools.LABEL_ATTR)
pageOverrideKeys = ()
@@ -566,7 +566,7 @@
setPauseState(panels, isPaused, page)
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 0 and (key == ord('s') or key == ord('S')):
# provides menu to pick stats to be graphed
#options = ["None"] + [label for label in panels["graph"].stats.keys()]
@@ -626,7 +626,7 @@
panels["graph"].bounds = (panels["graph"].bounds + 1) % 2
elif page == 0 and key in (ord('d'), ord('D')):
# provides popup with file descriptors
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -636,10 +636,10 @@
setPauseState(panels, isPaused, page)
curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 0 and (key == ord('e') or key == ord('E')):
# allow user to enter new types of events to log - unchanged if left blank
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
@@ -655,11 +655,11 @@
# lists event types
popup = panels["popup"]
popup.height = 10
- popup.recreate(stdscr, popup.startY, 80)
+ popup.recreate(stdscr, 80)
popup.clear()
popup.win.box()
- popup.addstr(0, 0, "Event Types:", util.LABEL_ATTR)
+ popup.addstr(0, 0, "Event Types:", uiTools.LABEL_ATTR)
lineNum = 1
for line in logPanel.EVENT_LISTING.split("\n"):
line = line[6:]
@@ -690,12 +690,12 @@
# reverts popup dimensions
popup.height = 9
- popup.recreate(stdscr, popup.startY, 80)
+ popup.recreate(stdscr, 80)
panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
setPauseState(panels, isPaused, page)
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 0 and (key == ord('f') or key == ord('F')):
# provides menu to pick previous regular expression filters or to add a new one
# for syntax see: http://docs.python.org/library/re.html#regular-expression-syntax
@@ -715,7 +715,7 @@
panels["log"].regexFilter = None
elif selection == len(options) - 1:
# selected 'New...' option - prompt user to input regular expression
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
# provides prompt
panels["control"].setMsg("Regular expression: ")
@@ -746,7 +746,7 @@
time.sleep(2)
panels["control"].setMsg(CTL_PAUSED if isPaused else CTL_HELP)
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif selection != -1:
try:
panels["log"].regexFilter = re.compile(regexFilters[selection - 1])
@@ -772,7 +772,7 @@
panels["conn"].sortConnections()
elif page == 1 and panels["conn"].isCursorEnabled and key in (curses.KEY_ENTER, 10, ord(' ')):
# provides details on selected connection
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
popup = panels["popup"]
@@ -792,12 +792,12 @@
while key not in (curses.KEY_ENTER, 10, ord(' ')):
popup.clear()
popup.win.box()
- popup.addstr(0, 0, "Connection Details:", util.LABEL_ATTR)
+ popup.addstr(0, 0, "Connection Details:", uiTools.LABEL_ATTR)
selection = panels["conn"].cursorSelection
if not selection or not panels["conn"].connections: break
selectionColor = connPanel.TYPE_COLORS[selection[connPanel.CONN_TYPE]]
- format = util.getColor(selectionColor) | curses.A_BOLD
+ format = uiTools.getColor(selectionColor) | curses.A_BOLD
selectedIp = selection[connPanel.CONN_F_IP]
selectedPort = selection[connPanel.CONN_F_PORT]
@@ -911,10 +911,10 @@
setPauseState(panels, isPaused, page)
curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 1 and panels["conn"].isCursorEnabled and key in (ord('d'), ord('D')):
# presents popup for raw consensus data
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -927,7 +927,7 @@
curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
panels["conn"].showLabel = True
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 1 and (key == ord('l') or key == ord('L')):
# provides menu to pick identification info listed for connections
optionTypes = [connPanel.LIST_IP, connPanel.LIST_HOSTNAME, connPanel.LIST_FINGERPRINT, connPanel.LIST_NICKNAME]
@@ -963,7 +963,7 @@
panels["conn"].sortConnections()
elif page == 1 and (key == ord('s') or key == ord('S')):
# set ordering for connection listing
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
curses.cbreak() # wait indefinitely for key presses (no timeout)
@@ -986,7 +986,7 @@
while len(selections) < 3:
popup.clear()
popup.win.box()
- popup.addstr(0, 0, "Connection Ordering:", util.LABEL_ATTR)
+ popup.addstr(0, 0, "Connection Ordering:", uiTools.LABEL_ATTR)
popup.addfstr(1, 2, prevOrdering)
# provides new ordering
@@ -1027,7 +1027,7 @@
setPauseState(panels, isPaused, page)
curses.halfdelay(REFRESH_RATE * 10) # reset normal pausing behavior
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 1 and (key == ord('c') or key == ord('C')):
# displays popup with client circuits
clientCircuits = None
@@ -1039,7 +1039,7 @@
if clientCircuits:
for clientEntry in clientCircuits: maxEntryLength = max(len(clientEntry), maxEntryLength)
- cursesLock.acquire()
+ panel.CURSES_LOCK.acquire()
try:
setPauseState(panels, isPaused, page, True)
@@ -1048,12 +1048,12 @@
popup._resetBounds()
if clientCircuits and maxEntryLength + 4 > popup.maxX:
popup.height = max(popup.height, len(clientCircuits) + 3)
- popup.recreate(stdscr, popup.startY, maxEntryLength + 4)
+ popup.recreate(stdscr, maxEntryLength + 4)
# lists commands
popup.clear()
popup.win.box()
- popup.addstr(0, 0, "Client Circuits:", util.LABEL_ATTR)
+ popup.addstr(0, 0, "Client Circuits:", uiTools.LABEL_ATTR)
if clientCircuits == None:
popup.addstr(1, 2, "Unable to retireve current circuits")
@@ -1074,11 +1074,11 @@
# reverts popup dimensions
popup.height = 9
- popup.recreate(stdscr, popup.startY, 80)
+ popup.recreate(stdscr, 80)
setPauseState(panels, isPaused, page)
finally:
- cursesLock.release()
+ panel.CURSES_LOCK.release()
elif page == 0:
panels["log"].handleKey(key)
elif page == 1:
Modified: arm/trunk/interface/cpuMemMonitor.py
===================================================================
--- arm/trunk/interface/cpuMemMonitor.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/cpuMemMonitor.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -6,7 +6,7 @@
import time
from TorCtl import TorCtl
-import util
+from util import uiTools
import graphPanel
class CpuMemMonitor(graphPanel.GraphStats, TorCtl.PostEventListener):
@@ -29,7 +29,7 @@
self._processEvent(float(self.headerPanel.vals["%cpu"]), float(self.headerPanel.vals["rss"]) / 1024.0)
else:
# cached results stale - requery ps
- psCall = os.popen('ps -p %s -o %s 2> /dev/null' % (self.headerPanel.vals["pid"], "%cpu,rss"))
+ psCall = os.popen('ps -p %s -o %s 2> /dev/null' % (self.headerPanel.vals["pid"], "%cpu,rss"))
try:
sampling = psCall.read().strip().split()[2:]
psCall.close()
@@ -50,5 +50,5 @@
def getHeaderLabel(self, width, isPrimary):
avg = (self.primaryTotal if isPrimary else self.secondaryTotal) / max(1, self.tick)
if isPrimary: return "CPU (%s%%, avg: %0.1f%%):" % (self.lastPrimary, avg)
- else: return "Memory (%s, avg: %s):" % (util.getSizeLabel(self.lastSecondary * 1048576, 1), util.getSizeLabel(avg * 1048576, 1))
+ else: return "Memory (%s, avg: %s):" % (uiTools.getSizeLabel(self.lastSecondary * 1048576, 1), uiTools.getSizeLabel(avg * 1048576, 1))
Modified: arm/trunk/interface/descriptorPopup.py
===================================================================
--- arm/trunk/interface/descriptorPopup.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/descriptorPopup.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -8,7 +8,7 @@
from TorCtl import TorCtl
import connPanel
-import util
+from util import panel, uiTools
# field keywords used to identify areas for coloring
LINE_NUM_COLOR = "yellow"
@@ -78,7 +78,7 @@
properties = PopupProperties(conn)
isVisible = True
- if not popup.lock.acquire(False): return
+ if not panel.CURSES_LOCK.acquire(False): return
try:
while isVisible:
selection = connectionPanel.cursorSelection
@@ -100,7 +100,7 @@
popup._resetBounds()
popup.height = min(len(properties.text) + height + 2, connectionPanel.maxY)
- popup.recreate(stdscr, popup.startY, width)
+ popup.recreate(stdscr, width)
while isVisible:
draw(popup, properties)
@@ -116,9 +116,9 @@
else: properties.handleKey(key, popup.height - 2)
popup.height = 9
- popup.recreate(stdscr, popup.startY, 80)
+ popup.recreate(stdscr, 80)
finally:
- popup.lock.release()
+ panel.CURSES_LOCK.release()
def draw(popup, properties):
popup.clear()
@@ -126,8 +126,8 @@
xOffset = 2
if properties.text:
- if properties.fingerprint: popup.addstr(0, 0, "Consensus Descriptor (%s):" % properties.fingerprint, util.LABEL_ATTR)
- else: popup.addstr(0, 0, "Consensus Descriptor:", util.LABEL_ATTR)
+ if properties.fingerprint: popup.addstr(0, 0, "Consensus Descriptor (%s):" % properties.fingerprint, uiTools.LABEL_ATTR)
+ else: popup.addstr(0, 0, "Consensus Descriptor:", uiTools.LABEL_ATTR)
isEncryption = False # true if line is part of an encryption block
@@ -145,31 +145,31 @@
numOffset = 0 # offset for line numbering
if properties.showLineNum:
- popup.addstr(lineNum, xOffset, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | util.getColor(LINE_NUM_COLOR))
+ popup.addstr(lineNum, xOffset, ("%%%ii" % numFieldWidth) % (i + 1), curses.A_BOLD | uiTools.getColor(LINE_NUM_COLOR))
numOffset = numFieldWidth + 1
if lineText:
keyword = lineText.split()[0] # first word of line
remainder = lineText[len(keyword):]
- keywordFormat = curses.A_BOLD | util.getColor(properties.entryColor)
- remainderFormat = util.getColor(properties.entryColor)
+ keywordFormat = curses.A_BOLD | uiTools.getColor(properties.entryColor)
+ remainderFormat = uiTools.getColor(properties.entryColor)
if lineText.startswith(HEADER_PREFIX[0]) or lineText.startswith(HEADER_PREFIX[1]):
keyword, remainder = lineText, ""
- keywordFormat = curses.A_BOLD | util.getColor(HEADER_COLOR)
+ keywordFormat = curses.A_BOLD | uiTools.getColor(HEADER_COLOR)
if lineText == UNRESOLVED_MSG or lineText == ERROR_MSG:
keyword, remainder = lineText, ""
if lineText in SIG_START_KEYS:
keyword, remainder = lineText, ""
isEncryption = True
- keywordFormat = curses.A_BOLD | util.getColor(SIG_COLOR)
+ keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR)
elif lineText in SIG_END_KEYS:
keyword, remainder = lineText, ""
isEncryption = False
- keywordFormat = curses.A_BOLD | util.getColor(SIG_COLOR)
+ keywordFormat = curses.A_BOLD | uiTools.getColor(SIG_COLOR)
elif isEncryption:
keyword, remainder = lineText, ""
- keywordFormat = util.getColor(SIG_COLOR)
+ keywordFormat = uiTools.getColor(SIG_COLOR)
lineNum, xLoc = popup.addstr_wrap(lineNum, 0, keyword, keywordFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1)
lineNum, xLoc = popup.addstr_wrap(lineNum, xLoc, remainder, remainderFormat, xOffset + numOffset, popup.maxX - 1, popup.maxY - 1)
Modified: arm/trunk/interface/fileDescriptorPopup.py
===================================================================
--- arm/trunk/interface/fileDescriptorPopup.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/fileDescriptorPopup.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -5,7 +5,7 @@
import os
import curses
-import util
+from util import panel, uiTools
class PopupProperties:
"""
@@ -27,7 +27,7 @@
# retrieves list of open files, options are:
# n = no dns lookups, p = by pid, -F = show fields (L = login name, n = opened files)
- lsofCall = os.popen3("lsof -np %s -F Ln" % torPid)
+ lsofCall = os.popen3("lsof -np %s -F Ln 2> /dev/null" % torPid)
results = lsofCall[1].readlines()
errResults = lsofCall[2].readlines()
@@ -103,7 +103,7 @@
properties = PopupProperties(torPid)
- if not popup.lock.acquire(False): return
+ if not panel.CURSES_LOCK.acquire(False): return
try:
if properties.errorMsg:
popupWidth = len(properties.errorMsg) + 4
@@ -118,7 +118,7 @@
popup._resetBounds()
popup.height = popupHeight
- popup.recreate(stdscr, popup.startY, popupWidth)
+ popup.recreate(stdscr, popupWidth)
while True:
draw(popup, properties)
@@ -132,9 +132,9 @@
break
popup.height = 9
- popup.recreate(stdscr, popup.startY, 80)
+ popup.recreate(stdscr, 80)
finally:
- popup.lock.release()
+ panel.CURSES_LOCK.release()
def draw(popup, properties):
popup.clear()
@@ -144,7 +144,7 @@
popup.addstr(0, 0, "Open File Descriptors:", curses.A_STANDOUT)
if properties.errorMsg:
- popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | util.getColor("red"))
+ popup.addstr(1, 2, properties.errorMsg, curses.A_BOLD | uiTools.getColor("red"))
else:
# text with file descriptor count and limit
fdCount = len(properties.fdFile) + len(properties.fdConn) + len(properties.fdMisc)
@@ -155,14 +155,14 @@
elif fdCountPer >= 50: statsColor = "yellow"
countMsg = "%i / %i (%i%%)" % (fdCount, properties.fdLimit, fdCountPer)
- popup.addstr(1, 2, countMsg, curses.A_BOLD | util.getColor(statsColor))
+ popup.addstr(1, 2, countMsg, curses.A_BOLD | uiTools.getColor(statsColor))
# provides a progress bar reflecting the stats
barWidth = popup.maxX - len(countMsg) - 6 # space between "[ ]" in progress bar
barProgress = barWidth * fdCountPer / 100 # filled cells
if fdCount > 0: barProgress = max(1, barProgress) # ensures one cell is filled unless really zero
popup.addstr(1, len(countMsg) + 3, "[", curses.A_BOLD)
- popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | util.getColor(statsColor))
+ popup.addstr(1, len(countMsg) + 4, " " * barProgress, curses.A_STANDOUT | uiTools.getColor(statsColor))
popup.addstr(1, len(countMsg) + 4 + barWidth, "]", curses.A_BOLD)
popup.win.hline(2, 1, curses.ACS_HLINE, popup.maxX - 2)
@@ -181,7 +181,7 @@
line = properties.fdConn[entryNum - len(properties.fdFile) - len(properties.fdMisc)]
color = "blue"
- popup.addstr(lineNum, 2, line, curses.A_BOLD | util.getColor(color))
+ popup.addstr(lineNum, 2, line, curses.A_BOLD | uiTools.getColor(color))
lineNum += 1
entryNum += 1
Modified: arm/trunk/interface/graphPanel.py
===================================================================
--- arm/trunk/interface/graphPanel.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/graphPanel.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -5,7 +5,7 @@
import copy
import curses
-import util
+from util import panel, uiTools
MAX_GRAPH_COL = 150 # max columns of data in graph
WIDE_LABELING_GRAPH_COL = 50 # minimum graph columns to use wide spacing for x-axis labels
@@ -90,9 +90,9 @@
return ""
- def redraw(self, panel):
+ def draw(self, panel):
"""
- Allows for any custom redrawing monitor wishes to append.
+ Allows for any custom drawing monitor wishes to append.
"""
pass
@@ -157,14 +157,14 @@
if self.graphPanel: self.graphPanel.redraw()
-class GraphPanel(util.Panel):
+class GraphPanel(panel.Panel):
"""
Panel displaying a graph, drawing statistics from custom GraphStats
implementations.
"""
- def __init__(self, lock):
- util.Panel.__init__(self, lock, 0) # height is overwritten with current module
+ def __init__(self):
+ panel.Panel.__init__(self, 0) # height is overwritten with current module
self.updateInterval = DEFAULT_UPDATE_INTERVAL
self.isPaused = False
self.showLabel = True # shows top label if true, hides otherwise
@@ -172,81 +172,74 @@
self.currentDisplay = None # label of the stats currently being displayed
self.stats = {} # available stats (mappings of label -> instance)
- def redraw(self):
+ def draw(self):
""" Redraws graph panel """
- if self.win:
- if not self.lock.acquire(False): return
- try:
- self.clear()
- graphCol = min((self.maxX - 10) / 2, MAX_GRAPH_COL)
+
+ graphCol = min((self.maxX - 10) / 2, MAX_GRAPH_COL)
+
+ if self.currentDisplay:
+ param = self.stats[self.currentDisplay]
+ primaryColor = uiTools.getColor(param.primaryColor)
+ secondaryColor = uiTools.getColor(param.secondaryColor)
+
+ if self.showLabel: self.addstr(0, 0, param.getTitle(self.maxX), uiTools.LABEL_ATTR)
+
+ # top labels
+ left, right = param.getHeaderLabel(self.maxX / 2, True), param.getHeaderLabel(self.maxX / 2, False)
+ if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
+ if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor)
+
+ # determines max value on the graph
+ primaryBound, secondaryBound = -1, -1
+
+ if self.bounds == BOUNDS_MAX:
+ primaryBound = param.maxPrimary[self.updateInterval]
+ secondaryBound = param.maxSecondary[self.updateInterval]
+ elif self.bounds == BOUNDS_TIGHT:
+ for value in param.primaryCounts[self.updateInterval][1:graphCol + 1]: primaryBound = max(value, primaryBound)
+ for value in param.secondaryCounts[self.updateInterval][1:graphCol + 1]: secondaryBound = max(value, secondaryBound)
+
+ # displays bound
+ self.addstr(2, 0, "%4s" % str(int(primaryBound)), primaryColor)
+ self.addstr(7, 0, " 0", primaryColor)
+
+ self.addstr(2, graphCol + 5, "%4s" % str(int(secondaryBound)), secondaryColor)
+ self.addstr(7, graphCol + 5, " 0", secondaryColor)
+
+ # creates bar graph of bandwidth usage over time
+ for col in range(graphCol):
+ colHeight = min(5, 5 * param.primaryCounts[self.updateInterval][col + 1] / max(1, primaryBound))
+ for row in range(colHeight): self.addstr(7 - row, col + 5, " ", curses.A_STANDOUT | primaryColor)
+
+ for col in range(graphCol):
+ colHeight = min(5, 5 * param.secondaryCounts[self.updateInterval][col + 1] / max(1, secondaryBound))
+ for row in range(colHeight): self.addstr(7 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor)
+
+ # bottom labeling of x-axis
+ intervalSec = 1
+ for (label, timescale) in UPDATE_INTERVALS:
+ if label == self.updateInterval: intervalSec = timescale
+
+ intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
+ unitsLabel, decimalPrecision = None, 0
+ for i in range(1, (graphCol + intervalSpacing - 4) / intervalSpacing):
+ loc = i * intervalSpacing
+ timeLabel = uiTools.getTimeLabel(loc * intervalSec, decimalPrecision)
- if self.currentDisplay:
- param = self.stats[self.currentDisplay]
- primaryColor = util.getColor(param.primaryColor)
- secondaryColor = util.getColor(param.secondaryColor)
-
- if self.showLabel: self.addstr(0, 0, param.getTitle(self.maxX), util.LABEL_ATTR)
-
- # top labels
- left, right = param.getHeaderLabel(self.maxX / 2, True), param.getHeaderLabel(self.maxX / 2, False)
- if left: self.addstr(1, 0, left, curses.A_BOLD | primaryColor)
- if right: self.addstr(1, graphCol + 5, right, curses.A_BOLD | secondaryColor)
-
- # determines max value on the graph
- primaryBound, secondaryBound = -1, -1
-
- if self.bounds == BOUNDS_MAX:
- primaryBound = param.maxPrimary[self.updateInterval]
- secondaryBound = param.maxSecondary[self.updateInterval]
- elif self.bounds == BOUNDS_TIGHT:
- for value in param.primaryCounts[self.updateInterval][1:graphCol + 1]: primaryBound = max(value, primaryBound)
- for value in param.secondaryCounts[self.updateInterval][1:graphCol + 1]: secondaryBound = max(value, secondaryBound)
-
- # displays bound
- self.addstr(2, 0, "%4s" % str(int(primaryBound)), primaryColor)
- self.addstr(7, 0, " 0", primaryColor)
-
- self.addstr(2, graphCol + 5, "%4s" % str(int(secondaryBound)), secondaryColor)
- self.addstr(7, graphCol + 5, " 0", secondaryColor)
-
- # creates bar graph of bandwidth usage over time
- for col in range(graphCol):
- colHeight = min(5, 5 * param.primaryCounts[self.updateInterval][col + 1] / max(1, primaryBound))
- for row in range(colHeight): self.addstr(7 - row, col + 5, " ", curses.A_STANDOUT | primaryColor)
-
- for col in range(graphCol):
- colHeight = min(5, 5 * param.secondaryCounts[self.updateInterval][col + 1] / max(1, secondaryBound))
- for row in range(colHeight): self.addstr(7 - row, col + graphCol + 10, " ", curses.A_STANDOUT | secondaryColor)
-
- # bottom labeling of x-axis
- intervalSec = 1
- for (label, timescale) in UPDATE_INTERVALS:
- if label == self.updateInterval: intervalSec = timescale
-
- intervalSpacing = 10 if graphCol >= WIDE_LABELING_GRAPH_COL else 5
- unitsLabel, decimalPrecision = None, 0
- for i in range(1, (graphCol + intervalSpacing - 4) / intervalSpacing):
- loc = i * intervalSpacing
- timeLabel = util.getTimeLabel(loc * intervalSec, decimalPrecision)
-
- if not unitsLabel: unitsLabel = timeLabel[-1]
- elif unitsLabel != timeLabel[-1]:
- # upped scale so also up precision of future measurements
- unitsLabel = timeLabel[-1]
- decimalPrecision += 1
- else:
- # if constrained on space then strips labeling since already provided
- timeLabel = timeLabel[:-1]
-
- self.addstr(8, 4 + loc, timeLabel, primaryColor)
- self.addstr(8, graphCol + 10 + loc, timeLabel, secondaryColor)
-
- # allows for finishing touches by monitor
- param.redraw(self)
-
- self.refresh()
- finally:
- self.lock.release()
+ if not unitsLabel: unitsLabel = timeLabel[-1]
+ elif unitsLabel != timeLabel[-1]:
+ # upped scale so also up precision of future measurements
+ unitsLabel = timeLabel[-1]
+ decimalPrecision += 1
+ else:
+ # if constrained on space then strips labeling since already provided
+ timeLabel = timeLabel[:-1]
+
+ self.addstr(8, 4 + loc, timeLabel, primaryColor)
+ self.addstr(8, graphCol + 10 + loc, timeLabel, secondaryColor)
+
+ # allows for finishing touches by monitor
+ param.draw(self)
def addStats(self, label, stats):
"""
Modified: arm/trunk/interface/headerPanel.py
===================================================================
--- arm/trunk/interface/headerPanel.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/headerPanel.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -7,7 +7,7 @@
import socket
from TorCtl import TorCtl
-import util
+from util import panel, uiTools
# minimum width for which panel attempts to double up contents (two columns to
# better use screen real estate)
@@ -21,7 +21,7 @@
VERSION_STATUS_COLORS = {"new": "blue", "new in series": "blue", "recommended": "green",
"old": "red", "obsolete": "red", "unrecommended": "red"}
-class HeaderPanel(util.Panel):
+class HeaderPanel(panel.Panel):
"""
Draws top area containing static information.
@@ -37,8 +37,8 @@
fingerprint: BDAD31F6F318E0413833E8EBDA956F76E4D66788
"""
- def __init__(self, lock, conn, torPid):
- util.Panel.__init__(self, lock, 6)
+ def __init__(self, conn, torPid):
+ panel.Panel.__init__(self, 6)
self.vals = {"pid": torPid} # mapping of information to be presented
self.conn = conn # Tor control port connection
self.isPaused = False
@@ -47,119 +47,112 @@
self.lastUpdate = -1 # time last stats was retrived
self._updateParams()
- def recreate(self, stdscr, startY, maxX=-1):
+ def recreate(self, stdscr, maxX=-1, newTop=None):
# might need to recreate twice so we have a window to get width
- if not self.win: util.Panel.recreate(self, stdscr, startY, maxX)
+ if not self.win: panel.Panel.recreate(self, stdscr, maxX, newTop)
self._resetBounds()
self.isWide = self.maxX >= MIN_DUAL_ROW_WIDTH
self.rightParamX = max(self.maxX / 2, 75) if self.isWide else 0
self.height = 4 if self.isWide else 6
- util.Panel.recreate(self, stdscr, startY, maxX)
+ panel.Panel.recreate(self, stdscr, maxX, newTop)
- def redraw(self):
- if self.win:
- if not self.lock.acquire(False): return
- try:
- if not self.isPaused: self._updateParams()
-
- # extra erase/refresh is needed to avoid internal caching screwing up and
- # refusing to redisplay content in the case of graphical glitches - probably
- # an obscure curses bug...
- self.win.erase()
- self.win.refresh()
-
- self.clear()
-
- # Line 1 (system and tor version information)
- systemNameLabel = "arm - %s " % self.vals["sys-name"]
- systemVersionLabel = "%s %s" % (self.vals["sys-os"], self.vals["sys-version"])
-
- # wraps systemVersionLabel in parentheses and truncates if too long
- versionLabelMaxWidth = 40 - len(systemNameLabel)
- if len(systemNameLabel) > 40:
- # we only have room for the system name label
- systemNameLabel = systemNameLabel[:39] + "..."
- systemVersionLabel = ""
- elif len(systemVersionLabel) > versionLabelMaxWidth:
- # not enough room to show full version
- systemVersionLabel = "(%s...)" % systemVersionLabel[:versionLabelMaxWidth - 3].strip()
- else:
- # enough room for everything
- systemVersionLabel = "(%s)" % systemVersionLabel
-
- self.addstr(0, 0, "%s%s" % (systemNameLabel, systemVersionLabel))
-
- versionStatus = self.vals["status/version/current"]
- versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
-
- # truncates torVersionLabel if too long
- torVersionLabel = self.vals["version"]
- versionLabelMaxWidth = (self.rightParamX if self.isWide else self.maxX) - 51 - len(versionStatus)
- if len(torVersionLabel) > versionLabelMaxWidth:
- torVersionLabel = torVersionLabel[:versionLabelMaxWidth - 1].strip() + "-"
-
- self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (torVersionLabel, versionColor, versionStatus, versionColor))
-
- # Line 2 (authentication label red if open, green if credentials required)
- dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
-
- if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
- elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
- else: controlPortAuthLabel = "open"
- controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
-
- labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
- self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
-
- # Line 3 (system usage info) - line 1 right if wide
- y, x = (0, self.rightParamX) if self.isWide else (2, 0)
- self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
- self.addstr(y, x + 13, "mem: %s (%s%%)" % (util.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
- self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
- self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
-
- # Line 4 (fingerprint) - line 2 right if wide
- y, x = (1, self.rightParamX) if self.isWide else (3, 0)
- self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
-
- # Line 5 (flags) - line 3 left if wide
- flagLine = "flags: "
- for flag in self.vals["flags"]:
- flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
- flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
-
- if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
- self.addfstr(2 if self.isWide else 4, 0, flagLine)
-
- # Line 3 right (exit policy) - only present if wide
- if self.isWide:
- exitPolicy = self.vals["ExitPolicy"]
-
- # adds note when default exit policy is appended
- if exitPolicy == None: exitPolicy = "<default>"
- elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
- exitPolicy += ", <default>"
-
- policies = exitPolicy.split(", ")
-
- # color codes accepts to be green, rejects to be red, and default marker to be cyan
- isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
- for i in range(len(policies)):
- policy = policies[i].strip()
- displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
- if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
- elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
- elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
- policies[i] = policy
- exitPolicy = ", ".join(policies)
-
- self.addfstr(2, self.rightParamX, "exit policy: %s" % exitPolicy)
-
- self.refresh()
- finally:
- self.lock.release()
+ def draw(self):
+ if not self.isPaused: self._updateParams()
+
+ # extra erase/refresh is needed to avoid internal caching screwing up and
+ # refusing to redisplay content in the case of graphical glitches - probably
+ # an obscure curses bug...
+ self.win.erase()
+ self.win.refresh()
+
+ self.clear()
+
+ # Line 1 (system and tor version information)
+ systemNameLabel = "arm - %s " % self.vals["sys-name"]
+ systemVersionLabel = "%s %s" % (self.vals["sys-os"], self.vals["sys-version"])
+
+ # wraps systemVersionLabel in parentheses and truncates if too long
+ versionLabelMaxWidth = 40 - len(systemNameLabel)
+ if len(systemNameLabel) > 40:
+ # we only have room for the system name label
+ systemNameLabel = systemNameLabel[:39] + "..."
+ systemVersionLabel = ""
+ elif len(systemVersionLabel) > versionLabelMaxWidth:
+ # not enough room to show full version
+ systemVersionLabel = "(%s...)" % systemVersionLabel[:versionLabelMaxWidth - 3].strip()
+ else:
+ # enough room for everything
+ systemVersionLabel = "(%s)" % systemVersionLabel
+
+ self.addstr(0, 0, "%s%s" % (systemNameLabel, systemVersionLabel))
+
+ versionStatus = self.vals["status/version/current"]
+ versionColor = VERSION_STATUS_COLORS[versionStatus] if versionStatus in VERSION_STATUS_COLORS else "white"
+
+ # truncates torVersionLabel if too long
+ torVersionLabel = self.vals["version"]
+ versionLabelMaxWidth = (self.rightParamX if self.isWide else self.maxX) - 51 - len(versionStatus)
+ if len(torVersionLabel) > versionLabelMaxWidth:
+ torVersionLabel = torVersionLabel[:versionLabelMaxWidth - 1].strip() + "-"
+
+ self.addfstr(0, 43, "Tor %s (<%s>%s</%s>)" % (torVersionLabel, versionColor, versionStatus, versionColor))
+
+ # Line 2 (authentication label red if open, green if credentials required)
+ dirPortLabel = "Dir Port: %s, " % self.vals["DirPort"] if self.vals["DirPort"] != "0" else ""
+
+ if self.vals["IsPasswordAuthSet"]: controlPortAuthLabel = "password"
+ elif self.vals["IsCookieAuthSet"]: controlPortAuthLabel = "cookie"
+ else: controlPortAuthLabel = "open"
+ controlPortAuthColor = "red" if controlPortAuthLabel == "open" else "green"
+
+ labelStart = "%s - %s:%s, %sControl Port (" % (self.vals["Nickname"], self.vals["address"], self.vals["ORPort"], dirPortLabel)
+ self.addfstr(1, 0, "%s<%s>%s</%s>): %s" % (labelStart, controlPortAuthColor, controlPortAuthLabel, controlPortAuthColor, self.vals["ControlPort"]))
+
+ # Line 3 (system usage info) - line 1 right if wide
+ y, x = (0, self.rightParamX) if self.isWide else (2, 0)
+ self.addstr(y, x, "cpu: %s%%" % self.vals["%cpu"])
+ self.addstr(y, x + 13, "mem: %s (%s%%)" % (uiTools.getSizeLabel(int(self.vals["rss"]) * 1024), self.vals["%mem"]))
+ self.addstr(y, x + 34, "pid: %s" % (self.vals["pid"] if self.vals["etime"] else ""))
+ self.addstr(y, x + 47, "uptime: %s" % self.vals["etime"])
+
+ # Line 4 (fingerprint) - line 2 right if wide
+ y, x = (1, self.rightParamX) if self.isWide else (3, 0)
+ self.addstr(y, x, "fingerprint: %s" % self.vals["fingerprint"])
+
+ # Line 5 (flags) - line 3 left if wide
+ flagLine = "flags: "
+ for flag in self.vals["flags"]:
+ flagColor = FLAG_COLORS[flag] if flag in FLAG_COLORS.keys() else "white"
+ flagLine += "<b><%s>%s</%s></b>, " % (flagColor, flag, flagColor)
+
+ if len(self.vals["flags"]) > 0: flagLine = flagLine[:-2]
+ self.addfstr(2 if self.isWide else 4, 0, flagLine)
+
+ # Line 3 right (exit policy) - only present if wide
+ if self.isWide:
+ exitPolicy = self.vals["ExitPolicy"]
+
+ # adds note when default exit policy is appended
+ if exitPolicy == None: exitPolicy = "<default>"
+ elif not exitPolicy.endswith("accept *:*") and not exitPolicy.endswith("reject *:*"):
+ exitPolicy += ", <default>"
+
+ policies = exitPolicy.split(", ")
+
+ # color codes accepts to be green, rejects to be red, and default marker to be cyan
+ isSimple = len(policies) <= 2 # if policy is short then it's kept verbose, otherwise 'accept' and 'reject' keywords removed
+ for i in range(len(policies)):
+ policy = policies[i].strip()
+ displayedPolicy = policy if isSimple else policy.replace("accept", "").replace("reject", "").strip()
+ if policy.startswith("accept"): policy = "<green><b>%s</b></green>" % displayedPolicy
+ elif policy.startswith("reject"): policy = "<red><b>%s</b></red>" % displayedPolicy
+ elif policy.startswith("<default>"): policy = "<cyan><b>%s</b></cyan>" % displayedPolicy
+ policies[i] = policy
+ exitPolicy = ", ".join(policies)
+
+ self.addfstr(2, self.rightParamX, "exit policy: %s" % exitPolicy)
def setPaused(self, isPause):
"""
@@ -222,6 +215,18 @@
# Tor shut down or crashed - keep last known values
if not self.vals[param]: self.vals[param] = "Unknown"
+ # if ORListenAddress is set overwrites 'address' (and possibly ORPort)
+ try:
+ listenAddr = self.conn.get_option("ORListenAddress")[0][1]
+ if listenAddr:
+ if ":" in listenAddr:
+ # both ip and port overwritten
+ self.vals["address"] = listenAddr[:listenAddr.find(":")]
+ self.vals["ORPort"] = listenAddr[listenAddr.find(":") + 1:]
+ else:
+ self.vals["address"] = listenAddr
+ except (socket.error, TorCtl.ErrorReply, TorCtl.TorCtlClosed): pass
+
# flags held by relay
self.vals["flags"] = []
if self.vals["fingerprint"] != "Unknown":
Modified: arm/trunk/interface/logPanel.py
===================================================================
--- arm/trunk/interface/logPanel.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/logPanel.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -8,7 +8,7 @@
from curses.ascii import isprint
from TorCtl import TorCtl
-import util
+from util import panel, uiTools
PRE_POPULATE_LOG = True # attempts to retrieve events from log file if available
@@ -84,14 +84,14 @@
if invalidFlags: raise ValueError(invalidFlags)
else: return expandedEvents
-class LogMonitor(TorCtl.PostEventListener, util.Panel):
+class LogMonitor(TorCtl.PostEventListener, panel.Panel):
"""
Tor event listener, noting messages, the time, and their type in a panel.
"""
- def __init__(self, lock, conn, loggedEvents):
+ def __init__(self, conn, loggedEvents):
TorCtl.PostEventListener.__init__(self)
- util.Panel.__init__(self, lock, -1)
+ panel.Panel.__init__(self, -1)
self.scroll = 0
self.msgLog = [] # tuples of (logText, color)
self.isPaused = False
@@ -287,78 +287,70 @@
if len(self.msgLog) > MAX_LOG_ENTRIES: del self.msgLog[MAX_LOG_ENTRIES:]
self.redraw()
- def redraw(self):
+ def draw(self):
"""
Redraws message log. Entries stretch to use available space and may
contain up to two lines. Starts with newest entries.
"""
- if self.win:
- if not self.lock.acquire(False): return
- try:
- self.clear()
-
- isScrollBarVisible = self.getLogDisplayLength() > self.maxY - 1
- xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
-
- # draws label - uses ellipsis if too long, for instance:
- # Events (DEBUG, INFO, NOTICE, WARN...):
- eventsLabel = "Events"
-
- # separates tor and arm runlevels (might be able to show as range)
- eventsList = list(self.loggedEvents)
- torRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, ""))
- armRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, "ARM_"))
-
- if armRunlevelLabel: eventsList = ["ARM " + armRunlevelLabel] + eventsList
- if torRunlevelLabel: eventsList = [torRunlevelLabel] + eventsList
-
- eventsListing = ", ".join(eventsList)
- filterLabel = "" if not self.regexFilter else " - filter: %s" % self.regexFilter.pattern
-
- firstLabelLen = eventsListing.find(", ")
- if firstLabelLen == -1: firstLabelLen = len(eventsListing)
- else: firstLabelLen += 3
-
- if self.maxX > 10 + firstLabelLen:
- eventsLabel += " ("
-
- if len(eventsListing) > self.maxX - 11:
- labelBreak = eventsListing[:self.maxX - 12].rfind(", ")
- eventsLabel += "%s..." % eventsListing[:labelBreak]
- elif len(eventsListing) + len(filterLabel) > self.maxX - 11:
- eventsLabel += eventsListing
- else: eventsLabel += eventsListing + filterLabel
- eventsLabel += ")"
- eventsLabel += ":"
-
- self.addstr(0, 0, eventsLabel, util.LABEL_ATTR)
-
- # log entries
- maxLoc = self.getLogDisplayLength() - self.maxY + 1
- self.scroll = max(0, min(self.scroll, maxLoc))
- lineCount = 1 - self.scroll
-
- for (line, color) in self.msgLog:
- if self.regexFilter and not self.regexFilter.search(line):
- continue # filter doesn't match log message - skip
-
- # splits over too lines if too long
- if len(line) < self.maxX:
- if lineCount >= 1: self.addstr(lineCount, xOffset, line, util.getColor(color))
- lineCount += 1
- else:
- (line1, line2) = splitLine(line, self.maxX - xOffset)
- if lineCount >= 1: self.addstr(lineCount, xOffset, line1, util.getColor(color))
- if lineCount >= 0: self.addstr(lineCount + 1, xOffset, line2, util.getColor(color))
- lineCount += 2
-
- if lineCount >= self.maxY: break # further log messages wouldn't fit
-
- if isScrollBarVisible: util.drawScrollBar(self, 1, self.maxY - 1, self.scroll, self.scroll + self.maxY - 1, self.getLogDisplayLength())
- self.refresh()
- finally:
- self.lock.release()
+ isScrollBarVisible = self.getLogDisplayLength() > self.maxY - 1
+ xOffset = 3 if isScrollBarVisible else 0 # content offset for scroll bar
+
+ # draws label - uses ellipsis if too long, for instance:
+ # Events (DEBUG, INFO, NOTICE, WARN...):
+ eventsLabel = "Events"
+
+ # separates tor and arm runlevels (might be able to show as range)
+ eventsList = list(self.loggedEvents)
+ torRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, ""))
+ armRunlevelLabel = ", ".join(parseRunlevelRanges(eventsList, "ARM_"))
+
+ if armRunlevelLabel: eventsList = ["ARM " + armRunlevelLabel] + eventsList
+ if torRunlevelLabel: eventsList = [torRunlevelLabel] + eventsList
+
+ eventsListing = ", ".join(eventsList)
+ filterLabel = "" if not self.regexFilter else " - filter: %s" % self.regexFilter.pattern
+
+ firstLabelLen = eventsListing.find(", ")
+ if firstLabelLen == -1: firstLabelLen = len(eventsListing)
+ else: firstLabelLen += 3
+
+ if self.maxX > 10 + firstLabelLen:
+ eventsLabel += " ("
+
+ if len(eventsListing) > self.maxX - 11:
+ labelBreak = eventsListing[:self.maxX - 12].rfind(", ")
+ eventsLabel += "%s..." % eventsListing[:labelBreak]
+ elif len(eventsListing) + len(filterLabel) > self.maxX - 11:
+ eventsLabel += eventsListing
+ else: eventsLabel += eventsListing + filterLabel
+ eventsLabel += ")"
+ eventsLabel += ":"
+
+ self.addstr(0, 0, eventsLabel, uiTools.LABEL_ATTR)
+
+ # log entries
+ maxLoc = self.getLogDisplayLength() - self.maxY + 1
+ self.scroll = max(0, min(self.scroll, maxLoc))
+ lineCount = 1 - self.scroll
+
+ for (line, color) in self.msgLog:
+ if self.regexFilter and not self.regexFilter.search(line):
+ continue # filter doesn't match log message - skip
+
+ # splits over too lines if too long
+ if len(line) < self.maxX:
+ if lineCount >= 1: self.addstr(lineCount, xOffset, line, uiTools.getColor(color))
+ lineCount += 1
+ else:
+ (line1, line2) = splitLine(line, self.maxX - xOffset)
+ if lineCount >= 1: self.addstr(lineCount, xOffset, line1, uiTools.getColor(color))
+ if lineCount >= 0: self.addstr(lineCount + 1, xOffset, line2, uiTools.getColor(color))
+ lineCount += 2
+
+ if lineCount >= self.maxY: break # further log messages wouldn't fit
+
+ if isScrollBarVisible: uiTools.drawScrollBar(self, 1, self.maxY - 1, self.scroll, self.scroll + self.maxY - 1, self.getLogDisplayLength())
def getLogDisplayLength(self):
"""
Deleted: arm/trunk/interface/util.py
===================================================================
--- arm/trunk/interface/util.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/interface/util.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,282 +0,0 @@
-#!/usr/bin/env python
-# util.py -- support functions common for arm user interface.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-import curses
-from sys import maxint
-
-LABEL_ATTR = curses.A_STANDOUT # default formatting constant
-
-# colors curses can handle
-COLOR_LIST = (("red", curses.COLOR_RED),
- ("green", curses.COLOR_GREEN),
- ("yellow", curses.COLOR_YELLOW),
- ("blue", curses.COLOR_BLUE),
- ("cyan", curses.COLOR_CYAN),
- ("magenta", curses.COLOR_MAGENTA),
- ("black", curses.COLOR_BLACK),
- ("white", curses.COLOR_WHITE))
-
-FORMAT_TAGS = {"<b>": curses.A_BOLD,
- "<u>": curses.A_UNDERLINE,
- "<h>": curses.A_STANDOUT}
-for (colorLabel, cursesAttr) in COLOR_LIST: FORMAT_TAGS["<%s>" % colorLabel] = curses.A_NORMAL
-
-# foreground color mappings (starts uninitialized - all colors associated with default white fg / black bg)
-COLOR_ATTR_INITIALIZED = False
-COLOR_ATTR = dict([(color[0], 0) for color in COLOR_LIST])
-
-def initColors():
- """
- Initializes color mappings for the current curses. This needs to be called
- after curses.initscr().
- """
-
- global COLOR_ATTR_INITIALIZED
- if not COLOR_ATTR_INITIALIZED:
- COLOR_ATTR_INITIALIZED = True
-
- # if color support is available initializes color mappings
- if curses.has_colors():
- colorpair = 0
-
- for name, fgColor in COLOR_LIST:
- colorpair += 1
- curses.init_pair(colorpair, fgColor, -1) # -1 allows for default (possibly transparent) background
- COLOR_ATTR[name] = curses.color_pair(colorpair)
-
- # maps color tags to initialized attributes
- for colorLabel in COLOR_ATTR.keys(): FORMAT_TAGS["<%s>" % colorLabel] = COLOR_ATTR[colorLabel]
-
-def getColor(color):
- """
- Provides attribute corresponding to a given text color. Supported colors
- include:
- red, green, yellow, blue, cyan, magenta, black, and white
-
- If color support isn't available then this uses the default terminal coloring
- scheme.
- """
-
- return COLOR_ATTR[color]
-
-def getSizeLabel(bytes, decimal = 0):
- """
- Converts byte count into label in its most significant units, for instance
- 7500 bytes would return "7 KB".
- """
-
- format = "%%.%if" % decimal
- if bytes >= 1073741824: return (format + " GB") % (bytes / 1073741824.0)
- elif bytes >= 1048576: return (format + " MB") % (bytes / 1048576.0)
- elif bytes >= 1024: return (format + " KB") % (bytes / 1024.0)
- else: return "%i bytes" % bytes
-
-def getTimeLabel(seconds, decimal = 0):
- """
- Concerts seconds into a time label truncated to its most significant units,
- for instance 7500 seconds would return "2h". Units go up through days.
- """
-
- format = "%%.%if" % decimal
- if seconds >= 86400: return (format + "d") % (seconds / 86400.0)
- elif seconds >= 3600: return (format + "h") % (seconds / 3600.0)
- elif seconds >= 60: return (format + "m") % (seconds / 60.0)
- else: return "%is" % seconds
-
-def drawScrollBar(panel, drawTop, drawBottom, top, bottom, size):
- """
- Draws scroll bar reflecting position within a vertical listing. This is
- squared off at the bottom, having a layout like:
- |
- *|
- *|
- *|
- |
- -+
- """
-
- barTop = (drawBottom - drawTop) * top / size
- barSize = (drawBottom - drawTop) * (bottom - top) / size
-
- # makes sure bar isn't at top or bottom unless really at those extreme bounds
- if top > 0: barTop = max(barTop, 1)
- if bottom != size: barTop = min(barTop, drawBottom - drawTop - barSize - 2)
-
- for i in range(drawBottom - drawTop):
- if i >= barTop and i <= barTop + barSize:
- panel.addstr(i + drawTop, 0, " ", curses.A_STANDOUT)
-
- # draws box around scroll bar
- panel.win.vline(drawTop, 1, curses.ACS_VLINE, panel.maxY - 2)
- panel.win.vline(drawBottom, 1, curses.ACS_LRCORNER, 1)
- panel.win.hline(drawBottom, 0, curses.ACS_HLINE, 1)
-
-class Panel():
- """
- Wrapper for curses subwindows. This provides safe proxies to common methods
- and is extended by panels.
- """
-
- def __init__(self, lock, height):
- self.win = None # associated curses subwindow
- self.lock = lock # global curses lock
- self.startY = -1 # top in parent window when created
- self.height = height # preferred (max) height of panel, -1 if infinite
- self.isDisplaced = False # window isn't in the right location - don't redraw
- self.maxY, self.maxX = -1, -1
- self._resetBounds() # sets last known dimensions of win (maxX and maxY)
-
- def redraw(self):
- pass # overwritten by implementations
-
- def recreate(self, stdscr, startY, maxX=-1):
- """
- Creates a new subwindow for the panel if:
- - panel currently doesn't have a subwindow
- - the panel is being moved (startY is different)
- - there's room for the panel to grow
-
- Returns True if subwindow's created, False otherwise.
- """
-
- # I'm not sure if recreating subwindows is some sort of memory leak but the
- # Python curses bindings seem to lack all of the following:
- # - subwindow deletion (to tell curses to free the memory)
- # - subwindow moving/resizing (to restore the displaced windows)
- # so this is the only option (besides removing subwindows entirly which
- # would mean more complicated code and no more selective refreshing)
-
- y, x = stdscr.getmaxyx()
- self._resetBounds()
-
- if self.win and startY > y:
- return False # trying to make panel out of bounds
-
- newHeight = max(0, y - startY)
- if self.height != -1: newHeight = min(newHeight, self.height)
-
- if self.startY != startY or newHeight != self.maxY or self.isDisplaced or (self.maxX != maxX and maxX != -1):
- # window resized or moving - recreate
- self.startY = startY
- startY = min(startY, y - 1) # better create a displaced window than leave it as None
- if maxX != -1: x = min(x, maxX)
-
- self.win = stdscr.subwin(newHeight, x, startY, 0)
- return True
- else: return False
-
- def clear(self):
- """
- Erases window and resets bounds used in writting to it.
- """
-
- if self.win:
- self.isDisplaced = self.startY > self.win.getparyx()[0]
- if not self.isDisplaced: self.win.erase()
- self._resetBounds()
-
- def refresh(self):
- """
- Proxy for window refresh.
- """
-
- if self.win and not self.isDisplaced: self.win.refresh()
-
- def addstr(self, y, x, msg, attr=curses.A_NORMAL):
- """
- Writes string to subwindow if able. This takes into account screen bounds
- to avoid making curses upset.
- """
-
- # subwindows need a character buffer (either in the x or y direction) from
- # actual content to prevent crash when shrank
- if self.win and self.maxX > x and self.maxY > y and not self.isDisplaced:
- self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
-
- def addfstr(self, y, x, msg):
- """
- Writes string to subwindow. The message can contain xhtml-style tags for
- formatting, including:
- <b>text</b> bold
- <u>text</u> underline
- <h>text</h> highlight
- <[color]>text</[color]> use color (see COLOR_LIST for constants)
-
- Tag nexting is supported and tag closing is not strictly enforced. This
- does not valididate input and unrecognized tags are treated as normal text.
- Currently this funtion has the following restrictions:
- - Duplicate tags nested (such as "<b><b>foo</b></b>") is invalid and may
- throw an error.
- - Color tags shouldn't be nested in each other (results are undefined).
- """
-
- if self.win and self.maxY > y and not self.isDisplaced:
- formatting = [curses.A_NORMAL]
- expectedCloseTags = []
-
- while self.maxX > x and len(msg) > 0:
- # finds next consumeable tag
- nextTag, nextTagIndex = None, maxint
-
- for tag in FORMAT_TAGS.keys() + expectedCloseTags:
- tagLoc = msg.find(tag)
- if tagLoc != -1 and tagLoc < nextTagIndex:
- nextTag, nextTagIndex = tag, tagLoc
-
- # splits into text before and after tag
- if nextTag:
- msgSegment = msg[:nextTagIndex]
- msg = msg[nextTagIndex + len(nextTag):]
- else:
- msgSegment = msg
- msg = ""
-
- # adds text before tag with current formatting
- attr = 0
- for format in formatting: attr |= format
- self.win.addstr(y, x, msgSegment[:self.maxX - x - 1], attr)
-
- # applies tag attributes for future text
- if nextTag:
- if not nextTag.startswith("</"):
- # open tag - add formatting
- expectedCloseTags.append("</" + nextTag[1:])
- formatting.append(FORMAT_TAGS[nextTag])
- else:
- # close tag - remove formatting
- expectedCloseTags.remove(nextTag)
- formatting.remove(FORMAT_TAGS["<" + nextTag[2:]])
-
- x += len(msgSegment)
-
- def addstr_wrap(self, y, x, text, formatting, startX = 0, endX = -1, maxY = -1):
- """
- Writes text with word wrapping, returning the ending y/x coordinate.
- y: starting write line
- x: column offset from startX
- text / formatting: content to be written
- startX / endX: column bounds in which text may be written
- """
-
- if not text: return (y, x) # nothing to write
- if endX == -1: endX = self.maxX # defaults to writing to end of panel
- if maxY == -1: maxY = self.maxY + 1 # defaults to writing to bottom of panel
- lineWidth = endX - startX # room for text
- while True:
- if len(text) > lineWidth - x - 1:
- chunkSize = text.rfind(" ", 0, lineWidth - x)
- writeText = text[:chunkSize]
- text = text[chunkSize:].strip()
-
- self.addstr(y, x + startX, writeText, formatting)
- y, x = y + 1, 0
- if y >= maxY: return (y, x)
- else:
- self.addstr(y, x + startX, text, formatting)
- return (y, x + len(text))
-
- def _resetBounds(self):
- if self.win: self.maxY, self.maxX = self.win.getmaxyx()
- else: self.maxY, self.maxX = -1, -1
-
Added: arm/trunk/util/__init__.py
===================================================================
--- arm/trunk/util/__init__.py (rev 0)
+++ arm/trunk/util/__init__.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,8 @@
+"""
+General purpose utilities for a variety of tasks including logging the
+application's status, making cross platform system calls, parsing tor data,
+and safely working with curses (hiding some of the gory details).
+"""
+
+__all__ = ["panel", "uiTools"]
+
Added: arm/trunk/util/panel.py
===================================================================
--- arm/trunk/util/panel.py (rev 0)
+++ arm/trunk/util/panel.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,230 @@
+"""
+Wrapper for safely working with curses subwindows.
+"""
+
+import curses
+from sys import maxint
+from threading import RLock
+
+import uiTools
+
+# TODO: external usage of clear and refresh are only used by popup (remove when alternative is used)
+
+# global ui lock governing all panel instances (curses isn't thread save and
+# concurrency bugs produce especially sinister glitches)
+CURSES_LOCK = RLock()
+
+class Panel():
+ """
+ Wrapper for curses subwindows. This hides most of the ugliness in common
+ curses operations including:
+ - locking when concurrently drawing to multiple windows
+ - gracefully handle terminal resizing
+ - clip text that falls outside the panel
+ - convenience methods for word wrap, inline formatting, etc
+
+ This can't be used until it has a subwindow instance, which is done via the
+ recreate() function. Until this is done the top, maxX, and maxY parameters
+ are defaulted to -1.
+
+ Parameters:
+ win - current curses subwindow
+ height - preferred (max) height of panel, -1 if infinite
+ top - upper Y-coordinate within parent window
+ maxX, maxY - cached bounds of subwindow
+ """
+
+ def __init__(self, height):
+ self.win = None
+ self.height = height
+ self.top = -1
+ self.maxY, self.maxX = -1, -1
+
+ # when the terminal is shrank then expanded curses attempts to draw
+ # displaced windows in the wrong location - this results in graphical
+ # glitches if we let the panel be redrawn
+ self.isDisplaced = True
+
+ def draw(self):
+ """
+ Draws display's content. This is meant to be overwriten by
+ impelementations.
+ """
+
+ pass
+
+ def redraw(self, block=False):
+ """
+ Clears display and redraws.
+ """
+
+ if self.win:
+ if not CURSES_LOCK.acquire(block): return
+ try:
+ self.clear()
+ self.draw()
+ self.refresh()
+ finally:
+ CURSES_LOCK.release()
+
+ def recreate(self, stdscr, newWidth=-1, newTop=None):
+ """
+ Creates a new subwindow for the panel if:
+ - panel currently doesn't have a subwindow
+ - the panel is being moved (top is different from newTop)
+ - there's room for the panel to grow
+
+ Returns True if subwindow's created, False otherwise.
+ """
+
+ if newTop == None: newTop = self.top
+
+ # I'm not sure if recreating subwindows is some sort of memory leak but the
+ # Python curses bindings seem to lack all of the following:
+ # - subwindow deletion (to tell curses to free the memory)
+ # - subwindow moving/resizing (to restore the displaced windows)
+ # so this is the only option (besides removing subwindows entirly which
+ # would mean more complicated code and no more selective refreshing)
+
+ y, x = stdscr.getmaxyx()
+ self._resetBounds()
+
+ if self.win and newTop > y:
+ return False # trying to make panel out of bounds
+
+ newHeight = max(0, y - newTop)
+ if self.height != -1: newHeight = min(newHeight, self.height)
+
+ recreate = False
+ recreate |= self.top != newTop # position has shifted
+ recreate |= newHeight != self.maxY # subwindow can grow (vertically)
+ recreate |= self.isDisplaced # resizing has bumped subwindow out of position
+ recreate |= self.maxX != newWidth and newWidth != -1 # set to use a new width
+
+ if recreate:
+ if newWidth == -1: newWidth = x
+ else: newWidth = min(newWidth, x)
+
+ self.top = newTop
+ newTop = min(newTop, y - 1) # better create a displaced window than leave it as None
+
+ self.win = stdscr.subwin(newHeight, newWidth, newTop, 0)
+ return True
+ else: return False
+
+ # TODO: merge into repaint when no longer needed
+ def clear(self):
+ """
+ Erases window and resets bounds used in writting to it.
+ """
+
+ if self.win:
+ self.isDisplaced = self.top > self.win.getparyx()[0]
+ if not self.isDisplaced: self.win.erase()
+ self._resetBounds()
+
+ # TODO: merge into repaint when no longer needed
+ def refresh(self):
+ """
+ Proxy for window refresh.
+ """
+
+ if self.win and not self.isDisplaced: self.win.refresh()
+
+ def addstr(self, y, x, msg, attr=curses.A_NORMAL):
+ """
+ Writes string to subwindow if able. This takes into account screen bounds
+ to avoid making curses upset.
+ """
+
+ # subwindows need a single character buffer (either in the x or y
+ # direction) from actual content to prevent crash when shrank
+ if self.win and self.maxX > x and self.maxY > y and not self.isDisplaced:
+ self.win.addstr(y, x, msg[:self.maxX - x - 1], attr)
+
+ def addfstr(self, y, x, msg):
+ """
+ Writes string to subwindow. The message can contain xhtml-style tags for
+ formatting, including:
+ <b>text</b> bold
+ <u>text</u> underline
+ <h>text</h> highlight
+ <[color]>text</[color]> use color (see COLOR_LIST for constants)
+
+ Tag nexting is supported and tag closing is not strictly enforced. This
+ does not valididate input and unrecognized tags are treated as normal text.
+ Currently this funtion has the following restrictions:
+ - Duplicate tags nested (such as "<b><b>foo</b></b>") is invalid and may
+ throw an error.
+ - Color tags shouldn't be nested in each other (results are undefined).
+ """
+
+ if self.win and self.maxY > y and not self.isDisplaced:
+ formatting = [curses.A_NORMAL]
+ expectedCloseTags = []
+
+ while self.maxX > x and len(msg) > 0:
+ # finds next consumeable tag
+ nextTag, nextTagIndex = None, maxint
+
+ for tag in uiTools.FORMAT_TAGS.keys() + expectedCloseTags:
+ tagLoc = msg.find(tag)
+ if tagLoc != -1 and tagLoc < nextTagIndex:
+ nextTag, nextTagIndex = tag, tagLoc
+
+ # splits into text before and after tag
+ if nextTag:
+ msgSegment = msg[:nextTagIndex]
+ msg = msg[nextTagIndex + len(nextTag):]
+ else:
+ msgSegment = msg
+ msg = ""
+
+ # adds text before tag with current formatting
+ attr = 0
+ for format in formatting: attr |= format
+ self.win.addstr(y, x, msgSegment[:self.maxX - x - 1], attr)
+
+ # applies tag attributes for future text
+ if nextTag:
+ if not nextTag.startswith("</"):
+ # open tag - add formatting
+ expectedCloseTags.append("</" + nextTag[1:])
+ formatting.append(uiTools.FORMAT_TAGS[nextTag])
+ else:
+ # close tag - remove formatting
+ expectedCloseTags.remove(nextTag)
+ formatting.remove(uiTools.FORMAT_TAGS["<" + nextTag[2:]])
+
+ x += len(msgSegment)
+
+ def addstr_wrap(self, y, x, text, formatting, startX = 0, endX = -1, maxY = -1):
+ """
+ Writes text with word wrapping, returning the ending y/x coordinate.
+ y: starting write line
+ x: column offset from startX
+ text / formatting: content to be written
+ startX / endX: column bounds in which text may be written
+ """
+
+ if not text: return (y, x) # nothing to write
+ if endX == -1: endX = self.maxX # defaults to writing to end of panel
+ if maxY == -1: maxY = self.maxY + 1 # defaults to writing to bottom of panel
+ lineWidth = endX - startX # room for text
+ while True:
+ if len(text) > lineWidth - x - 1:
+ chunkSize = text.rfind(" ", 0, lineWidth - x)
+ writeText = text[:chunkSize]
+ text = text[chunkSize:].strip()
+
+ self.addstr(y, x + startX, writeText, formatting)
+ y, x = y + 1, 0
+ if y >= maxY: return (y, x)
+ else:
+ self.addstr(y, x + startX, text, formatting)
+ return (y, x + len(text))
+
+ def _resetBounds(self):
+ if self.win: self.maxY, self.maxX = self.win.getmaxyx()
+ else: self.maxY, self.maxX = -1, -1
+
Added: arm/trunk/util/uiTools.py
===================================================================
--- arm/trunk/util/uiTools.py (rev 0)
+++ arm/trunk/util/uiTools.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+# util.py -- support functions common for arm user interface.
+# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
+
+import curses
+
+LABEL_ATTR = curses.A_STANDOUT # default formatting constant
+
+# colors curses can handle
+COLOR_LIST = (("red", curses.COLOR_RED),
+ ("green", curses.COLOR_GREEN),
+ ("yellow", curses.COLOR_YELLOW),
+ ("blue", curses.COLOR_BLUE),
+ ("cyan", curses.COLOR_CYAN),
+ ("magenta", curses.COLOR_MAGENTA),
+ ("black", curses.COLOR_BLACK),
+ ("white", curses.COLOR_WHITE))
+
+FORMAT_TAGS = {"<b>": curses.A_BOLD,
+ "<u>": curses.A_UNDERLINE,
+ "<h>": curses.A_STANDOUT}
+for (colorLabel, cursesAttr) in COLOR_LIST: FORMAT_TAGS["<%s>" % colorLabel] = curses.A_NORMAL
+
+# foreground color mappings (starts uninitialized - all colors associated with default white fg / black bg)
+COLOR_ATTR_INITIALIZED = False
+COLOR_ATTR = dict([(color[0], 0) for color in COLOR_LIST])
+
+def initColors():
+ """
+ Initializes color mappings for the current curses. This needs to be called
+ after curses.initscr().
+ """
+
+ global COLOR_ATTR_INITIALIZED
+ if not COLOR_ATTR_INITIALIZED:
+ COLOR_ATTR_INITIALIZED = True
+
+ # if color support is available initializes color mappings
+ if curses.has_colors():
+ colorpair = 0
+
+ for name, fgColor in COLOR_LIST:
+ colorpair += 1
+ curses.init_pair(colorpair, fgColor, -1) # -1 allows for default (possibly transparent) background
+ COLOR_ATTR[name] = curses.color_pair(colorpair)
+
+ # maps color tags to initialized attributes
+ for colorLabel in COLOR_ATTR.keys(): FORMAT_TAGS["<%s>" % colorLabel] = COLOR_ATTR[colorLabel]
+
+def getColor(color):
+ """
+ Provides attribute corresponding to a given text color. Supported colors
+ include:
+ red, green, yellow, blue, cyan, magenta, black, and white
+
+ If color support isn't available then this uses the default terminal coloring
+ scheme.
+ """
+
+ return COLOR_ATTR[color]
+
+def getSizeLabel(bytes, decimal = 0):
+ """
+ Converts byte count into label in its most significant units, for instance
+ 7500 bytes would return "7 KB".
+ """
+
+ format = "%%.%if" % decimal
+ if bytes >= 1073741824: return (format + " GB") % (bytes / 1073741824.0)
+ elif bytes >= 1048576: return (format + " MB") % (bytes / 1048576.0)
+ elif bytes >= 1024: return (format + " KB") % (bytes / 1024.0)
+ else: return "%i bytes" % bytes
+
+def getTimeLabel(seconds, decimal = 0):
+ """
+ Concerts seconds into a time label truncated to its most significant units,
+ for instance 7500 seconds would return "2h". Units go up through days.
+ """
+
+ format = "%%.%if" % decimal
+ if seconds >= 86400: return (format + "d") % (seconds / 86400.0)
+ elif seconds >= 3600: return (format + "h") % (seconds / 3600.0)
+ elif seconds >= 60: return (format + "m") % (seconds / 60.0)
+ else: return "%is" % seconds
+
+def drawScrollBar(panel, drawTop, drawBottom, top, bottom, size):
+ """
+ Draws scroll bar reflecting position within a vertical listing. This is
+ squared off at the bottom, having a layout like:
+ |
+ *|
+ *|
+ *|
+ |
+ -+
+ """
+
+ if panel.maxY < 2: return # not enough room
+
+ barTop = (drawBottom - drawTop) * top / size
+ barSize = (drawBottom - drawTop) * (bottom - top) / size
+
+ # makes sure bar isn't at top or bottom unless really at those extreme bounds
+ if top > 0: barTop = max(barTop, 1)
+ if bottom != size: barTop = min(barTop, drawBottom - drawTop - barSize - 2)
+
+ for i in range(drawBottom - drawTop):
+ if i >= barTop and i <= barTop + barSize:
+ panel.addstr(i + drawTop, 0, " ", curses.A_STANDOUT)
+
+ # draws box around scroll bar
+ panel.win.vline(drawTop, 1, curses.ACS_VLINE, panel.maxY - 2)
+ panel.win.vline(drawBottom, 1, curses.ACS_LRCORNER, 1)
+ panel.win.hline(drawBottom, 0, curses.ACS_HLINE, 1)
+
Deleted: arm/trunk/versionCheck.py
===================================================================
--- arm/trunk/versionCheck.py 2010-02-14 22:38:33 UTC (rev 21645)
+++ arm/trunk/versionCheck.py 2010-02-15 01:01:34 UTC (rev 21646)
@@ -1,17 +0,0 @@
-#!/usr/bin/env python
-# versionCheck.py -- Provides a warning and error code if python version isn't compatible.
-# Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)
-
-import sys
-
-if __name__ == '__main__':
- majorVersion = sys.version_info[0]
- minorVersion = sys.version_info[1]
-
- if majorVersion > 2:
- print("arm isn't compatible beyond the python 2.x series\n")
- sys.exit(1)
- elif majorVersion < 2 or minorVersion < 5:
- print("arm requires python version 2.5 or greater\n")
- sys.exit(1)
-
More information about the tor-commits
mailing list