Compare commits

..

291 commits

Author SHA1 Message Date
0e5270f8d5 fix remaining 'implements'... 2025-02-22 11:31:19 +01:00
fe632fbeab more Python3 fixes (unicode, basestring) 2025-01-11 11:35:21 +01:00
860d18cae8 Python3 fix for schema.grid 2025-01-07 08:46:31 +01:00
569e197609 more minor Python3 fixes 2024-11-24 10:18:21 +01:00
12978d2389 tracks: determine indexAttributes dynamically, allowing later change 2024-11-24 10:11:25 +01:00
df1229d8fd minor fixes for avoiding errors with Python3 / up-to-date libraries 2024-10-26 19:22:59 +02:00
93a2acf7db some more Python3 fixes 2024-10-03 17:00:16 +02:00
25277a0b65 avoid testing deprecation errors with Python3.12 2024-10-02 10:14:22 +02:00
aef9ad8cb5 make Python3.12 happy 2024-10-02 09:45:57 +02:00
814d7c0762 changes to allow zodbupdate to migrate old data 2024-10-02 09:08:16 +02:00
8a6277fbff some more Python3 fixes 2024-10-01 16:09:46 +02:00
2cbd4c11d3 fix UID handling in commerce 2024-10-01 15:40:38 +02:00
968aeab42a one more Python3 fix in ajax 2024-09-29 12:04:56 +02:00
f1080df88b use zope-testrunner for running all relevant tests => more Python3 fixes 2024-09-28 11:42:20 +02:00
33ab512b7f some more Python3 fixes (composer.report, tracking) 2024-09-26 16:24:24 +02:00
17fbeb2c2c browser.loops.auth: Python3 fix 2024-09-25 17:05:19 +02:00
52e3fc72c6 more Python3 fixes in composer, knowledge 2024-09-25 10:57:30 +02:00
db31a73bc9 container: Python3 fix 2024-09-25 09:31:49 +02:00
ee82ee7b32 composer: some more Python3 fixes 2024-09-24 19:24:25 +02:00
71f36283b9 Merge branch '2master' into 3master 2024-09-24 17:15:06 +02:00
d66aa31058 more Python3 fixes (browser.action, composer...field) 2024-09-24 17:11:06 +02:00
5f3bd3c04f composer: more Python3 fixes 2024-09-24 11:14:22 +02:00
b22dbf879c remove deprecated zope.app.container references 2024-09-23 22:50:46 +02:00
9fbd97386e some more Python3 fixes... 2024-09-23 15:35:14 +02:00
b8eba239ed typology: Python3 fixes; + update of example data 2024-09-23 15:05:10 +02:00
61c78fe3e7 reporter: Python3 fixes 2024-09-23 13:12:33 +02:00
3ec90f4b66 relation: Python3 fixes 2024-09-23 12:17:52 +02:00
f9a3326ec7 process: Python3 fixes 2024-09-23 11:14:21 +02:00
8037ac38be organize: Python3 fixes 2024-09-23 11:00:35 +02:00
52990d1df6 work in progress: organize: Python3 fixes 2024-09-22 18:10:02 +02:00
8d260908a5 meta: Python3 fixes 2024-09-22 17:16:33 +02:00
b60d6d3b10 external: some Python3 fixes 2024-09-22 16:12:36 +02:00
e98dd2ed34 media: Python3 fixes 2024-09-22 15:24:24 +02:00
c9220d834d linK: Python3 fixes 2024-09-22 14:46:28 +02:00
383b77edf1 knowledge: Python3 fixes 2024-09-22 13:31:58 +02:00
b866ac4267 integrator.filesystem: Python3 fixes 2024-09-22 13:25:46 +02:00
ee9d062833 docgen: Python3 fixes 2024-09-22 11:16:47 +02:00
4b84e816b4 container: Python3 fixes 2024-09-22 10:41:30 +02:00
80766f2279 composer, util: Python3 fixes 2024-09-22 10:24:02 +02:00
87ca77df45 commerce: Python3 fixes, + composer (.base, .schema) 2024-09-21 22:36:59 +02:00
c5fe028756 catalog: Python3 fixes (+ some fixes in relation) 2024-09-21 17:04:16 +02:00
ad632e23ee browser: Python3 fixes 2024-09-21 16:12:03 +02:00
98ebc30bd5 tracking: tests OK, including some fixes in container 2024-09-21 15:04:57 +02:00
bd631677d6 brain: fixes (zope.interface.implementer); + text: improvements 2024-09-21 14:27:15 +02:00
06682d2a5c define project via pyproject.toml, removing setup.py; + fixes => cybertools.text tests OK 2024-09-21 09:06:42 +02:00
35ab24a78a tracking: add updateIndex() method 2024-05-03 13:08:35 +02:00
a6fb663192 minor additions for compatibility with new cco.storage implementation 2024-01-03 11:07:55 +01:00
94ce64ef00 add RecordsTable field - context-based, i.e. the columns are defined in the object or type 2023-12-28 09:09:00 +01:00
e5f1be9f91 provide method for automatic conversion from KeyTable to Records field 2023-12-25 10:52:45 +01:00
abfd75a782 date2TimeStamp(): accept also int and float as input 2023-12-21 08:18:49 +01:00
6a52601060 move tracking migration to loops 2023-12-15 14:03:35 +01:00
d6666c166f first migration (with favorites) basically working 2023-12-11 09:46:27 +01:00
382ffe2e28 add MIT license file as default for new or changed modules 2023-12-11 08:24:54 +01:00
6f86e74feb work in progress: migration of records (tracking) - main development in python2 (2master) branch 2023-12-11 08:19:49 +01:00
1fb58a7db3 remove copyright comments - see LICENSE file 2023-11-29 13:19:47 +01:00
fad566b354 make package cybertools.tracking Python3-ready 2023-11-29 11:48:36 +01:00
43ea46e401 cybertools.text: OK on py3 2023-11-27 18:49:05 +01:00
Hannes Plattner
f5c80948c0 refix 2023-06-28 13:54:51 +02:00
Hannes Plattner
88f8b968e6 fix method call 2023-06-28 13:50:28 +02:00
zope
674cd62ae6 avoid error when timestamp is already a date 2021-11-24 09:32:41 +01:00
zope
1629defe3d add parameter usePredicateIndex to getRelations() 2021-08-07 11:44:01 +02:00
Hannes Plattner
bd1aa11beb Merge branch '2master' of ssh://git.cy55.de/home/git/cybertools into 2master 2020-10-13 13:22:40 +02:00
Hannes Plattner
46582b51fe fix instance default value getter 2020-10-13 13:22:21 +02:00
59cd18cc63 fix import: stop deprecation warning 2020-05-04 16:30:01 +02:00
ab72a31bf0 merge field methods correctly 2020-04-15 11:56:56 +02:00
a8565f58c3 don't suppress 'hidden' fields 2020-04-08 14:52:51 +02:00
51be7dbcbb avoid deprecation warnings, fix doctests 2020-03-07 13:44:10 +01:00
f2eae818db set version number to 2.3.0 2020-03-07 12:51:33 +01:00
3c8edc3e90 merge bbmaster + bbmaster2 to new branch 2master 2020-03-03 15:22:13 +01:00
83e18e10c1 avoid error caused by missing table columns 2019-11-25 19:20:07 +01:00
df15344db5 fix tests/doctests according to current ZTK and BlueBream versions 2019-04-26 17:42:10 +02:00
6ee25a78a4 avoid template compilation error with new ZPT version 2019-04-26 11:25:39 +02:00
69513bdd9f avoid years before 1900 that cannot be formatted 2019-04-26 11:08:02 +02:00
b690273bf0 update version number 2019-03-09 13:24:14 +01:00
9f1bbb5193 fix moving of work items 2018-01-09 07:55:06 +01:00
hplattner
c5715c322d subtotals fixes 2017-10-11 14:21:13 +02:00
hplattner
0208aa5595 add getExportValue method for report fields 2017-02-09 13:44:53 +01:00
93249faf94 fix check for workitem type 2017-01-16 15:23:48 +01:00
bd3d7a0e2f fix action start: 'party' query only works in adapter 2017-01-16 15:12:50 +01:00
3b53657c5d fix MANIFEST.in: include missing files/directories 2017-01-02 16:07:09 +01:00
2e09bb3738 minor package-level changes, add README file 2017-01-01 17:21:32 +01:00
82bc58329b dirty fix: monkey patch in IntIds to avoid ForbiddenAttribute error 2017-01-01 17:19:52 +01:00
d385dc8dea make package ready for distribution as an sdist egg 2016-12-31 13:43:05 +01:00
hplattner
aab06b0cc9 use subtotals row id instead of class concat 2016-12-28 11:40:09 +01:00
c03e47ab3d cleanup pagetemplate code with string: literals 2016-12-26 18:14:15 +01:00
325f463ce8 remove deprecated import from zope.testing 2016-12-26 11:26:10 +01:00
9205e8592e remove deprecated import from zope.testing 2016-12-26 10:42:27 +01:00
170e8f1d4e allow hiding of a field from schema-based forms 2016-12-23 17:14:11 +01:00
hplattner
e26d61c38b use cssClass from row property 2016-12-12 11:50:17 +01:00
4d493dda45 second fix: call normalizeName only for strings 2016-11-30 18:10:16 +01:00
173e0d4140 now the real fix: call normalizeName only for strings 2016-11-30 13:48:57 +01:00
hplattner
64d7f4e701 fix 2016-11-30 13:42:00 +01:00
519140aa6d use 'suppress' property name instead of 'hidden' (which is used already) 2016-11-30 10:10:00 +01:00
b6833af5f9 allow simple hiding of a field from a schema 2016-11-29 13:08:29 +01:00
hplattner
78748051e2 add report row properties for js calculating totals and subtotals 2016-11-25 16:38:27 +01:00
78969355b6 backport from bbmaster2: add field property 'visible'; add FieldGroup 2016-10-08 14:13:33 +02:00
5aa9869181 add 'visible' property to IField interface 2016-10-08 14:05:19 +02:00
4c18a9731c add 'visible' property to field definition 2016-10-08 11:17:39 +02:00
6cff2cc6df update copyright info 2016-10-08 09:37:47 +02:00
473dd10029 allow schema processor plugin adapter 2016-10-04 07:56:26 +02:00
1cd5f59738 call schema processor with **kw to provide view/request 2016-08-27 09:31:56 +02:00
d742b87c9c allow schema processor plugin adapter 2016-08-25 16:41:40 +02:00
ef05da60e1 take URLGetter implementation from common location 2016-06-28 08:14:02 +02:00
0d1a37b5d2 provide method for tweaking request/URL, esp remove trailing '/@@index.html' 2016-06-17 09:43:29 +02:00
81ae451969 fix stripping of '/@@index.html' 2016-06-13 10:01:47 +02:00
443fee52bb fix URL clipping 2016-06-12 09:04:30 +02:00
813299d358 revert change in main.pt, makes problems with relative URLs 2016-06-10 12:35:28 +02:00
34d65623ad revert change for request/URL 2016-06-09 13:43:11 +02:00
043d423a0e use new 'view/requestUrl' to avoid '/@@index.html' in URLs 2016-06-09 10:32:04 +02:00
8777d1ecdb provide 'view/requestUrl' method to be used instead of 'request/URL' 2016-06-09 09:03:44 +02:00
8166a0e3c3 add green/blue LED icon 2016-06-09 09:03:03 +02:00
2a2097fa0d tracking: allow wildcard (*) query 2016-04-25 12:27:03 +02:00
b988ec0f32 try to avoid ValueError because of erroneous date 2016-04-05 08:57:32 +02:00
c7059e4f52 try to avoid ValueError because of erroneous date 2016-04-05 08:52:35 +02:00
hplattner
8190d56286 wrap relation field totals description display value 2016-03-04 13:32:26 +01:00
hplattner
a68eddd928 fix merge conflict 2016-03-01 15:01:19 +01:00
hplattner
66526863ca use Pillow instead of PIL; Fix Image import 2016-03-01 15:00:45 +01:00
0507cc7a15 remove PIL from requirements list 2016-02-27 19:06:50 +01:00
65181d3098 update editing rules (still only informative) to changed list of work item states 2016-02-20 21:30:21 +01:00
5ab63ee78c remove redundant call to setConctroller() 2016-02-20 21:29:19 +01:00
861f340b81 backport changes from bbmaster2: avoid creation of new controller via view/@@controller 2016-02-05 12:37:52 +01:00
3721e5caf7 backport changes from bbmaster2 2016-02-04 18:08:40 +01:00
99628fedd5 provide optional export value for special formattings 2016-01-26 13:30:02 +01:00
14cfdb7624 allow setting of main template via controller (which is found via skin) 2016-01-09 12:01:09 +01:00
fcb29171ad Merge branch 'master' into bbmaster2 2015-10-10 11:42:22 +02:00
caee0eccc5 Merge branch 'master' of ssh://git.cy55.de/home/git/cybertools 2015-10-10 11:38:51 +02:00
8eed8f52df Merge branch 'bbmaster' into bbmaster2 2015-08-29 11:06:02 +02:00
a6e9a8543c merge branch master 2015-08-29 11:05:12 +02:00
296ce1a3e8 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2015-08-29 11:03:43 +02:00
773cf5f5a9 Merge branch 'master' of ssh://git.cy55.de/home/git/cybertools 2015-08-29 11:03:07 +02:00
6b22eb85dd add zope.app.dublincore (deprecated) dependency to support old persistent data 2015-07-29 09:25:20 +02:00
129f4ba73d avoid querying for runId = None 2015-07-10 09:07:24 +02:00
b5994952c0 add *.pyo to .gitignore 2015-07-10 09:06:47 +02:00
190fae3b9a HTML sanitize: allow controlling of allowed styles 2015-07-10 09:06:23 +02:00
e32c0411d4 change PIL import to work with Pillow 2015-06-13 11:32:04 +02:00
dfe5aefa69 merge branch master 2015-06-12 08:34:10 +02:00
8824044726 Merge branch 'master' of ssh://git.cy55.de/home/git/cybertools 2015-06-12 07:31:58 +02:00
df41516015 minor fixes 2015-06-12 07:30:35 +02:00
hplattner
a477c59908 add groupSequence Numbers for resultset rows 2015-05-12 10:51:11 +02:00
e948df2ea2 add magnifier (= search) icon 2014-09-21 11:12:48 +02:00
90bdbb0535 additional icons for documents and time/waiting states 2014-08-17 14:56:15 +02:00
d09b0fd786 provide new method for merging macros from different templates 2014-08-12 13:06:37 +02:00
6740d350dd set default encoding for Word to Windows-1252 2014-07-30 16:53:27 +02:00
hplattner
51c0b94d8f add rowClass attributes 2014-06-27 12:51:34 +02:00
hplattner
cb01ec4247 add colspan to subtotals row dependant on group header colspan 2014-03-13 17:49:38 +01:00
hplattner
770b4acd44 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2014-03-04 15:59:01 +01:00
30b6d90dde Merge branch 'bbmaster' of ssh://git.cy55.de/home/hplattner/git/cybertools into bbmaster 2014-02-26 10:24:51 +01:00
hplattner
ba2503560a Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2014-02-26 10:22:19 +01:00
hplattner
41e143bcec add groupHeaderColspan to groupHeaderRows 2014-02-26 10:22:03 +01:00
ec5f453077 bug fix: parameter default value may not be mutable literal object 2014-02-18 12:16:16 +01:00
e05ccdc96f make email address field not required 2014-02-10 10:24:46 +01:00
4fc98bc281 add field properties for controlling export of query results to an external database 2014-02-09 10:24:59 +01:00
hplattner
ff3c94d40e fix totalsDescription group row output 2014-01-28 16:28:25 +01:00
hplattner
25bcc53a5f set own rowtype for totalsrow 2014-01-16 08:20:21 +01:00
hplattner
d49b4888cc Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2014-01-05 12:03:35 +01:00
5ba1f5cbdd round value before formatting in order to avoid zope.i18n formatter bug 2014-01-02 17:30:57 +01:00
452dc0290f Merge branch 'master' into bbmaster 2013-12-20 10:23:47 +01:00
953df5a3be revert accedentially pulled obsolete change from 2013-01 2013-12-20 10:23:38 +01:00
3acb664355 Merge branch 'master' into bbmaster 2013-12-20 10:13:08 +01:00
7940efc315 merge remote hplattner 2013-12-20 10:12:54 +01:00
e07bcefe0c Merge branch 'bbmaster' of ssh://git.cy55.de/home/hplattner/git/cybertools into bbmaster 2013-12-20 10:10:52 +01:00
dde80ae7d7 merge remote hplattner 2013-12-20 10:10:38 +01:00
hplattner
8f1951d8cb Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2013-12-20 10:00:40 +01:00
hplattner
11498e14e7 dont't sum totals values on subtotals row 2013-12-20 09:56:33 +01:00
3bb7796fbf merge branch master 2013-12-14 09:22:58 +01:00
fb9de8acbd fix validation for KeyTableFieldInstance - must not inherit from RecordsFieldInstance 2013-11-22 16:48:37 +01:00
688059189f prevent compatibility mode in IE 2013-11-19 12:21:38 +01:00
1414a29436 merge branch master 2013-11-16 13:11:32 +01:00
hplattner
03f2812088 fix sequenceNumber generation in reports result rows 2013-08-07 17:34:25 +02:00
hplattner
f68b1db2a4 fix list slicing 2013-08-07 11:48:37 +02:00
hplattner
7aaa0547a1 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2013-08-07 11:03:16 +02:00
hplattner
8915a22bda only generate subTotalsRows for totals fields with corresponding group field 2013-08-07 11:02:50 +02:00
9989af825a Merge branch 'master' into bbmaster 2013-07-26 09:34:23 +02:00
2be1e6df6b Merge branch 'master' into bbmaster 2013-07-18 13:29:43 +02:00
ca313dad57 Merge branch 'master' into bbmaster 2013-07-16 18:11:21 +02:00
1b07899d22 Merge branch 'master' into bbmaster 2013-07-04 08:52:03 +02:00
d6fb686e48 merge branch master 2013-06-23 09:39:34 +02:00
6241719b97 fix file header 2013-05-25 09:10:28 +02:00
524975de32 Merge branch 'master' into bbmaster 2013-04-01 12:41:10 +02:00
d3986258db merge branch master 2013-03-30 09:22:19 +01:00
e2a19d5ffc allow specification of message factory for states definition in order to provide package-specific translation domains 2013-03-24 17:13:13 +01:00
f898a2b49e merge branch master 2013-03-23 11:31:13 +01:00
2a60b12100 minor fixes 2013-02-06 10:19:40 +01:00
a111780fc0 Merge branch 'master' into bbmaster 2012-11-09 09:51:51 +01:00
302b8959c4 reportign: fix subtotals (work in progress) 2012-11-09 09:51:17 +01:00
4da72fb2a7 suppress 'required' attribute in input fields to avoid misinterpretation by FF 2012-10-28 15:27:30 +01:00
be8872ce62 Merge branch 'master' into bbmaster 2012-10-15 08:17:57 +02:00
eb802de848 new flag for fields: show field content when empty 2012-09-13 16:41:33 +02:00
421ec2081a suppress check on index type, not useful for BlueBream 2012-09-10 10:24:05 +02:00
221ee291eb merge branch master 2012-09-05 16:16:24 +02:00
0ca62ba27e tolerate non-float values when displaying decimal field 2012-08-24 10:08:45 +02:00
6336567674 add view attribute to job manager 2012-08-13 12:44:20 +02:00
bd9afff0bf fix performance problem in relation registry 2012-07-31 14:44:02 +02:00
hplattner
de06a8d30d use getDisplayValue in subTotalsRow 2012-07-26 10:20:01 +02:00
2d3dbf2be7 merge branch master 2012-07-17 18:28:55 +02:00
hplattner
183098d3ff Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-07-10 10:25:12 +02:00
173e59e30b merge branch master 2012-07-10 09:04:11 +02:00
hplattner
26487b5598 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-07-09 08:24:28 +02:00
bf88e8f600 Merge branch 'master' into bbmaster 2012-07-08 18:53:08 +02:00
784ca70365 rename 'float' field to 'decimal' 2012-07-07 15:42:30 +02:00
8a77fec13d provide separate float field instance; predefine cssClass in row base class 2012-07-07 10:05:00 +02:00
hplattner
48fbacf2bc fix: subTotalsRow calculation 2012-07-06 11:37:37 +02:00
hplattner
b0354c4435 add: subTotals to group output field 2012-07-05 17:35:18 +02:00
649f79b000 fix issue with empty slots 2012-07-04 09:03:14 +02:00
6b3b7dad7a Merge branch 'master' into bbmaster 2012-07-03 13:17:06 +02:00
hplattner
c50aff0098 add: subTotalsRow Description support 2012-07-02 16:42:11 +02:00
hplattner
0efbc5db1b add: subTotals support 2012-06-26 15:20:26 +02:00
hplattner
1a58bb60c6 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-06-19 09:22:22 +02:00
3c203fa495 fix form layout (width of input fields) 2012-06-18 15:10:10 +02:00
ff9a2a8fd3 merge branch master - 'limits' parameter for reports 2012-06-18 10:28:48 +02:00
a9cecb28ce tolerate error when retrieving user title 2012-06-05 18:10:16 +02:00
0c85ff82df revert last change 2012-06-05 18:06:14 +02:00
0c2be1bcab tolerate missing user name 2012-06-05 17:51:04 +02:00
bbb44970e1 Merge branch 'bbmaster' of ssh://git.cy55.de/home/hplattner/git/cybertools into bbmaster 2012-06-05 10:55:56 +02:00
df1e93d536 merge branch master 2012-06-05 10:54:52 +02:00
bdb58534df formal clean-up: add lines between methods, remove excess spaces 2012-06-05 10:53:18 +02:00
hplattner
202dd2511f Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-06-04 13:33:49 +02:00
hplattner
3709453810 add cssClass to headerColumn 2012-06-04 13:33:29 +02:00
bd6c0167fd Merge branch 'master' into bbmaster 2012-05-20 15:55:35 +02:00
ae04c1ec8d fix merge conflict 2012-05-15 16:48:01 +02:00
bcacbe6145 merge branch master 2012-05-15 16:42:41 +02:00
14abba0e3d define standard method useRowProperty() in a central place 2012-05-01 14:16:17 +02:00
97da6c9d01 provide css class attribute for report fields 2012-05-01 10:41:06 +02:00
89491922a5 Merge branch 'master' into bbmaster 2012-04-25 14:00:08 +02:00
2aefaa082b fix setting of 'hideTime' flag 2012-04-23 13:43:10 +02:00
c65ab2ce77 allow for protect a field from overwriting (e.g. on import) by using 'magic' value __no_change__ 2012-04-20 13:10:46 +02:00
ed0148b213 Merge branch 'master' into bbmaster 2012-04-14 12:40:51 +02:00
hplattner
d2067fd92f fix: totals calculation 2012-04-13 13:24:59 +02:00
hplattner
b5bb5ada15 add computed totals 2012-04-13 09:52:18 +02:00
hplattner
fa7a07349d use row to get displayed Columns/ replace fields in headerRows 2012-04-13 07:38:01 +02:00
hplattner
41b1aa2630 add items method to treat TableCellStyle as dict 2012-04-12 11:20:30 +02:00
ec5e832dd6 introduce special Style class; implement grouping via sequential access 2012-04-11 12:34:03 +02:00
cd97e6787e improve group handling 2012-04-11 11:20:29 +02:00
25030e3e8d remove obsolete CombinedResultSet class 2012-04-07 12:22:43 +02:00
d6a1816d54 merge result set grouping from hplattner 2012-04-07 12:20:41 +02:00
hplattner
7eda3c2e35 fix: replace Combined/CategoryResultSets with computed HeaderRows 2012-04-03 15:22:14 +02:00
40995a6e44 improve duplicates filtering routing (and fix spelling) 2012-03-30 10:44:07 +02:00
hplattner
66087e14dc add: CombinedResultSet 2012-03-30 08:36:23 +02:00
hplattner
5fbb588743 add support for category rows/ execution steps 2012-03-29 16:05:06 +02:00
hplattner
be53682e0d add: make columnBorder configurable for report fields 2012-03-27 16:28:29 +02:00
hplattner
cf0888415d make colspan/ colwidth/ coltextalign configurable 2012-03-27 15:01:00 +02:00
99a89b6f0d provide appropriate default definitions for select and sort values 2012-03-19 19:07:00 +01:00
c50e7f07d3 vocabulary: handle integer and string tokens as equivalent 2012-03-17 16:52:14 +01:00
7483763564 use ISO encoding for BlueBream version 2012-03-17 14:29:18 +01:00
322515f966 Merge branch 'master' into bbmaster 2012-03-17 11:57:11 +01:00
edc5df6aba Merge branch 'bbmaster' of ssh://git.cy55.de/home/hplattner/git/cybertools into bbmaster 2012-03-17 11:55:38 +01:00
fdd17496ab suppress __getattr__() - is not used any more 2012-03-17 11:55:16 +01:00
hplattner
4c16a233d9 add: *.project to ignored files 2012-03-16 15:43:04 +01:00
hplattner
76e40bd412 fix: merge conflict 2012-03-09 08:22:52 +01:00
6aea218261 fix error in formatNumber 2012-03-08 21:18:28 +01:00
hplattner
7476928fb9 fix: forNumber use location 2012-03-08 20:47:44 +01:00
hplattner
d09ec9f469 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-03-08 13:31:26 +01:00
6579752dce loading of CSS file controllable by subclass 2012-03-08 11:54:44 +01:00
hplattner
31954942fb Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-03-07 16:15:02 +01:00
08c0ecadbf mark WordDocument base class as top-level page 2012-03-07 15:33:09 +01:00
hplattner
db7e10ac51 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-03-06 09:49:50 +01:00
23c6b430d7 Merge branch 'master' into bbmaster 2012-03-05 10:00:03 +01:00
1421e8cb1a Merge branch 'master' into bbmaster 2012-03-01 17:29:36 +01:00
f654890a6e Merge branch 'bbmaster' of ssh://git.cy55.de/home/hplattner/git/cybertools into bbmaster 2012-03-01 17:25:34 +01:00
hplattner
d836613734 Merge branch 'bbmaster' of ssh://git.cy55.de/home/git/cybertools into bbmaster 2012-03-01 12:15:40 +01:00
hplattner
6fd1a5ed8f fix: get self context attr 2012-03-01 12:15:19 +01:00
fdb51f6b1a merge branch 'master' 2012-02-26 10:23:53 +01:00
1be9a85798 use correct getContextAttr() method: from object, not class 2012-02-26 10:20:50 +01:00
d908b18188 handle display format setting when creating field from interface 2012-02-08 11:50:29 +01:00
bbc5f16454 use ISO encoding for Word; use Section2 CSS class (landscape orientation) as standard for listings 2012-02-07 15:05:26 +01:00
cc8584f957 Merge branch 'master' into bbmaster 2012-02-07 09:33:21 +01:00
21f51f904b Merge branch 'master' into bbmaster 2012-01-29 11:23:18 +01:00
69351428b9 Merge branch 'master' into bbmaster 2012-01-23 11:09:29 +01:00
8fb5102aa8 Merge branch 'master' into bbmaster 2012-01-19 18:18:10 +01:00
f3fb703e97 Merge branch 'master' into bbmaster 2012-01-05 12:32:00 +01:00
694cef4585 merge branch 'master' 2011-12-28 08:36:36 +01:00
531073648b update file headers 2011-12-28 08:33:10 +01:00
3bc258a428 make keytable field and widget fully operable 2011-12-23 14:14:04 +01:00
2d5b4e5603 Merge branch 'master' into bbmaster 2011-12-23 10:31:42 +01:00
474062399f add keytable stuff to grid-like field definitions 2011-12-23 10:30:55 +01:00
59a56b1f49 Merge branch 'master' into bbmaster 2011-12-04 11:22:51 +01:00
cad45fc4ac Merge branch 'master' into bbmaster 2011-12-02 10:18:15 +01:00
ef31310ef1 add .po file metadata 2011-11-15 11:44:05 +01:00
hplattner
28dfd993b7 add: german translations for IAddress attributes 2011-11-15 11:36:51 +01:00
hplattner
416f8d841a add: german translations for IAddress attributes 2011-11-15 11:35:30 +01:00
hplattner
875789307d add: german translations for IAddress attributes 2011-11-15 11:34:51 +01:00
8c3118e333 provide field instance with context and request information, e.g. for use in persistent vocabularies 2011-11-14 15:08:33 +01:00
5fc606bbd3 Merge branch 'master' into bbmaster 2011-11-11 15:19:01 +01:00
862b358254 provide method for controlling which macro from which template will be used for rendering 2011-10-28 13:02:50 +02:00
a14c614fb3 include zope.dublincore package 2011-10-20 17:54:58 +02:00
2d1efac60e Merge branch 'master' into bbmaster 2011-10-10 12:29:19 +02:00
78441e0444 remove obsolete agent and twisted packages 2011-10-10 12:27:00 +02:00
78a0a83c2f remove deprecated zapi reference 2011-10-04 17:04:42 +02:00
198b0f10c2 make loops package work with BlueBream 1.0: system running 2011-09-29 22:27:40 +02:00
b327062492 work in progress: make cybertools package work with BlueBream 1.0 2011-09-29 18:40:47 +02:00
f0e789d844 work in progress: make cybertools package work with BlueBream 1.0 2011-09-29 18:17:35 +02:00
751 changed files with 2778 additions and 12533 deletions

15
.gitignore vendored
View file

@ -1,6 +1,13 @@
*.pyc
ajax/dojo/*
.project
.pydevproject
*.pyo
*/ajax/dojo/dojo*
build/
dist/
*.swp
*.egg-info
*.project
*.pydevproject
*.sublime-project
*.sublime-workspace
.settings
*.ropeproject

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (C) 2023 cyberconcepts.org team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,226 +0,0 @@
================================================
Agents for Job Execution and Communication Tasks
================================================
Agents do some work specified by jobs, the main task being to collect
information objects from the local machine or some external source and
transfer them e.g. to a loops server on the same machine or another.
($Id$)
This package does not depend on Zope but represents a standalone application.
Sub-Packages
============
Top-level
Generic interfaces, ``commponents``: adapter registry,
``tests`` module, this ``README.txt`` file.
base
Base and sample classes.
core
Agent and scheduling implementations.
control
Communication with an external agent control and job database application.
crawl
Scanning/crawling some system, e.g. the database of an email application,
the local file system, or an external document source.
transport
Transfer of information objects to agents on another machine or
to an information management application (e.g. loops).
util
Various utility modules, e.g. a backport of the
``twisted.internet.task.coiterate()`` function from Twisted 2.5 so that
we can use the Twisted version coming with Zope 3.3.1 for
cybertools.agent.
All sub-packages except ``base`` depend on Twisted.
Object Structure and Control Flow
=================================
::
---------- ------------
-------- | |<---1| |
| config |--> | master |<---2| controller |
-------- /| |4--->| |
/ ---------- ------------
/ 1 ^ 2
/ | | \ -----------
/ | | `---->| scheduler |
v v 4 -----------
----- --------- 3
| log |<--| agent |<----------´
----- ---------
(1) Agent specifications control creation and setup of agents
(2) Job specifications control scheduling of jobs
(3) Scheduler triggers job execution by agent
(4) Results are recorded, possibly triggering further actions
Basic Stuff
===========
While the real application is based on the asynchronous communication
framework Twisted there is some basic stuff (mainly interfaces and
base classes with basic, sample, or dummy implementations) that is
independent of Twisted.
The code for this resides in in the ``base`` sub-package.
Master Agent and Configuration
------------------------------
All activity is controlled by the master agent.
The master agent is set up according to the specifications in a
configuration file.
The configuration typically provides only basic informations, e.g. about
the controller(s) and logger(s) to be used; details on jobs and agent
configuration are provided by the controller.
>>> from cybertools.agent.tests import baseDir
>>> import os
>>> configFile = open(os.path.join(baseDir, 'base', 'sample.cfg'))
So we are now ready to create a master agent and configure it by supplying
the path to the configuration file.
>>> from cybertools.agent.main import setup
>>> master = setup(configFile)
Starting agent application...
Using controllers base.sample.
>>> configFile.close()
>>> master.config
controller.names = ['base.sample']
logger.name = 'default'
logger.standard = 30
scheduler.name = 'sample'
Controllers
-----------
Creation of agents and scheduling of jobs is controlled by controller
objects. These are typically associated with a sort of control storage that
provides agent and job specifications and receives the results of job
execution.
>>> master.controllers
[<cybertools.agent.base.control.SampleController object ...>]
We make the contollers provide the specifications via the master agent's
``setup()`` method.
>>> master.setup()
Starting agent application...
Using controllers base.sample.
Other Agents
------------
The above ``setup()`` call has triggered the creation of one child agent -
that is all the sample controller provides.
>>> master.children
{'sample01': <cybertools.agent.base.agent.SampleAgent object ...>}
Let's check a few attributes of the newly created agent.
>>> agent01 = master.children['sample01']
>>> agent01.master is master
True
>>> agent01.config is master.config
True
Job Scheduling and Execution
----------------------------
A scheduler is responsible for triggering the execution of a job at the
appropriate time. The master agent schedules the jobs based upon the
information (job specifications) it gets from the controller. There
is just one scheduler associated with the master agent.
>>> master.scheduler
<cybertools.agent.base.schedule.Scheduler object ...>
We schedule a sample job by calling an internal method of the agent's
controller. In addition to the output of the job execution itself we
also get a note from the controller about the feedback it received
about the outcome of the job execution.
>>> master.controllers[0].enterJob('sample', 'sample01')
Job 00001 on agent sample01 has been executed.
Job 00001 completed; result: None;
Logging
-------
>>> master.logger
<cybertools.agent.base.log.Logger object ...>
>>> agent01.logger is master.logger
True
>>> master.config.logger.standard = 20
>>> master.logger.setup()
>>> master.controllers[0].enterJob('sample', 'sample01')
Job 00002 on agent sample01 has been executed.
2... agent:sample01 job:00002 message:job execution result:OK
Job 00002 completed; result: None;
>>> for r in master.logger.records:
... print r
2... agent:sample01 job:00001 message:job execution result:OK
2... agent:sample01 job:00002 message:job execution result:OK
Using the Twisted-based Scheduler
=================================
By specifying the core scheduler in the agent's configuration this will be
used automatically for scheduling.
In addition, we use another sample controller, now also the twisted-based
from the core package. This will in turn set up a queueable agent from
the core package so that now everything is running under the control of
the twisted reactor.
>>> config = '''
... controller(names=['core.sample'])
... scheduler(name='core')
... logger(name='default', standard=30)
... '''
>>> master = setup(config)
Starting agent application...
Using controllers core.sample.
>>> master.scheduler
<cybertools.agent.core.schedule.Scheduler object ...>
We enter the same job specification as above.
>>> master.controllers[0].enterJob('sample', 'sample01')
Now the job is not executed immediately - we have to hand over control to
the twisted reactor first. The running of the reactor is simulated by
the ``iterate()`` method provided for testing.
>>> from cybertools.agent.tests import tester
>>> tester.iterate()
Job 00001 on agent sample01 has been executed.
Job 00001 completed; result: Done;

View file

@ -1,10 +0,0 @@
#
# Standard configuration for agent application
#
# $Id$
#
controller(names=['cmdline', 'telnet'])
controller.telnet.port = 5001
scheduler(name='core')
logger(name='default', standard=30)

View file

@ -1,31 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Agent application.
$Id$
"""
from twisted.application import service
from cybertools.agent import main
application = main.application = service.Application('Agent Application')
main.setup()

View file

@ -1,121 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Agent base and sample classes.
$Id$
"""
from zope.interface import implements
from cybertools.agent.common import states
from cybertools.agent.components import agents, controllers, jobs
from cybertools.agent.components import loggers, schedulers
from cybertools.agent.components import servers, clients
from cybertools.agent.interfaces import IAgent
from cybertools.util.config import Configurator
class Agent(object):
implements(IAgent)
name = '???'
master = None
config = None
logger = None
def __init__(self, master):
self.master = master
self.config = master.config
self.logger = master.logger
def send(self, job):
self.execute(job)
def execute(self, job):
pass
def log(self, job, result='OK'):
self.logger.log(dict(message='job execution', job=job.identifier,
agent=self.name, result=result))
class Master(Agent):
name = 'master'
scheduler = None
logger = None
def __init__(self, configuration):
if isinstance(configuration, Configurator):
self.config = configuration
else: # configuration is path to config file
self.config = Configurator()
self.config.load(configuration)
self.master = self
self.controllers = []
self.children = {}
self.servers = []
def setup(self):
config = self.config
self.logger = loggers(self, name=config.logger.name)
print 'Starting agent application...'
for n in config.controller.names:
self.controllers.append(controllers(self, n))
self.scheduler = schedulers(self, name=config.scheduler.name)
for cont in self.controllers:
cont.setup()
print 'Using controllers %s.' % ', '.join(config.controller.names)
for n in config.talk.server.names:
server = servers(self, n)
self.servers.append(server)
server.setup()
def setupAgents(self, controller, agentSpecs):
for spec in agentSpecs:
agent = agents(self, spec.type)
agent.name = spec.name
self.children[spec.name] = agent
def setupJobs(self, controller, jobSpecs):
for spec in jobSpecs:
job = jobs(self.scheduler, spec.type)
job.agent = self.children[spec.agent]
job.identifier = spec.identifier
job.controller = controller
job.params = spec.params
self.scheduler.schedule(job, spec.startTime)
def notify(self, job, result=None, message=''):
if job.state.hasFinished():
job.controller.notify(job.identifier, job.state, result, message)
class SampleAgent(Agent):
def execute(self, job):
job.state = states.running
print 'Job %s on agent %s has been executed.' % (job.identifier, self.name)
self.log(job)
job.state = states.completed
self.master.notify(job)
agents.register(SampleAgent, Master, name='base.sample')

View file

@ -1,100 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base/sample controller implementation.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.components import controllers
from cybertools.agent.interfaces import IController
class Controller(object):
implements(IController)
def __init__(self, agent):
self.agent = agent
def setup(self):
self.agent.setupAgents(self, self._getAgents())
self.agent.setupJobs(self, self._getCurrentJobs())
def _getAgents(self):
return []
def _getCurrentJobs(self):
return []
def notify(self, identifier, state, result=None, message=''):
pass
class SampleController(Controller):
jobNumber = 0
result = None
agents = (('sample01', 'base.sample'),)
def notify(self, identifier, state, result=None, message=''):
self.result = result
msg = ('Job %s %s; result: %s; %s' %
(identifier, state, result, message))
print msg
def _getAgents(self):
return [AgentSpecification(name, type) for name, type in self.agents]
def createAgent(self, agentType, name):
spec = AgentSpecification(name, agentType)
self.agent.setupAgents(self, [spec])
def enterJob(self, jobType, agent, **kw):
self.jobNumber += 1
spec = JobSpecification(jobType, '%05i' % self.jobNumber, agent=agent, **kw)
self.agent.setupJobs(self, [spec])
controllers.register(SampleController, Master, name='base.sample')
class AgentSpecification(object):
def __init__(self, name, type, **kw):
self.name = name
self.type = type
for k, v in kw.items():
setattr(self, k, v)
class JobSpecification(object):
startTime = None
def __init__(self, type, identifier, **kw):
self.type = type
self.identifier = identifier
self.params = kw.pop('params', {})
for k, v in kw.items():
setattr(self, k, v)

View file

@ -1,63 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
The real agent stuff.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.schedule import Scheduler
from cybertools.agent.common import states
from cybertools.agent.components import jobs
from cybertools.agent.interfaces import IScheduledJob
class Job(object):
implements(IScheduledJob)
identifier = '???'
agent = None
startTime = None
repeat = 0
state = states.initialized
def __init__(self, scheduler):
self.scheduler = scheduler
self.params = {}
self.successors = []
def execute(self):
if self.agent is not None:
self.agent.send(self)
self.state = states.submitted
def reschedule(self, startTime=None):
self.scheduler.schedule(self.copy(), startTime)
def copy(self):
newJob = self.__class__(self.scheduler)
newJob.agent = self.agent
newJob.params = self.params
newJob.repeat = self.repeat
newJob.successors = [s.copy() for s in self.successors]
jobs.register(Job, Scheduler, name='sample')

View file

@ -1,81 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Log information management.
$Id$
"""
import logging
import sys
import time
from zope.interface import implements
from cybertools.agent.base.agent import Agent
from cybertools.agent.components import loggers
from cybertools.agent.interfaces import ILogger, ILogRecord
class LogRecord(object):
implements(ILogRecord)
datefmt = '%Y-%m-%dT%H:%S'
def __init__(self, logger, data):
self.logger = logger
self.data = data
self.timeStamp = time.time()
def __str__(self):
msg = [str(time.strftime(self.datefmt, time.localtime(self.timeStamp)))]
for k in sorted(self.data):
msg.append('%s:%s' % (str(k), str(self.data[k])))
return ' '.join(msg)
class Logger(object):
implements(ILogger)
recordFactory = LogRecord
def __init__(self, agent):
self.agent = agent
self.records = []
self.setup()
self.externalLoggers = []
def setup(self):
conf = self.agent.config.logger
self.externalLoggers = []
if conf.standard:
logger = logging.getLogger()
logger.level = conf.standard
logger.addHandler(logging.StreamHandler(sys.stdout))
self.externalLoggers.append(logger)
def log(self, data):
record = self.recordFactory(self, data)
self.records.append(record)
for logger in self.externalLoggers:
logger.info(str(record))
loggers.register(Logger, Agent, name='default')

View file

@ -1,9 +0,0 @@
#
# sample.cfg - agent configuration for demonstration and testing purposes
#
# $Id$
#
controller(names=['base.sample'])
scheduler(name='sample')
logger(name='default', standard=30)

View file

@ -1,46 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Basic (sample) job scheduler.
$Id$
"""
from time import time
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.common import states
from cybertools.agent.components import schedulers
from cybertools.agent.interfaces import IScheduler
class Scheduler(object):
implements(IScheduler)
def __init__(self, agent):
self.agent = agent
def schedule(self, job, startTime=None):
job.startTime = startTime or int(time())
job.state = states.scheduled
job.execute() # the sample scheduler does not care about startTime
schedulers.register(Scheduler, Master, name='sample')

View file

@ -1,50 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Miscellaneous common stuff.
$Id$
"""
from cybertools.util.jeep import Jeep
class JobState(object):
def __init__(self, name, value):
self.name = name
self.value = value
def hasError(self):
return self.value < 0
def hasFinished(self):
return (self.value <= states.aborted.value or
self.value >= states.completed.value)
def __str__(self):
return self.name
def __repr__(self):
return '<JobState %s>' % self.name
states = Jeep([JobState(n, v) for n, v in
(('initialized', 0), ('scheduled', 1), ('submitted', 2),
('running', 3), ('completed', 4), ('aborted', -1))])

View file

@ -1,34 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Component registries.
$Id$
"""
from cybertools.util.adapter import AdapterFactory
agents = AdapterFactory()
controllers = AdapterFactory()
jobs = AdapterFactory()
loggers = AdapterFactory()
schedulers = AdapterFactory()
servers = AdapterFactory()
clients = AdapterFactory()

View file

@ -1,145 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base/sample controller implementation.
$Id$
"""
from twisted.application import service, internet
from twisted.internet import protocol, reactor, stdio
from twisted.protocols import basic
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.core.control import SampleController
from cybertools.agent.components import controllers
class CmdlineController(SampleController):
def setup(self):
super(CmdlineController, self).setup()
prot = CmdlineProtocol()
prot.controller = self
stdio.StandardIO(prot)
self.results = {}
controllers.register(CmdlineController, Master, name='cmdline')
class TelnetController(CmdlineController):
def setup(self):
super(CmdlineController, self).setup()
port = self.agent.config.controller.telnet.port
from cybertools.agent.main import application
if application is None:
reactor.listenTCP(port, TelnetServerFactory(self))
else:
service = internet.TCPServer(port, TelnetServerFactory(self))
service.setServiceParent(application)
controllers.register(TelnetController, Master, name='telnet')
class CmdlineProtocol(basic.LineReceiver):
delimiter = '\n'
controller = None
def connectionMade(self):
self.sendLine("Agent console. Type 'help' for help.")
self.transport.write('> ')
def lineReceived(self, line):
if not line:
return
commandParts = line.split()
command = commandParts[0].lower()
args = commandParts[1:]
posArgs = []
kwArgs = {}
for arg in args:
if '=' in arg: # directory='...'
key, value = arg.split('=', 1)
if value in ('True', 'False'):
value = eval(value)
#elif value.isdigit():
# value = int(value)
kwArgs[key] = value
else:
posArgs.append(arg)
try:
method = getattr(self, 'do_' + command)
except AttributeError, e:
self.sendLine('Error: no such command.')
else:
try:
method(*posArgs, **kwArgs)
except Exception, e:
self.sendLine('Error: ' + str(e))
self.transport.write('> ')
def do_help(self, command=None):
"Help"
if command:
self.sendLine(getattr(self, 'do_' + command).__doc__)
else:
commands = [cmd[3:] for cmd in dir(self) if cmd.startswith('do_')]
self.sendLine("Valid commands: " +" ".join(commands))
def do_shutdown(self):
"Shut down"
self.sendLine('Shutting down.')
reactor.stop()
def do_agent(self, agentType='crawl.filesystem', name='a1'):
"Create agent"
self.controller.createAgent(agentType, name)
def do_job(self, jobType='sample', agent='a1', **kw):
"Enter job"
self.controller.enterJob(jobType, agent, params=kw)
def do_showresult(self):
"Show last result"
print self.controller.result
class TelnetProtocol(CmdlineProtocol):
delimiter = '\r\n'
def do_quit(self):
self.sendLine('Goodbye.')
self.transport.loseConnection()
class TelnetServerFactory(protocol.ServerFactory):
def __init__(self, controller):
self.controller = controller
def protocol(self, *args, **kw):
prot = TelnetProtocol(*args, **kw)
prot.controller = self.controller
return prot

View file

@ -1,38 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Controller that accepts and processes requests via XML-RPC.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.core.control import SampleController
from cybertools.agent.components import controllers
class RemoteController(SampleController):
def setup(self):
super(RemoteController, self).setup()
controllers.register(RemoteController, Master, name='remote')

View file

@ -1,87 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Queueable agent base/sample classes.
$Id$
"""
from twisted.internet.defer import succeed
from zope.interface import implements
from cybertools.agent.base.agent import Agent, Master
from cybertools.agent.common import states
from cybertools.agent.components import agents
from cybertools.agent.interfaces import IQueueableAgent
class QueueableAgent(Agent):
implements(IQueueableAgent)
currentJob = None
def __init__(self, master):
super(QueueableAgent, self).__init__(master)
self.queue = []
def send(self, job):
if self.currentJob is None:
if self.queue: # this should not happen...
self.queue.insert(0, job)
job = self.queue.pop()
self.execute(job)
else:
self.queue.insert(0, job)
def execute(self, job):
job.state = states.running
self.currentJob = job
self.params = job.params
d = self.process()
d.addCallbacks(self.completed, self.error)
def process(self):
# do something with the current job, return a deferred
print ('Job %s on agent %s has been executed.'
% (self.currentJob.identifier, self.name))
return succeed('Done')
def completed(self, result):
job = self.currentJob
job.state = states.completed
self.log(job)
self.master.notify(job, result)
self.finishJob()
def error(self, result):
print '*** error', result
job = self.currentJob
job.state = states.aborted
self.log(self.currentJob, result='Error')
self.master.notify(job, result)
self.finishJob()
def finishJob(self):
self.currentJob = None
if self.queue:
job = self.queue.pop()
self.execute(job, job.params)
agents.register(QueueableAgent, Master, name='core.sample')

View file

@ -1,36 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base/sample controller implementation.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.base.control import SampleController, AgentSpecification
from cybertools.agent.components import controllers
class SampleController(SampleController):
agents = (('sample01', 'core.sample'),)
controllers.register(SampleController, Master, name='core.sample')

View file

@ -1,57 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Basic job scheduler using twisted.
$Id$
"""
from time import time
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.base.schedule import Scheduler as BaseScheduler
from cybertools.agent.common import states
from cybertools.agent.components import schedulers
from cybertools.agent.interfaces import IScheduler
from twisted.internet import reactor
from twisted.internet.defer import Deferred
class Scheduler(BaseScheduler):
implements(IScheduler)
def __init__(self, agent):
self.agent = agent
def schedule(self, job, startTime=None):
job.startTime = startTime or int(time())
if startTime is None:
startTime = int(time())
job.startTime = startTime
job.state = states.scheduled
reactor.callLater(startTime-int(time()), job.execute)
return startTime
def getJobsToExecute(startTime=0):
return [j for j in self.queue.values() if startTime <= j.startTime]
schedulers.register(Scheduler, Master, name='core')

View file

@ -1,46 +0,0 @@
================================================
Agents for Job Execution and Communication Tasks
================================================
($Id$)
>>> config = '''
... controller(names=['core.sample'])
... scheduler(name='core')
... logger(name='default', standard=30)
... '''
>>> from cybertools.agent.main import setup
>>> master = setup(config)
Starting agent application...
Using controllers core.sample.
Crawler
=======
The agent uses Twisted's cooperative multitasking model.
Crawler is the base class for all derived crawlers like the filesystem crawler
and the mailcrawler. The SampleCrawler returns a deferred that already had a
callback being called, so it will return at once.
Returns a deferred that must be supplied with a callback method (and in
most cases also an errback method).
We create the sample crawler via the master's controller. The sample
controller provides a simple method for this purpose.
>>> controller = master.controllers[0]
>>> controller.createAgent('crawl.sample', 'crawler01')
In the next step we request the start of a job, again via the controller.
>>> controller.enterJob('sample', 'crawler01')
The job is not executed immediately - we have to hand over control to
the twisted reactor first.
>>> from cybertools.agent.tests import tester
>>> tester.iterate()
SampleCrawler is collecting.
Job 00001 completed; result: [];

View file

@ -1,92 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Crawl base and sample classes.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.core.agent import QueueableAgent
from cybertools.agent.interfaces import ICrawler
from cybertools.agent.interfaces import IResource, IMetadataSet
from cybertools.agent.components import agents
from twisted.internet.defer import succeed
class Crawler(QueueableAgent):
implements(ICrawler)
def __init__(self, master, params={}):
super(Crawler, self).__init__(master)
def process(self):
return self.collect()
def collect(self, filter=None):
d = defer.succeed([])
return d
class SampleCrawler(Crawler):
def collect(self, filter=None):
print 'SampleCrawler is collecting.'
d = succeed([])
return d
agents.register(SampleCrawler, Master, name='crawl.sample')
class Resource(object):
implements(IResource)
data = file = path = None
type = 'sample'
contentType = 'text/plain'
encoding = ''
application = 'sample'
metadata = None
def __init__(self, data=None, **kw):
if data is not None:
self.data = data
for k, v in kw.items():
setattr(self, k, v)
self.subResources = []
class Metadata(dict):
implements(IMetadataSet)
def __init__(self, data=dict()):
self.update(data)
def asXML(self):
# TODO...
return ''
def set(self, key, value):
self['key'] = value

View file

@ -1,85 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Filesystem crawler.
$Id$
"""
import os
from fnmatch import filter
from datetime import datetime
from twisted.internet.defer import Deferred
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.components import agents
from cybertools.agent.crawl.base import Resource, Metadata
from cybertools.agent.crawl.base import Crawler
from cybertools.agent.util.task import coiterate
class FilesystemCrawler(Crawler):
def collect(self):
self.collected = []
coiterate(self.crawlFilesystem()).addCallback(self.finished)
# TODO: addErrback()
self.deferred = Deferred()
return self.deferred
def finished(self, result):
self.deferred.callback(self.collected)
def crawlFilesystem(self):
directory = self.params.get('directory')
pattern = self.params.get('pattern') or '*'
lastRun = self.params.get('lastrun') or datetime(1980, 1, 1)
for path, dirs, files in os.walk(directory):
if '.svn' in dirs:
del dirs[dirs.index('.svn')]
for x in self.loadFiles(path, files, pattern, lastRun):
yield None
def loadFiles(self, path, files, pattern, lastRun):
for f in filter(files, pattern):
filename = os.path.join(path, f)
mtime = datetime.fromtimestamp(os.path.getmtime(filename))
if mtime <= lastRun: # file not changed
continue
meta = dict(
path=filename,
)
self.collected.append(FileResource(path=filename, metadata=Metadata(meta)))
yield None
agents.register(FilesystemCrawler, Master, name='crawl.filesystem')
class FileResource(Resource):
type = 'file'
application = 'filesystem'
@property
def data(self):
f = open(self.path, 'r')
text = f.read()
f.close()
return text

View file

@ -1,42 +0,0 @@
================================================
Agents for Job Execution and Communication Tasks
================================================
($Id$)
>>> import os
>>> from time import time
>>> from cybertools.agent.tests import tester, baseDir
>>> config = '''
... controller(names=['core.sample'])
... scheduler(name='core')
... logger(name='default', standard=30)
... '''
>>> from cybertools.agent.main import setup
>>> master = setup(config)
Starting agent application...
Using controllers core.sample.
Filesystem Crawler
==================
>>> controller = master.controllers[0]
>>> controller.createAgent('crawl.filesystem', 'sample03')
In the next step we request the start of a job, again via the controller.
>>> path = os.path.join(baseDir, 'testing', 'data')
>>> controller.enterJob('sample', 'sample03', params=dict(directory=path))
The job is not executed immediately - we have to hand over control to
the twisted reactor first.
>>> from cybertools.agent.tests import tester
>>> tester.iterate()
Job 00001 completed; result: [..., ...];
>>> r0 = controller.result[0]
>>> r0.metadata, r0.data
({'path': '...file1.txt'}, 'Data from file1.txt')

View file

@ -1,76 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Crawl base and sample classes.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.agent import Agent, Master
from cybertools.agent.crawl.base import Resource
from cybertools.agent.crawl.base import Metadata
from cybertools.agent.crawl.base import Crawler
from cybertools.agent.components import agents
from twisted.internet.defer import succeed
class MailCrawler(Crawler):
def __init__(self, master):
super(MailCrawler, self).__init__(master)
self.result = []
def collect(self, filter=None):
print 'MailCrawler is collecting.'
d = self.crawlFolders()
return d
def fetchCriteria(self):
pass
def crawlFolders(self):
return succeed([])
def loadMailsFromFolder(self, folder):
pass
def createResource(self, data, path=None, application=None, metadata=None):
resource = MailResource(data=data, path=path, application=application,
metadata=metadata)
self.result.append(resource)
def createMetadata(self, metadata):
metadata = Metadata(metadata)
## for k, v in kw.items():
## metadata[k] = v
return metadata
def login(self):
pass
agents.register(MailCrawler, Master, name='crawl.mail')
class MailResource(Resource):
type = 'email'
application = 'mailclient'

View file

@ -1,231 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Outlook Crawler Class.
$Id$
"""
import re
from email import MIMEMultipart
import tempfile
import os
from zope.interface import implements
from twisted.internet import defer
#from pywintypes import com_error
#The watsup import is needed as soon as we start handling the Outlook Pop-Up
#again
#This should also be integrated within the wrapper-api for doctests
#from watsup.winGuiAuto import findTopWindow, findControl, findControls, clickButton, \
# getComboboxItems, selectComboboxItem, setCheckBox
from cybertools.agent.base.agent import Agent, Master
from cybertools.agent.crawl.mail import MailCrawler
from cybertools.agent.crawl.mail import MailResource
from cybertools.agent.crawl.filesystem import FileResource
from cybertools.agent.components import agents
from cybertools.agent.system.windows import api
from cybertools.agent.util.task import coiterate
from cybertools.agent.system.windows.codepages import codepages
# some constants
COMMASPACE = ', '
class OutlookCrawler(MailCrawler):
keys = ""
inbox = ""
subfolders = ""
pattern = ""
def collect(self, filter=None):
self.result = []
self.d = defer.Deferred()
self.oOutlookApp = None
if self.findOutlook():
self.fetchCriteria()
coiterate(self.crawlFolders()).addCallback(self.finished).addErrback(self.error)
else:
pass
#self.d.addErrback([])
return self.d
def error(self, reason):
print '***** error',
print reason
def finished(self, result):
self.d.callback(self.result)
def fetchCriteria(self):
criteria = self.params
self.keys = criteria.get('keys')
self.inbox = criteria.get('inbox') #boolean
self.subfolders = criteria.get('subfolders') #boolean
self.pattern = criteria.get('pattern')
if self.pattern != '' and self.pattern != None:
self.pattern = re.compile(criteria.get('pattern') or '.*')
def crawlFolders(self):
onMAPI = self.oOutlookApp.GetNamespace("MAPI")
ofInbox = \
onMAPI.GetDefaultFolder(api.client.constants.olFolderInbox)
# fetch mails from inbox
if self.inbox:
for m in self.loadMailsFromFolder(ofInbox):
yield None
# fetch mails of inbox subfolders
if self.subfolders and self.pattern is None:
lInboxSubfolders = getattr(ofInbox, 'Folders')
for of in range(lInboxSubfolders.__len__()):
# get a MAPI-subfolder object and load its emails
for m in self.loadMailsFromFolder(lInboxSubfolders.Item(of + 1)):
yield None
elif self.subfolders and self.pattern:
lInboxSubfolders = getattr(ofInbox, 'Folders')
for of in range(lInboxSubfolders.__len__()):
# get specified MAPI-subfolder object and load its emails
if self.pattern.match(getattr(lInboxSubfolders.Item(of + 1), 'Name')):
for m in self.loadMailsFromFolder(lInboxSubfolders.Item(of + 1)):
yield None
def loadMailsFromFolder(self, folder):
# get items of the folder
folderItems = getattr(folder, 'Items')
for item in range(len(folderItems)):
mail = folderItems.Item(item+1)
if mail.Class == api.client.constants.olMail:
if self.keys is None:
self.keys = []
for key in mail._prop_map_get_.items():
try:
if isinstance(key[0], (int, str, unicode, bool)):
self.keys.append(key[0])
except api.com_error:
pass
record = {}
for key in self.keys:
try:
if (hasattr(mail, key)):
value = getattr(mail, key)
if isinstance(value, (int, str, unicode, bool)):
record[key] = value
else:
record[key] = None
except:
pass
metadata = self.assembleMetadata(folder, record)
# Create a resource and append it to the result list
self.createResource(mail, folder, metadata)
yield None
def findOutlook(self):
outlookFound = False
try:
self.oOutlookApp = \
api.client.gencache.EnsureDispatch("Outlook.Application")
outlookFound = True
except com_error:
pass
return outlookFound
def assembleMetadata(self, folder, mailAttr):
meta = {}
for key in mailAttr.keys():
if isinstance(mailAttr[key], (str, unicode))\
and mailAttr[key] != 'Body' and mailAttr[key] != 'HTMLBody':
meta[key] = mailAttr[key].encode('utf-8')
elif isinstance(mailAttr[key], (list, tuple, dict)):
lst = []
for rec in mailAttr[key]:
lst.append(rec)
meta[key] = COMMASPACE.join(lst)
else:
meta[key] = mailAttr[key]
meta["path"] = folder
metadata = self.createMetadata(meta)
return metadata
def createResource(self, mail, folder, metadata):
enc = None
textType = "application/octet-stream"
attachments = []
mailContent = ""
ident = None
if (hasattr(mail, 'BodyFormat')):
value = getattr(mail, 'BodyFormat')
if value == 1:
#1: it is a plain text mail, that is maybe decorated with
#some html Tags by Outlook for formatting
#so save it as plain text mail
if hasattr(mail, 'Body'):
mailContent = getattr(mail, 'Body')
textType = "text/plain"
else:
mailContent = ""
textType = "text/plain"
elif value == 2:
#2: it is a HTML mail
if hasattr(mail, 'HTMLBody'):
mailContent = getattr(mail, 'HTMLBody')
textType = "text/html"
else:
mailContent = ""
textType = "text/html"
else:
#Could not determine BodyFormat. Try to retrieve plain text
if hasattr(mail, 'Body'):
mailContent = getattr(mail, 'Body')
else:
mailContent = ""
if hasattr(mail, 'InternetCodepage'):
Codepage = getattr(mail, 'InternetCodepage')
if codepages.has_key(Codepage):
enc = codepages[Codepage]
if hasattr(mail, 'EntryID'):
ident = getattr(mail, 'EntryID')
if hasattr(mail, 'Attachments'):
attachedElems = getattr(mail, 'Attachments')
for item in range(1, len(attachedElems)+1):
fileHandle, filePath = tempfile.mkstemp(prefix="outlook")
attachedItem = attachedElems.Item(item)
attachedItem.SaveAsFile(filePath)
os.close(fileHandle)
metadat = self.createMetadata(dict(filename=filePath))
fileRes = FileResource(data=None,
path=filePath,
metadata=metadat)
attachments.append(fileRes)
fileHandle, filePath = tempfile.mkstemp(prefix="olmail")
filePointer = os.fdopen(fileHandle, "w")
mailContent = mailContent.encode('utf-8')
filePointer.write(mailContent)
filePointer.close()
resource = MailResource(data=mailContent,
contentType=textType,
encoding=enc,
path=filePath,
application='outlook',
identifier=ident,
metadata=metadata,
subResources=attachments)
self.result.append(resource)
agents.register(OutlookCrawler, Master, name='crawl.outlook')

View file

@ -1,53 +0,0 @@
================================================
Agents for Job Execution and Communication Tasks
================================================
($Id$)
>>> config = '''
... controller(names=['core.sample'])
... scheduler(name='core')
... logger(name='default', standard=30)
... system.winapi = 'testing'
... '''
>>> from cybertools.agent.main import setup
>>> master = setup(config)
Starting agent application...
Using controllers core.sample.
OutlookCrawler
==============
The agent uses Twisted's cooperative multitasking model.
OutlookCrawler is derived from MailCrawler. The OutlookCrawler returns a deferred
which itself holds a list of MailResource Objects.
Returns a deferred that must be supplied with a callback method (and in
most cases also an errback method).
The TestCase here is using subsidiary methods which replace calls to the "real Outlook
dlls".
>>> controller = master.controllers[0]
>>> controller.createAgent('crawl.outlook', 'sample02')
In the next step we request the start of a job, again via the controller.
>>> controller.enterJob('sample', 'sample02', params=dict(inbox=True))
The job is not executed immediately - we have to hand over control to
the twisted reactor first.
>>> from cybertools.agent.tests import tester
>>> tester.iterate()
Outlook.Application retrieved
Namespace MAPI retrieved
retrieving Outlook default folder
collecting Mails from folder
Attachment: Invitation.pdf
Attachment: 21.pdf
Attachment saved
Attachment saved
Job 00001 completed; result: [<...MailResource...>, <...MailResource...>, <...MailResource...>];

View file

@ -1,278 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
cybertools agent interfaces.
$Id$
"""
from zope.interface import Interface, Attribute
from cybertools.util.jeep import Jeep
# agents
class IAgent(Interface):
""" An agent waits for jobs to execute.
"""
name = Attribute('A name identifying the agent.')
master = Attribute('IMaster instance.')
config = Attribute('Configuration settings.')
logger = Attribute('Logger instance to be used for recording '
'job execution and execution results.')
def send(job):
""" If the agent supports queueing and the agent is busy put
job in queue, otherwise just execute the job.
"""
def execute(job):
""" Execute a job using the parameters given.
"""
class IQueueableAgent(Interface):
""" An agent that keeps a queue of jobs. A queueable agent
executes not more than one job at a time; when a running
job is finished the next one will be taken from the queue.
"""
queue = Attribute('A sequence of jobs to execute.')
def process():
""" Do the real work asynchronously, returning a deferred.
This method will be called by execute().
"""
class IMaster(IAgent):
""" The top-level controller agent.
"""
config = Attribute('Central configuration settings.')
controllers = Attribute('Collection of IController instances.')
scheduler = Attribute('IScheduler instance.')
children = Attribute('A collection of agents that are managed by this '
'master.')
def setup():
""" Set up the master agent by triggering all assigned controllers.
Each controller will then call the master agent's callback
methods ``setupAgents()`` and ``setupJobs()``.
"""
def setupAgents(controller, agentSpecs):
""" Callback for loading agent specifications from the controller
and setting up the corresponding agents.
Will be called upon agent setup and later when the controller
wants to provide new agent information.
"""
def setupJobs(controller, jobSpecs):
""" Callback for loading the specifications of active jobs from
the controller and scheduling the corresponding jobs.
Will be called upon agent setup and later when the controller
wants to provide new job information.
"""
def notify(job, result=None, message=''):
""" Callback for informing the master about the state of a job.
"""
class ICrawler(IAgent):
""" Collects resources.
"""
def collect(filter=None):
""" Return a deferred that upon callback will provide a
collection of resource objects that should be transferred
to the server.
Use the selection criteria given to filter the resources that
should be collected.
"""
class ITransporter(IAgent):
""" Transfers one or more collected resources and corresponding
metadata to another entity - a remote agent or another application.
"""
serverURL = Attribute('URL of the server the resources will be '
'transferred to. The URL also determines the '
'transfer protocol, e.g. HTTP or SFTP.')
method = Attribute('Transport method, e.g. PUT.')
machineName = Attribute('Name under which the local machine is '
'known to the server.')
userName = Attribute('User name for logging in to the server.')
password = Attribute('Password for logging in to the server.')
def transfer(resource):
""" Transfer the resource (an object providing IResource)
to the server and return a Deferred.
"""
# job control
class IController(Interface):
""" Fetches agent and job specifications from a control
storage and updates the storage with the status and result
information.
"""
def setup():
""" Set up the controller; e.g. create agents and jobs by calling
the agent's callback methods.
"""
class IScheduler(Interface):
""" Manages jobs and cares that they are started at the appropriate
time by the agents responsible for it.
"""
def schedule(job, startTime=None):
""" Register the job given for execution at the intended start
date/time (an integer timestamp) and return the job.
If the start time is not given schedule the job for immediate
start. Return the start time with which the job has been
scheduled - this may be different from the start time
supplied.
"""
# jobs
class IScheduledJob(Interface):
""" A job that will be executed on some external triggering at
a predefined date and time - this is the basic job interface.
"""
identifier = Attribute('A name/ID unique within the realm of the '
'controller.')
scheduler = Attribute('Scheduler that controls this job.')
agent = Attribute('Agent responsible for executing the job.')
controller = Attribute('Controller that issued the job.')
startTime = Attribute('Date/time at which the job should be executed.')
params = Attribute('Mapping with key/value pairs to be used by '
'the ``execute()`` method.')
state = Attribute('An object representing the current state of the job.')
repeat = Attribute('Number of seconds after which the job should be '
'rescheduled. Do not repeat if 0.')
successors = Attribute('Jobs to execute immediately after this '
'one has been finished.')
def execute():
""" Execute the job, typically by calling the ``execute()`` method
of the agent responsible for it.
"""
def reschedule(startTime):
""" Re-schedule the job, setting the date/time the job should be
executed again.
"""
# information objects
class IResource(Interface):
""" Represents a data object that is collected by a crawler and
will be transferred to the server.
"""
data = Attribute('A string representation of the '
'resource\'s content; may be None if the receiver of '
'the information can retrieve the data from the file or path '
'attribute.')
file = Attribute('A file-like object providing the data via its read() '
'method; may be None if the data or path attribute '
'is given.')
path = Attribute('A filesystem path for accessing the resource; may be '
'None if the data or file attribute is given.')
identifier = Attribute('A string (usually derived from the path) that '
'uniquely identifies the resource.')
type = Attribute('A string denoting the type of the resource, e.g. '
'"file" or "email".')
contentType = Attribute('A string denoting the MIME type of the data, '
'e.g. "text/plain" or "application/octet-stream"')
encoding = Attribute('Optional: a string denoting the encoding of the '
'file data, e.g. "UTF-8".')
application = Attribute('The name of the application that provided '
'the resource, e.g. "filesystem" or "mail".')
metadata = Attribute('Information describing this resource; '
'should be an IMetadataSet object.')
subResources = Attribute('A collection of resources that are inherently '
'connected to or parts of this resource, e.g. attachments '
'of an email. Will be None or empty in most cases.')
class IMetadataSet(Interface):
""" Metadata associated with a resource; a mapping.
"""
def asXML():
""" Return an XML string representing the metadata set.
If this metadata set contains other metadata sets
(nested metadata) these will be converted to XML as well.
"""
def set(key, value):
""" Set a metadata element.
The value may be a string or another metadata set
(nested metadata).
"""
# logging
class ILogger(Interface):
""" Ordered collection (list) of log records, probably stored on some
external device.
"""
externalLoggers = Attribute('A collection of logger objects '
'to which the logging records should be written.')
def setup():
""" Initialize the logger with the current configuration settings.
"""
def log(data):
""" Record the information given by the ``data`` argument
(a mapping).
"""
class ILogRecord(Interface):
"""
"""
def __str__():
""" Return a string representation suitable for writing to a
log file.
"""

View file

@ -1,77 +0,0 @@
#! /usr/bin/env python2.4
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Agent application.
$Id$
"""
import os
from twisted.internet import reactor
from cybertools.agent.base.agent import Master
application = None # contains application object if started via twistd
def getConfig():
agentHome = os.path.abspath(os.path.dirname(__file__))
configName = 'agent.cfg'
configFile = open(os.path.join(agentHome, configName))
config = configFile.read()
configFile.close()
return config
def setup(configInfo=None):
if configInfo is None:
configInfo = getConfig()
master = Master(configInfo)
setupEnvironment(master.config)
master.setup()
return master
def setupEnvironment(config):
# API registration:
from cybertools.agent.system.windows import api
api.setup(config)
from cybertools.agent.system import http, xmlrpc, sftp
http.setup(config)
xmlrpc.setup(config)
sftp.setup(config)
# self registration of components:
from cybertools.agent.base import agent, control, job, log, schedule
from cybertools.agent.core import agent, control, schedule
from cybertools.agent.control import cmdline, remote
from cybertools.agent.crawl import base, filesystem, outlook
from cybertools.agent.talk import http
from cybertools.agent.transport import remote, loops
def startReactor():
reactor.run()
print 'Agent application has been stopped.'
if __name__ == '__main__':
setup()
startReactor()

View file

@ -1,31 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Configuration-controlled import of HTTP communication functionality.
$Id$
"""
def setup(config):
global listener, getPage
if config.talk.http.handler == 'testing':
from cybertools.agent.testing.http import listener, getPage
else:
from twisted.internet import reactor as listener
from twisted.web.client import getPage

View file

@ -1,30 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Configuration-controlled import of sftp functionality.
$Id: rpcapi.py
"""
def setup(config):
global FileTransfer
if config.transport.remote.sftp == 'testing':
from cybertools.agent.testing.sftp import FileTransfer
else:
from cybertools.agent.transport.file.sftp import FileTransfer

View file

@ -1,39 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Conficuration-controlled import of Windows API functions.
$Id$
"""
def setup(config):
global client, ctypes, win32api, win32process, win32con, com_error
if config.system.winapi == 'testing':
from cybertools.agent.testing.winapi import \
client, ctypes, win32api, win32process, win32con, com_error
else:
try:
from win32com import client
import ctypes
import win32api, win32process, win32con
from pywintypes import com_error
except ImportError:
from cybertools.agent.testing.winapi import \
client, ctypes, win32api, win32process, win32con, com_error

View file

@ -1,59 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Codepages Module
$Id$
"""
codepages = {28596: 'iso-8859-6',\
1256: 'windows-1256',\
28594: 'iso-8859-4',\
1257: 'windows-1257',\
28592: 'iso-8859-2',\
1250: 'windows-1250',\
936: 'gb2312',\
52936: 'hz-gb-2312',\
950: 'big5',\
28595: 'iso-8859-5',\
20866: 'koi8-r',\
21866: 'koi8-u',\
1251: 'windows-1251',\
28597: 'iso-8859-7',\
1253: 'windows-1253',\
38598: 'iso-8859-8-i',\
1255: 'windows-1255',\
51932: 'euc-jp',\
50220: 'iso-2022-jp',\
50221: 'csISO2022JP',\
932: 'iso-2022-jp',\
949: 'ks_c_5601-1987',\
51949: 'euc-kr',\
28593: 'iso-8859-3',\
28605: 'iso-8859-15',\
874: 'windows-874',\
28599: 'iso-8859-9',\
1254: 'windows-1254',\
65000: 'utf-7',\
65001: 'utf-8',\
20127: 'us-ascii',\
1258: 'windows-1258',\
28591: 'iso-8859-1',\
1252: 'Windows-1252'
}

View file

@ -1,56 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Module for handling Outlook dialoges
$Id$
"""
def login(self):
pass
def handleOutlookDialog(self):
"""
This function handles the outlook dialog, which appears if someone
tries to access to MS Outlook.
"""
hwnd = None
while True:
hwnd = api.ctypes.windll.user32.FindWindowExA(None, hwnd, None, None)
if hwnd == None:
break
else:
val = u"\0" * 1024
api.ctypes.windll.user32.GetWindowTextW(hwnd, val, len(val))
val = val.replace(u"\000", u"")
if val and repr(val) == "u'Microsoft Office Outlook'":
print repr(val)
# get the Main Control
form = api.findTopWindow(wantedText='Microsoft Office Outlook')
controls = findControls(form)
# get the check box
checkBox = findControl(form, wantedText='Zugriff')
setCheckBox(checkBox, 1)
# get the combo box
comboBox = findControl(form, wantedClass='ComboBox')
items = getComboboxItems(comboBox)
selectComboboxItem(comboBox, items[3])#'10 Minuten'
# finally get the button and click it
button = findControl(form, wantedText = 'Erteilen')
clickButton(button)
break

View file

@ -1,30 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Configuration controlled import of twisted xmlrpc functionality
$Id: rpcapi.py
"""
def setup(config):
global xmlrpc
if config.transport.remote.server == 'testing':
from cybertools.agent.testing.rpcserver import xmlrpc
else:
from twisted.web import xmlrpc

View file

@ -1,73 +0,0 @@
================================================
Agents for Job Execution and Communication Tasks
================================================
($Id$)
>>> from cybertools.agent.tests import tester
Communication Handling
======================
Communication services are provided by handlers specified in the ``talk``
package.
Set up and start an agent with a server
---------------------------------------
>>> config = '''
... controller(names=['core.sample'])
... scheduler(name='core')
... logger(name='default', standard=30)
... talk.server(names=['http'])
... talk.server.http(port=8081)
... talk.http(handler='testing')
... '''
>>> from cybertools.agent.main import setup
>>> master = setup(config)
Starting agent application...
Using controllers core.sample.
Setting up HTTP handler for port 8081.
>>> master.servers
[<cybertools.agent.talk.http.HttpServer object...>]
We also provide a class to be used for creating subscribers, i.e. objects
that receive messages.
>>> class Subscriber(object):
... def __init__(self, name):
... self.name = name
... def onMessage(self, interaction, data):
... print ('%s receiving: interaction=%s, data=%s' %
... (self.name, interaction, data))
... tester.stop()
>>> serverSub = Subscriber('server')
>>> master.servers[0].subscribe(serverSub, 'testing')
Set up a client
---------------
In order to simplify the testing we do not set up a separate agent to
work with the client but handle the client directly.
>>> from cybertools.agent.talk.http import HttpClient
>>> client = HttpClient(master)
>>> clientSub = Subscriber('client')
>>> session = client.connect(clientSub, 'http://localhost:8081/')
Run the communication dialog
----------------------------
>>> tester.run()
client receiving: interaction=None, data={u'status': u'OK'}
Fin de Partie
=============
>>> tester.stopThreads()

View file

@ -1,93 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Handling asynchronous communication tasks - common and base classes.
$Id$
"""
from twisted.web.client import getPage
from zope.interface import implements
from cybertools.agent.talk.interfaces import ISession, IInteraction
from cybertools.util import json
class Session(object):
implements(ISession)
def __init__(self, id, manager, subscriber, url):
self.id = id
self.manager = manager
self.subscriber = subscriber
self.url = url
self.state = 'logon'
self.sending = False
self.queue = []
self.interactions = {}
self.interactionCount = 0
def received(self, data):
data = json.loads(data)
# TODO: check data; notify sender?
self.sending = False
self._processQueue()
def send(self, data, interaction):
data['interaction'] = interaction.id
if self.sending or self.queue:
self.queue.append(data)
else:
self._sendData(data)
def processQueue(self):
if not self.queue:
return
self._sendData(self.queue.pop(0))
def sendData(self, data, command='send'):
self.sending = True
content = dict(id=self.id, command=command, data=data)
d = getPage(self.url, postdata=json.dumps(content))
d.addCallback(s.received)
def connected(self, data):
data = json.loads(data)
self.state = 'open'
self.subscriber.onMessage(None, data)
self.sending = False
self.processQueue()
def generateInteractionId(self):
self.interactionCount += 1
return '%07i' % self.interactionCount
class Interaction(object):
implements(IInteraction)
finished = False
def __init__(self, session):
self.session = session
self.id = self.session.generateInteractionId()
self.session.interactions[self.id] = self

View file

@ -1,181 +0,0 @@
#
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Handling asynchronous and possibly asymmetric communication tasks via HTTP.
$Id$
"""
from time import time
from twisted.web.client import getPage
from twisted.web.resource import Resource
from twisted.web.server import Site, NOT_DONE_YET
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.components import servers, clients
from cybertools.agent.system.http import listener
from cybertools.agent.talk.base import Session, Interaction
from cybertools.agent.talk.interfaces import IServer, IClient
from cybertools.util import json
# server implementation
#@server
class HttpServer(object):
implements(IServer)
def __init__(self, agent):
self.agent = agent
self.port = agent.config.talk.server.http.port
self.subscribers = {}
self.sessions = {}
self.site = Site(RootResource(self))
def setup(self):
print 'Setting up HTTP handler for port %i.' % self.port
listener.listenTCP(self.port, self.site)
def subscribe(self, subscriber, aspect):
subs = self.subscribers.setdefault(aspect, [])
if subscriber not in subs:
subs.append(subscriber)
def unsubscribe(self, subscriber, aspect):
pass
def send(self, session, data, interaction=None):
if interaction is None:
interaction = Interaction(session)
# check session's queue
# check open poll - write response
return interaction
def process(self, client, data):
action = data.get('action')
if not action:
return self._error('missing action')
amethod = self.actions.get(action)
if amethod is None:
return self._error('illegal action %r' % action)
sid = data.get('session')
if not sid:
return self._error('missing session id')
sessionId = ':'.join((client, sid))
message = amethod(self, sessionId, client, data)
if message:
return self._error(message)
return '{"status": "OK"}'
def _connect(self, sessionId, client, data):
if sessionId in self.sessions:
return 'duplicate session id %r' % sessionId
self.sessions[sessionId] = HttpServerSession(sessionId, self, None, client)
# TODO: notify subscribers
def _poll(self, sessionId, client, data):
# record deferred with session
return NOT_DONE_YET
def _send(self, sessionId, client, data):
for sub in self.subscribers.values():
sub.onMessage(data)
def _error(self, message):
return json.dumps(dict(status='error', message=message))
actions = dict(connect=_connect, poll=_poll, send=_send)
servers.register(HttpServer, Master, name='http')
class RootResource(Resource):
isLeaf = True
def __init__(self, server):
self.server = server
def render(self, request):
client = request.getClient()
data = json.loads(request.content.read())
return self.server.process(client, data)
class HttpServerSession(Session):
pass
# client implementation
#@client
class HttpClient(object):
implements(IClient)
def __init__(self, agent):
self.agent = agent
self.sessions = {}
def connect(self, subscriber, url, credentials=None):
id = self.generateSessionId()
s = HttpClientSession(self, id, subscriber, url)
self.sessions[id] = s
data = dict(action='connect', session=id)
if credentials is not None:
data.update(credentials)
# s.send(data, None)
d = getPage(url, postdata=json.dumps(data))
d.addCallback(s.connected)
return s
def disconnect(self, session):
pass
def send(self, session, data, interaction=None):
if interaction is None:
interaction = Interaction(session)
session.send(data, interaction)
return interaction
def generateSessionId(self):
return '%.7f' % time()
clients.register(HttpClient, Master, name='http')
class HttpClientSession(Session):
def connected(self, data):
super(HttpClientSession, self).connected(data)
# self.poll()
def pollReceived(self, data):
data = json.loads(data)
if data.get('action') != 'idle':
self.subscriber.onMessage(interaction, data)
# self.poll()
def poll(self):
content = dict(id=self.id, command='poll')
d = getPage(self.url, postdata=json.dumps(content))
d.addCallback(s.pollReceived)

View file

@ -1,121 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Interfaces for handling asynchronous communication tasks.
$Id$
"""
from zope.interface import Interface, Attribute
class IServer(Interface):
""" A server waits for connection requests from a client. A connected
client may then send data to or receive messages from the server.
"""
def subscribe(subscriber, aspect):
""" The subscriber will receive messages via its ``onMesssage`` method.
The aspect is a dotted string used to select the kind of
sessions/remote clients the subscriber wants to receive messages
from.
"""
def unsubscribe(subscriber, aspect):
""" Stop receiving messages.
"""
def send(session, data, interaction=None):
""" Send data to the remote client specified via the session given.
The session has to be created previously by a connect attempt
from the client.
If interaction is None, create a new one.
Return the interaction.
"""
class IClient(Interface):
""" A client initiates a connection (session) to a server and may then
sent data to or receive data from the server.
"""
def connect(subscriber, url, credentials=None):
""" Connect to a server using the URL given, optionally logging in
with the credentials given.
The subscriber will receive messages via its ``onMesssage`` callback.
Return a an ISession implementation that may be used for sending
data to the server.
"""
def disconnect(session):
""" Close the connection for the session given.
"""
def send(session, data, interaction=None):
""" Send data to the server specified via the session given.
If interaction is None, create a new one.
Return the interaction.
Sending an interaction with ``finished`` set to True signifies
the last message of an interaction.
"""
# auxiliary interfaces
class ISubscriber(Interface):
""" May receive message notifications.
"""
def onMessage(interaction, data):
""" Callback method for message notifications.
"""
def onError(interaction, data):
""" Callback method for error notifications.
"""
class ISession(Interface):
""" Represents the connection to a server within a client or
a remote client connection within a server.
"""
manager = Attribute("""The server or client object, respectively, that
created the session.""")
subscriber = Attribute("The subscriber that initiated the session.")
url = Attribute("The URL of the server (or client) the session connects to.")
state = Attribute("""A string specifying the current state of the session:
'logon': The remote client is trying to connect/log in,
data may contain credential information;
'logoff': The remote client is closing the connection;
'open': The connection is open.""")
class IInteraction(Interface):
""" Represents a set of message exchanges belonging together.
"""
session = Attribute("The session the interaction belongs to.")
finished = Attribute("The interaction is finished, interaction data may be cleared.")

View file

@ -1,9 +0,0 @@
#
# Standard configuration for agent application
#
# $Id$
#
controller(names=['test'])
scheduler(name='core')
logger(name='default', standard=30)

View file

@ -1,43 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Controller for testing purposes.
$Id$
"""
from zope.interface import implements
from cybertools.agent.base.agent import Master
from cybertools.agent.base.control import SampleController, JobSpecification
from cybertools.agent.components import controllers
from cybertools.agent.crawl.mail import MailResource
class Controller(SampleController):
agents = (('tr1', 'transport.remote'),)
def _getCurrentJobs(self):
print '_getCurrentJobs'
return [JobSpecification('sample', '00001', agent='tr1',
params=dict(resource=MailResource()))]
controllers.register(Controller, Master, name='test')

View file

@ -1 +0,0 @@
Data from file1.txt

View file

@ -1 +0,0 @@
Data from file2.txt

View file

@ -1,44 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Fake testing objects/functions for HTTP communication.
$Id$
"""
from twisted.internet.defer import Deferred, succeed
class Listener(object):
site = port = None
def listenTCP(self, port, site):
self.port = port
self.site = site
self.resource = site.resource
deferred = self.deferred = Deferred()
return deferred
listener = Listener()
def getPage(url, contextFactory=None, method='GET', postdata=None, **kwargs):
return succeed('{"message": "OK"}')

View file

@ -1,73 +0,0 @@
#! /usr/bin/env python2.4
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Agent application.
$Id$
"""
import os
from twisted.internet import reactor
from cybertools.agent.base.agent import Master
application = None # contains application object if started via twistd
def getConfig():
home = os.path.abspath(os.path.dirname(__file__))
configName = 'agent.cfg'
configFile = open(os.path.join(home, configName))
config = configFile.read()
configFile.close()
return config
def setup(configInfo=None):
if configInfo is None:
configInfo = getConfig()
master = Master(configInfo)
setupEnvironment(master.config)
master.setup()
print 'Starting agent application...'
print 'Using controllers %s.' % ', '.join(master.config.controller.names)
return master
def setupEnvironment(config):
from cybertools.agent.base import agent, control, job, log, schedule
from cybertools.agent.core import agent, control, schedule
from cybertools.agent.control import cmdline, remote
from cybertools.agent.transport import remote, loops
from cybertools.agent.testing import control
from cybertools.agent.system.windows import api
api.setup(config)
from cybertools.agent.crawl import base, filesystem, outlook
def startReactor():
reactor.run()
print 'Agent application has been stopped.'
if __name__ == '__main__':
setup()
startReactor()

View file

@ -1,74 +0,0 @@
#! /usr/bin/env python2.4
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Agent application.
$Id$
"""
import os
from twisted.internet import reactor
from cybertools.agent.base.agent import Master
application = None # contains application object if started via twistd
def getConfig():
agentHome = os.path.abspath(os.path.dirname(__file__))
configName = 'outlook.cfg'
configFile = open(os.path.join(agentHome, configName))
config = configFile.read()
configFile.close()
return config
def setup(configInfo=None):
if configInfo is None:
configInfo = getConfig()
master = Master(configInfo)
setupEnvironment(master.config)
master.setup()
print 'Starting agent application...'
print 'Using controllers %s.' % ', '.join(master.config.controller.names)
return master
def setupEnvironment(config):
from cybertools.agent.base import agent, control, job, log, schedule
from cybertools.agent.core import agent, control, schedule
from cybertools.agent.control import cmdline
from cybertools.agent.system.windows import api
api.setup(config)
from cybertools.agent.crawl import base, outlook
def startReactor():
reactor.run()
print 'Agent application has been stopped.'
if __name__ == '__main__':
master = setup()
controller = master.controllers[0]
controller.createAgent('crawl.outlook', 'sample02')
controller.enterJob('sample', 'sample02', params=dict(inbox=True))
startReactor()

View file

@ -1,82 +0,0 @@
#! /usr/bin/env python2.4
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Agent application.
$Id$
"""
import sys
import os
from twisted.internet import reactor
from cybertools.agent.base.agent import Master
from cybertools.agent.crawl.base import Metadata, Resource
application = None # contains application object if started via twistd
def getConfig():
agentHome = os.path.abspath(os.path.dirname(__file__))
configName = 'transporter.cfg'
configFile = open(os.path.join(agentHome, configName))
config = configFile.read()
configFile.close()
return config
def setup(configInfo=None):
if configInfo is None:
configInfo = getConfig()
master = Master(configInfo)
setupEnvironment(master.config)
master.setup()
print 'Starting agent application...'
print 'Using controllers %s.' % ', '.join(master.config.controller.names)
return master
def setupEnvironment(config):
from cybertools.agent.base import agent, control, job, log, schedule
from cybertools.agent.core import agent, control, schedule
from cybertools.agent.control import cmdline
from cybertools.agent.system import rpcapi
rpcapi.setup(config)
from cybertools.agent.system import sftpapi
sftpapi.setup(config)
from cybertools.agent.transport import remote
def startReactor():
reactor.run()
print 'Agent application has been stopped.'
if __name__ == '__main__':
master = setup()
controller = master.controllers[0]
controller.createAgent('transport.remote', 'sample03')
metadata01 = Metadata(dict(filename='dummy.txt'))
res01 = Resource()
res01.metadata = metadata01
res01.path = 'data/file1.txt'
controller.enterJob('sample', 'sample03', params=dict(resource=res01))
startReactor()

View file

@ -1,10 +0,0 @@
#
# Standard configuration for agent application
#
# $Id: outlook.cfg 2496 2008-04-04 08:07:22Z helmutm $
#
controller(names=['core.sample'])
scheduler(name='core')
logger(name='default', standard=30)
system.winapi = 'use_outlook'

View file

@ -1,135 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Fake rpcserver for testing purposes
$Id$
"""
from twisted.internet.defer import succeed
class RPCServer(object):
serverURL = ''
method = ''
machineName = ''
userName = ''
password = ''
controller = ''
def __init__(self, serverURL = '', method = '', machineName = '',
userName = '', password = '', controlObj= None):
self.serverURL = serverURL
self.method = method
self.machineName = machineName
self.userName = userName
self.password = password
self.controller = controlObj
def getMetadata(self, metadata):
if self.controller is not None:
# pass metadata to controller
# this is done AFTER the resource (like e.g. file or mail)
# is handed over
pass
deferred = succeed('Metadata accepted by server')
return deferred
def xmlrpc_shutdownRPCServer():
return "xmlrRPC server shutdown completed!"
class XmlRpc(object):
Proxy = None
XMLRPC = None
Handler = None
XMLRPCIntrospection = None
QueryProtocol = None
_QueryFactory = None
def __init__(self):
self.Proxy = Proxy
#self.XMLRPC = XMLRPC()
#self.Handler = Handler()
#self.XMLRPCIntrospection = XMLRPCIntrospection()
#self.QueryProtocol = QueryProtocol()
#self._QueryFactory = _QueryFactory()
def addIntrospection(self, xmlrpc):
pass
class Proxy(object):
url = ''
user = None
password = None
allowNone = False
queryFactory = None
def __init__(self, url, user=None, password=None, allowNone=False):
self.url = url
self.user = user
self.password = password
self.allowNone = allowNone
self.RPCServer = RPCServer()
def callRemote(self, methodName, *params):
"""
intended to simulate the callRemote command of a real xmlrpcserver
that takes a method name and calls the method, returning the results
as xml formatted strings
"""
method = getattr(self.RPCServer, methodName)
return method(*params)
xmlrpc = XmlRpc()
class XMLRPC(object):
def __init__(self):
pass
class Handler(object):
def __init__(self):
pass
class XMLRPCIntrospection(object):
def __init__(self):
pass
class QueryProtocol(object):
def __init__(self):
pass
class _QueryFactory(object):
def __init__(self):
pass

View file

@ -1,37 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Fake sftp class for testing purposes
$Id$
"""
from twisted.internet.defer import succeed
class FileTransfer(object):
def __init__(self, host, port, username, password):
pass
def upload(self, localPath, remotePath):
deferred = succeed('Upload completed')
return deferred

View file

@ -1,80 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Providing access for remote agent instances by listening for requests
from remote transport agents.
$Id$
"""
from twisted.web import xmlrpc, server, resource
from twisted.internet import defer, reactor
from cybertools.agent.base.agent import Agent
application = None
class RPCServer(xmlrpc.XMLRPC):
serverURL = ''
method = ''
machineName = ''
userName = ''
password = ''
controller = ''
close = reactor.stop
def __init__(self, serverURL = '', method = '', machineName = '',
userName = '', password = '', controlObj= None):
self.serverURL = serverURL
self.method = method
self.machineName = machineName
self.userName = userName
self.password = password
self.controller = controlObj
xmlrpc.XMLRPC.__init__(self)
def xmlrpc_transfer(self, resource):
if self.controller is not None:
# pass resource object to controller
# this is done BEFORE the metadata is handed over
# call notify method of controller
pass
print resource
return "Resource received: ", resource
def xmlrpc_getMetadata(self, metadata):
if self.controller is not None:
# pass metadata to controller
# this is done AFTER the resource (like e.g. file or mail)
# is handed over
pass
print '*** metadata', metadata
metadata = "Echo: ", metadata
return metadata
def xmlrpc_shutdownRPCServer():
self.close()
if __name__ == '__main__':
from twisted.internet import reactor
site = RPCServer()
reactor.listenTCP(8082, server.Site(site))
print '*** listening...'
reactor.run()

View file

@ -1,18 +0,0 @@
from twisted.internet import reactor
from cybertools.agent.transport.file.sftp import FileTransfer
def output(x):
print x
ft = FileTransfer('cy05.de', 22, 'scrat', '...')
d = ft.upload('d:\\text2.rtf', 'text.txt')
d.addCallback(output)
reactor.callLater(21, ft.close)
reactor.callLater(32, reactor.stop)
reactor.run()

View file

@ -1,17 +0,0 @@
#
# sample.cfg - agent configuration for demonstration and testing purposes
#
# $Id$
#
# transportserver.xmlrpc='testing'
controller(names=['core.sample'])
scheduler(name='core')
logger(name='default', standard=30)
#transport.remote.server = 'testing'
transport.remote.url = 'http://localhost:8082'
transport.remote.ftp.url = 'cy05.de'
transport.remote.ftp.user = 'scrat'
transport.remote.ftp.password = '...'
transport.remote.sftp = 'http://cy05.de'
transport.remote.chunksize = 4096

View file

@ -1,240 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Fake Windows API functions for testing purposes.
$Id$
"""
win32api = win32process = win32con = None
class com_error(Exception):
pass
class Attachments(list):
elemCount = 0
data = []
def __init__(self, params=[]):
for elem in params:
fileitem = Attachment(filename=elem[0], ParentMail=elem[1])
self.data.append(fileitem)
print "Attachment: ", fileitem.FileName
@property
def Application(self):
print "Outlook application instance"
return "Outlook application instance"
def Item(self, index):
return self.data[index-1]
@property
def count(self):
return len(data)
def __len__(self):
return len(self.data)
def __iter__(self):
yield self.data
def __getitem__(self, idx):
return self.data[idx]
class Attachment(object):
File = ""
parentMailObject = None
def __init__(self, ParentMail, filename=""):
self.File = filename
self.parentMailObject = ParentMail
def SaveAsFile(self, path=""):
print "Attachment saved"
@property
def Parent(self):
" return value of Attribute Parent is of type _MailItem"
return self.parentMailObject
@property
def Type(self):
pass
@property
def Size(self):
# the size property is not available in Outlook 2000
pass
@property
def Application(self):
" Actual instance of Outlook application"
return None
@property
def FileName(self):
return self.File
class Mail(object):
#this is just a guess what a Outlook Mail Object Probably returns
#Class = client.constants.olMail
def __init__(self, subj="", sendName="", to="", body="", **kw):
self.Class = client.constants.olMail
self.Subject = subj
self.SenderName = sendName
self.To = to
self.Body = body
for k, v in kw.items():
setattr(self, k, v)
def addAttachment(self, **kw):
"""
this is a method which probably does not exist in a real mail
Currently this is a work around to add attachments to a mail
"""
for k, v in kw.items():
setattr(self, k, v)
@property
def _prop_map_get_(self):
#here it is necessary of what attributes (called keys in outlok.py)
#an Outlook Mail typically has
return self.__dict__
class Items(object):
temp = {}
data = []
def __init__(self):
self.data.append(Mail(subj="Python Training",
sendName="Mark Pilgrim",
to="allPythonics@python.org",
body="The training will take place on Wed, 21st Dec.\
Kindly check the enclosed invitation.",
BodyFormat=1
))
self.data[0].addAttachment(Attachments=Attachments([("Invitation.pdf", self.data[0]), ("21.pdf", self.data[0])]))
self.data.append(Mail(subj="Information Technolgies Inc. Test it!",
sendName="IT.org",
to="allUser@internet.com",
BodyFormat=2,
HTMLBody="<html>\
<head>\
<title>Test-HTML-Mail</title>\
</head>\
<body>\
<h1>Das ist eine HTML-Mail</h1>\
<div align='center'>Hier steht \
<b>Beispiel</b>-Text</div>\
</body>\
</html>",
SentOn="21.04.07"
))
self.data.append(Mail(subj="@ Product Details @",
sendName="",
senderEmailAddress="custominfo@enterprise.com",
to="recipient1@mail.com, customer@web.de",
BodyFormat=1,
body="Dear customer,\
Hereby we submit you the information you ordered.\
Please feel free to ask anytime you want.\
Sincerely, Customer Support",
SentOn="30.07.07"
))
def Item(self, idx):
return self.data[idx-1]
def __len__(self):
return len(self.data)
class OutlookFolder(object):
# Folders defines in Outlook the sub folders under the "Main" Folder
Folders = None
def __init__(self):
print "collecting Mails from folder"
self.Items = Items()
class OutlookNamespace(object):
def __init__(self):
pass
def GetDefaultFolder(self, message=""):
print "retrieving Outlook default folder"
folder = OutlookFolder()
return folder
class OutlookApp(object):
def __init__(self):
pass
def GetNamespace(self, message=""):
print "Namespace " + message + " retrieved"
oNamespace = OutlookNamespace()
return oNamespace
class Message(object):
olFolderInbox = None
# esp. for olMail, for further dummy implementations it is necessary
# to find out, what class is expected. Meaning what type of object has
# to be faked and what attributes it has. see outlook.py
# loadMailsfromFolder
olMail = Mail
def __init__(self):
pass
def EnsureDispatch(self, message=""):
print message + " retrieved"
oApp = OutlookApp()
return oApp
class client(object):
gencache = Message()
constants = Message()
def __init__(self):
pass
class ctypes(object):
def __init__(self):
pass

View file

@ -1,74 +0,0 @@
#! /usr/bin/env python
# $Id$
import os, time
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from twisted.internet import reactor
#from twisted.internet.defer import Deferred
#from twisted.trial import unittest as trial_unittest
baseDir = os.path.dirname(__file__)
class Tester(object):
""" Used for controlled execution of reactor iteration cycles.
"""
stopped = False
def iterate(self, n=10, delay=0):
self.stopped = False
for i in range(n):
if self.stopped:
return
reactor.iterate(delay)
def run(self, maxduration=1.0, delay=0):
self.stopped = False
end = time.time() + maxduration
while not self.stopped:
reactor.iterate(delay)
if time.time() >= end:
return
def stop(self):
self.stopped = True
def stopThreads(self):
reactor.threadpool.stop()
reactor.threadpool = None
tester = Tester()
class Test(unittest.TestCase):
"Basic tests for the cybertools.agent package."
def setUp(self):
pass
def tearDown(self):
pass
def testBasicStuff(self):
pass
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
testSuite = unittest.TestSuite((
unittest.makeSuite(Test),
DocFileSuite('README.txt', optionflags=flags),
DocFileSuite('crawl/README.txt', optionflags=flags),
DocFileSuite('crawl/filesystem.txt', optionflags=flags),
DocFileSuite('crawl/outlook.txt', optionflags=flags),
DocFileSuite('transport/transporter.txt', optionflags=flags),
DocFileSuite('talk/README.txt', optionflags=flags),
))
return testSuite
if __name__ == '__main__':
standard_unittest.main(defaultTest='test_suite')

View file

@ -1,160 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Transferring files to a remote site via SFTP.
$Id$
"""
from twisted.conch.ssh import channel, common, connection
from twisted.conch.ssh import filetransfer, transport, userauth
from twisted.internet import defer, protocol, reactor
CHUNKSIZE = 4096
class FileTransfer(protocol.ClientFactory):
""" Transfers files to a remote SCP/SFTP server.
"""
channel = None
def __init__(self, host, port, username, password):
self.username = username
self.password = password
self.queue = []
reactor.connectTCP(host, port, self)
def buildProtocol(self, addr):
protocol = self.protocol = ClientTransport(self)
return protocol
def upload(self, localPath, remotePath):
""" Copies a file, returning a deferred.
"""
d = self.deferred = defer.Deferred()
# we put everything in a queue so that more than one file may
# be transferred in one connection.
self.queue.append(dict(deferred=d,
command='upload',
localPath=localPath,
remotePath=remotePath))
if len(self.queue) == 1 and self.channel is not None:
# the channel has emptied the queue
self.channel.execute()
return d
def close(self):
# TODO: put in queue...
self.protocol.transport.loseConnection()
print 'connection closed'
class SFTPChannel(channel.SSHChannel):
""" An SSH channel using the SFTP subsystem for transferring files
and issuing other filesystem requests.
"""
name = 'session'
remFile = ''
remOffset = 0
def channelOpen(self, data):
d = self.conn.sendRequest(self, 'subsystem', common.NS('sftp'), wantReply=1)
d.addCallback(self.channelOpened)
def channelOpened(self, data):
self.client = filetransfer.FileTransferClient()
self.client.makeConnection(self)
self.dataReceived = self.client.dataReceived
self.execute()
self.conn.factory.channel = self
def execute(self):
queue = self.conn.factory.queue
if queue:
command = queue.pop()
commandName = command.pop('command')
method = getattr(self, 'command_' + commandName, None)
if method is not None:
self.params = command
method()
def command_upload(self):
params = self.params
remotePath = params['remotePath']
localPath = params['localPath']
self.localFile = open(localPath, 'rb')
d = self.client.openFile(remotePath,
filetransfer.FXF_WRITE | filetransfer.FXF_CREAT, {})
d.addCallbacks(self.writeChunk, self.logError)
def writeChunk(self, remoteFile):
if isinstance(remoteFile, tuple) == False:
self.remFile = remoteFile
data = self.localFile.read(CHUNKSIZE)
if len(data) < CHUNKSIZE:
self.d = self.remFile.writeChunk(self.remOffset, data)
self.d.addCallbacks(self.finished, self.logError)
else:
self.d = self.remFile.writeChunk(self.remOffset, data)
self.remOffset = self.remOffset + CHUNKSIZE
self.d.addCallbacks(self.writeChunk, self.logError)
def logError(self, reason):
print 'error', reason
def finished(self, result):
self.localFile.close()
self.remFile.close()
#self.d.callback('finished')
self.conn.factory.deferred.callback('finished')
# classes for managing the SSH protocol and connection
class ClientTransport(transport.SSHClientTransport):
def __init__(self, factory):
self.factory = factory
def verifyHostKey(self, pubKey, fingerprint):
# this is insecure!!!
return defer.succeed(True)
def connectionSecure(self):
self.requestService(UserAuth(self.factory, ClientConnection(self.factory)))
class ClientConnection(connection.SSHConnection):
def __init__(self, factory):
connection.SSHConnection.__init__(self)
self.factory = factory
def serviceStarted(self):
self.openChannel(SFTPChannel(conn=self))
class UserAuth(userauth.SSHUserAuthClient):
def __init__(self, factory, connection):
userauth.SSHUserAuthClient.__init__(self, factory.username, connection)
self.password = factory.password
def getPassword(self, prompt=None):
return defer.succeed(self.password)

View file

@ -1,27 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Transferring information to a loops site on a local Zope instance.
$Id$
"""
from zope.interface import implements

View file

@ -1,92 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Transferring information to or requesting information from a remote
cybertools.agent instance by transferring files to the remote system
and sending requests to a corresponding remote controller.
$Id$
"""
from twisted.internet import defer
from zope.interface import implements
import os
from cybertools.agent.system import xmlrpc
from cybertools.agent.system import sftp
from cybertools.agent.base.agent import Master
from cybertools.agent.core.agent import QueueableAgent
from cybertools.agent.interfaces import ITransporter
from cybertools.agent.crawl.base import Metadata
from cybertools.agent.crawl.mail import MailResource
from cybertools.agent.crawl.filesystem import FileResource
from cybertools.agent.components import agents
from cybertools.util.config import Configurator
class Transporter(QueueableAgent):
implements(ITransporter)
port = 22
machineName = ''
def __init__(self, master):
super(Transporter, self).__init__(master)
config = master.config
serverURL = config.transport.remote.url
self.server = xmlrpc.xmlrpc.Proxy(serverURL)
userName = config.transport.remote.ftp.user
password = config.transport.remote.ftp.password
host = config.transport.remote.ftp.url
self.ftpServer = sftp.FileTransfer(host, self.port, userName, password)
def process(self):
return self.transfer(self.params['resource'])
def transfer(self, resource):
""" Transfer the resource (an object providing IResource)
to the server and return a Deferred.
"""
self.deferred = defer.Deferred()
remoteFile = os.path.basename(resource.path)
d = self.ftpServer.upload(resource.path, remoteFile)
d.addErrback(self.errorHandler)
d.addCallback(lambda result:
self.server.callRemote('getMetadata', dict(resource.metadata)))
d.addCallback(self.transferDone)
return self.deferred
def errorHandler(self, errorInfo):
"""
Invoked as a callback from self.transfer
Error handler.
"""
print errorInfo
#self.server.close()
def transferDone(self, result):
"""
Invoked as a callback from self.transfer
This callback method is called when resource and metadata
have been transferred successfully.
"""
self.deferred.callback(result)
agents.register(Transporter, Master, name='transport.remote')

View file

@ -1,52 +0,0 @@
================================================
Agents for Job Execution and Communication Tasks
================================================
($Id$)
>>> config = '''
... controller(names=['core.sample'])
... scheduler(name='core')
... logger(name='default', standard=30)
... transport.remote.server = 'testing'
... transport.remote.sftp = 'testing'
... transport.remote.url = 'http://localhost:8123'
... '''
>>> from cybertools.agent.main import setup
>>> master = setup(config)
Starting agent application...
Using controllers core.sample.
Transporter
===========
The agent uses Twisted's cooperative multitasking model.
The Transporter is used to contact an xmlrpc Server and transmit the metadata
to the other loops system. The Transporter is derived from Queueable agent
to ensure that only one item at a time is transmitted.
Returns a deferred that must be supplied with a callback method (and in
most cases also an errback method).
This Testcase is using subsidiary methods to simulate a real xmlrpc server.
>>> controller = master.controllers[0]
>>> controller.createAgent('transport.remote', 'sample03')
In the next step we request the start of a job, again via the controller.
>>> from cybertools.agent.crawl.base import Metadata, Resource
>>> md01 = Metadata(dict(filename='dummy.txt'))
>>> r01 = Resource()
>>> r01.metadata = md01
>>> r01.path = 'resource.txt'
>>> controller.enterJob('sample', 'sample03', params=dict(resource=r01))
The job is not executed immediately - we have to hand over control to
the twisted reactor first.
>>> from cybertools.agent.tests import tester
>>> tester.iterate()
Job 00001 completed; result: Metadata accepted by server;

View file

@ -1,368 +0,0 @@
# -*- test-case-name: twisted.test.test_task -*-
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
# See LICENSE for details.
"""Scheduling utility methods and classes.
API Stability: Unstable
@author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
"""
__metaclass__ = type
import time
from twisted.python.runtime import seconds
from twisted.python import reflect
from twisted.internet import base, defer
class LoopingCall:
"""Call a function repeatedly.
@ivar f: The function to call.
@ivar a: A tuple of arguments to pass the function.
@ivar kw: A dictionary of keyword arguments to pass to the function.
If C{f} returns a deferred, rescheduling will not take place until the
deferred has fired. The result value is ignored.
"""
call = None
running = False
deferred = None
interval = None
count = None
starttime = None
def _callLater(self, delay):
from twisted.internet import reactor
return reactor.callLater(delay, self)
_seconds = staticmethod(seconds)
def __init__(self, f, *a, **kw):
self.f = f
self.a = a
self.kw = kw
def start(self, interval, now=True):
"""Start running function every interval seconds.
@param interval: The number of seconds between calls. May be
less than one. Precision will depend on the underlying
platform, the available hardware, and the load on the system.
@param now: If True, run this call right now. Otherwise, wait
until the interval has elapsed before beginning.
@return: A Deferred whose callback will be invoked with
C{self} when C{self.stop} is called, or whose errback will be
invoked when the function raises an exception or returned a
deferred that has its errback invoked.
"""
assert not self.running, ("Tried to start an already running "
"LoopingCall.")
if interval < 0:
raise ValueError, "interval must be >= 0"
self.running = True
d = self.deferred = defer.Deferred()
self.starttime = self._seconds()
self.count = 0
self.interval = interval
if now:
self()
else:
self._reschedule()
return d
def stop(self):
"""Stop running function.
"""
assert self.running, ("Tried to stop a LoopingCall that was "
"not running.")
self.running = False
if self.call is not None:
self.call.cancel()
self.call = None
d, self.deferred = self.deferred, None
d.callback(self)
def __call__(self):
def cb(result):
if self.running:
self._reschedule()
else:
d, self.deferred = self.deferred, None
d.callback(self)
def eb(failure):
self.running = False
d, self.deferred = self.deferred, None
d.errback(failure)
self.call = None
d = defer.maybeDeferred(self.f, *self.a, **self.kw)
d.addCallback(cb)
d.addErrback(eb)
def _reschedule(self):
if self.interval == 0:
self.call = self._callLater(0)
return
fromNow = self.starttime - self._seconds()
while self.running:
self.count += 1
fromStart = self.count * self.interval
delay = fromNow + fromStart
if delay > 0:
self.call = self._callLater(delay)
return
def __repr__(self):
if hasattr(self.f, 'func_name'):
func = self.f.func_name
if hasattr(self.f, 'im_class'):
func = self.f.im_class.__name__ + '.' + func
else:
func = reflect.safe_repr(self.f)
return 'LoopingCall<%r>(%s, *%s, **%s)' % (
self.interval, func, reflect.safe_repr(self.a),
reflect.safe_repr(self.kw))
class SchedulerStopped(Exception):
"""
The operation could not complete because the scheduler was stopped in
progress or was already stopped.
"""
class _Timer(object):
MAX_SLICE = 0.01
def __init__(self):
self.end = time.time() + self.MAX_SLICE
def __call__(self):
return time.time() >= self.end
_EPSILON = 0.00000001
def _defaultScheduler(x):
from twisted.internet import reactor
return reactor.callLater(_EPSILON, x)
class Cooperator(object):
"""
Cooperative task scheduler.
"""
def __init__(self,
terminationPredicateFactory=_Timer,
scheduler=_defaultScheduler,
started=True):
"""
Create a scheduler-like object to which iterators may be added.
@param terminationPredicateFactory: A no-argument callable which will
be invoked at the beginning of each step and should return a
no-argument callable which will return False when the step should be
terminated. The default factory is time-based and allows iterators to
run for 1/100th of a second at a time.
@param scheduler: A one-argument callable which takes a no-argument
callable and should invoke it at some future point. This will be used
to schedule each step of this Cooperator.
@param started: A boolean which indicates whether iterators should be
stepped as soon as they are added, or if they will be queued up until
L{Cooperator.start} is called.
"""
self.iterators = []
self._metarator = iter(())
self._terminationPredicateFactory = terminationPredicateFactory
self._scheduler = scheduler
self._delayedCall = None
self._stopped = False
self._started = started
def coiterate(self, iterator, doneDeferred=None):
"""
Add an iterator to the list of iterators I am currently running.
@return: a Deferred that will fire when the iterator finishes.
"""
if doneDeferred is None:
doneDeferred = defer.Deferred()
if self._stopped:
doneDeferred.errback(SchedulerStopped())
return doneDeferred
self.iterators.append((iterator, doneDeferred))
self._reschedule()
return doneDeferred
def _tasks(self):
terminator = self._terminationPredicateFactory()
while self.iterators:
for i in self._metarator:
yield i
if terminator():
return
self._metarator = iter(self.iterators)
def _tick(self):
"""
Run one scheduler tick.
"""
self._delayedCall = None
for taskObj in self._tasks():
iterator, doneDeferred = taskObj
try:
result = iterator.next()
except StopIteration:
self.iterators.remove(taskObj)
doneDeferred.callback(iterator)
except:
self.iterators.remove(taskObj)
doneDeferred.errback()
else:
if isinstance(result, defer.Deferred):
self.iterators.remove(taskObj)
def cbContinue(result, taskObj=taskObj):
self.coiterate(*taskObj)
result.addCallbacks(cbContinue, doneDeferred.errback)
self._reschedule()
_mustScheduleOnStart = False
def _reschedule(self):
if not self._started:
self._mustScheduleOnStart = True
return
if self._delayedCall is None and self.iterators:
self._delayedCall = self._scheduler(self._tick)
def start(self):
"""
Begin scheduling steps.
"""
self._stopped = False
self._started = True
if self._mustScheduleOnStart:
del self._mustScheduleOnStart
self._reschedule()
def stop(self):
"""
Stop scheduling steps. Errback the completion Deferreds of all
iterators which have been added and forget about them.
"""
self._stopped = True
for iterator, doneDeferred in self.iterators:
doneDeferred.errback(SchedulerStopped())
self.iterators = []
if self._delayedCall is not None:
self._delayedCall.cancel()
self._delayedCall = None
_theCooperator = Cooperator()
def coiterate(iterator):
"""
Cooperatively iterate over the given iterator, dividing runtime between it
and all other iterators which have been passed to this function and not yet
exhausted.
"""
return _theCooperator.coiterate(iterator)
class Clock:
"""
Provide a deterministic, easily-controlled implementation of
L{IReactorTime.callLater}. This is commonly useful for writing
deterministic unit tests for code which schedules events using this API.
"""
rightNow = 0.0
def __init__(self):
self.calls = []
def seconds(self):
"""
Pretend to be time.time(). This is used internally when an operation
such as L{IDelayedCall.reset} needs to determine a a time value
relative to the current time.
@rtype: C{float}
@return: The time which should be considered the current time.
"""
return self.rightNow
def callLater(self, when, what, *a, **kw):
"""
See L{twisted.internet.interfaces.IReactorTime.callLater}.
"""
self.calls.append(
base.DelayedCall(self.seconds() + when,
what, a, kw,
self.calls.remove,
lambda c: None,
self.seconds))
self.calls.sort(lambda a, b: cmp(a.getTime(), b.getTime()))
return self.calls[-1]
def advance(self, amount):
"""
Move time on this clock forward by the given amount and run whatever
pending calls should be run.
@type amount: C{float}
@param amount: The number of seconds which to advance this clock's
time.
"""
self.rightNow += amount
while self.calls and self.calls[0].getTime() <= self.seconds():
call = self.calls.pop(0)
call.called = 1
call.func(*call.args, **call.kw)
def pump(self, timings):
"""
Advance incrementally by the given set of times.
@type timings: iterable of C{float}
"""
for amount in timings:
self.advance(amount)
__all__ = [
'LoopingCall',
'Clock',
'SchedulerStopped', 'Cooperator', 'coiterate',
]

View file

@ -1,41 +0,0 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Transaction management.
$Id$
"""
from zope.interface import implements
from cybertools.brain.interfaces import ISession
class Session(object):
implements(ISession)
def __init__(self):
self.states = {}
def setState(self, neuron, state):
self.states[neuron] = state
def getState(self, neuron):
return self.states.get(neuron, neuron.state)

View file

@ -1,55 +0,0 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base classes for state and state manipulations using a float-based state.
$Id$
"""
from zope.interface import implements
from cybertools.brain.interfaces import IState, ITransition
class State(object):
""" The state of a neuron.
"""
implements(IState)
def __init__(self, value=0.0):
self.value = value
def __repr__(self):
return '<State %0.1f>' % self.value
class Transition(object):
implements(ITransition)
def __init__(self, synapsis, factor=1.0):
self.synapsis = synapsis
self.factor = factor
def execute(self, session=None):
oldState = self.synapsis.receiver.getState(session)
senderState = self.synapsis.sender.getState(session)
return State(oldState.value + senderState.value * self.factor)

View file

@ -1,48 +0,0 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""Form Controller stuff: form processing is the part of the
model/view/controller pattern that deals withform input.
$Id$
"""
from zope.interface import Interface, implements
class IFormController(Interface):
""" Used as a named adapter by GenericView for processing form input.
"""
def update():
""" Processing form input...
"""
class FormController(object):
implements(IFormController)
def __init__(self, context, request):
self.view = self.__parent__ = view = context
self.context = view.context # the controller is adapted to a view
self.request = request
def update(self):
pass

View file

@ -1,38 +0,0 @@
#
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
""" URL manipulation utilities
$Id$
"""
from urlparse import urlparse
from zope.app.container.traversal import ItemTraverser
from zope.interface import Interface, implements
TraversalRedirector(ItemTraverser):
port = 9083
names = ('ctt', 'sona',)
loc_pattern = 'www.%s.de'
skip = (0, 4)
def publishTraverse(self, request, name):
return super(TraversalRedirector, self).publishTraverse(request, name)

View file

@ -1,43 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""Keyword catalog index.
$Id$
"""
import zope.index.keyword
import zope.interface
import zope.app.container.contained
import zope.app.catalog.attribute
import zope.app.catalog.interfaces
class IKeywordIndex(zope.app.catalog.interfaces.IAttributeIndex,
zope.app.catalog.interfaces.ICatalogIndex):
"""Interface-based catalog keyword index.
"""
class KeywordIndex(zope.app.catalog.attribute.AttributeIndex,
zope.index.keyword.KeywordIndex,
zope.app.container.contained.Contained):
zope.interface.implements(IKeywordIndex)

View file

@ -1,46 +0,0 @@
#
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Customer classes.
$Id$
"""
from zope.interface import implements, Interface
from cybertools.commerce.common import RelationSet, BaseObject
from cybertools.commerce.interfaces import ICustomer, IAddress
class Customer(BaseObject):
implements(ICustomer)
def __init__(self, customerId, title=None, client=None):
self.name = self.customerId = customerId
self.title = title or u'unknown'
self.client = client
self.shops = self.collection(self, 'customers')
self.orders = self.collection(self, 'customer')
class Address(BaseObject):
implements(IAddress)

View file

@ -1,48 +0,0 @@
#
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base classes.
$Id$
"""
from zope.interface import implements
from cybertools.commerce.common import RelationSet, BaseObject
from cybertools.commerce.interfaces import IShop
class Shop(BaseObject):
implements(IShop)
collection = RelationSet
def __init__(self, name, title=None):
self.name = name
self.title = title or u'Shop'
self.products = self.collection(self, 'shops')
self.customers = self.collection(self, 'shops')
self.orderNumber = 0
def getNewOrderId(self):
last = self.orderNumber or 0
num = last + 1
self.orderNumber = num
return '%05i' % num

View file

@ -1,62 +0,0 @@
#
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Basic classes for complex template structures.
$Id$
"""
from zope.interface import implements
from cybertools.composer.interfaces import IComponent, IElement, ICompound
from cybertools.composer.interfaces import ITemplate
from cybertools.util.jeep import Jeep
class Component(object):
implements(IComponent)
class Element(Component):
implements(IElement)
class Compound(Component):
implements(ICompound)
componentStorage = Jeep
def __init__(self):
self.parts = self.componentStorage()
class Template(object):
implements(ITemplate)
componentStorage = Jeep
components = None
def __init__(self):
if self.componentStorage is not None:
self.components = self.componentStorage()

View file

@ -1,55 +0,0 @@
#
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base classes to be used for client adapters.
$Id$
"""
from zope.interface import implements
from cybertools.composer.interfaces import IInstance
class Instance(object):
implements(IInstance)
templateFactory = dict
templateAttributeName = '__ctc_template__'
aspect = 'composer.default'
def __init__(self, context):
self.context = context
def setTemplate(self, temp):
template = getattr(self.context,
self.templateAttributeName,
self.templateFactory())
template.setdefault(self.aspect, temp)
setattr(self.context, self.templateAttributeName, template)
def getTemplate(self):
template = getattr(self.context, self.templateAttributeName, {})
return template.get(self.aspect, [])
template = property(getTemplate, setTemplate)
def applyTemplates(self, *args, **kw):
raise ValueError('To be implemented by subclass')

View file

@ -1,43 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Default layouts for the liquid skin.
$Id$
"""
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope import component
from zope.interface import implements
from cybertools.browser.liquid import Liquid
from cybertools.browser.renderer import RendererFactory
from cybertools.composer.layout.base import Layout
from cybertools.composer.layout.browser.standard import standardRenderers
defaultRenderers = RendererFactory(ViewPageTemplateFile('default.pt'))
Layout('css.liquid', 'page.css', renderer=standardRenderers['css'],
media='all', resource='liquid.css', skin=Liquid)
Layout('body.liquid', 'page.body', renderer=defaultRenderers.body,
skin=Liquid)

View file

@ -1,39 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Region implementation.
$Id$
"""
from zope.interface import implements
from cybertools.composer.layout.interfaces import IRegion
from cybertools.util.jeep import Jeep
class Region(object):
implements(IRegion)
allowedLayoutCategories = None
def __init__(self, name):
self.name = name
self.layouts = Jeep()

View file

@ -1,125 +0,0 @@
#
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Report result sets and related classes.
"""
from copy import copy
from zope.cachedescriptors.property import Lazy
from cybertools.composer.interfaces import IInstance
from cybertools.composer.report.base import BaseQueryCriteria
from cybertools.util.jeep import Jeep
class BaseRow(object):
def __init__(self, context, parent):
self.context = context
self.parent = parent
self.data = {}
self.sequenceNumber = 0
def getRawValue(self, attr):
return self.data.get(attr)
class Row(BaseRow):
attributeHandlers = {}
def getRawValue(self, attr):
return self.attributeHandlers.get(attr, self.getContextAttr)(self, attr)
@staticmethod
def getContextAttr(obj, attr):
return getattr(obj.context, attr)
def getGroupFields(self):
return [self.getRawValue(f.name) for f in
self.parent.context.fields if 'group' in f.executionSteps]
@Lazy
def displayedColumns(self):
return self.parent.displayedColumns
def useRowProperty(self, attr):
return getattr(self, attr)
class GroupHeaderRow(BaseRow):
def getRawValue(self, attr):
return self.data.get(attr, u'')
@Lazy
def displayedColumns(self):
fields = self.parent.context.getActiveOutputFields()
for col in self.headerColumns:
for idx, f in enumerate(fields):
if f.name == col.name:
fields[idx] = col
return fields
class ResultSet(object):
def __init__(self, context, data, rowFactory=Row,
sortCriteria=None, sortDescending=False,
queryCriteria=BaseQueryCriteria(),
limits=None):
self.context = context # the report or report instance
self.data = data
self.rowFactory = rowFactory
self.sortCriteria = sortCriteria
self.sortDescending = sortDescending
self.queryCriteria = queryCriteria
self.limits = limits
self.totals = BaseRow(None, self)
def getResult(self):
result = [self.rowFactory(item, self) for item in self.data]
result = [row for row in result if self.queryCriteria.check(row)]
if self.sortCriteria:
result.sort(key=lambda x:
[f.getSortValue(x) for f in self.sortCriteria],
reverse=self.sortDescending)
if self.limits:
start, stop = self.limits
result = result[start:stop]
for idx, row in enumerate(result):
row.sequenceNumber = idx + 1
return result
@Lazy
def result(self):
return self.getResult()
def __iter__(self):
return iter(self.result)
def first(self):
if len(self.result) > 0:
return self.result[0]
return self.rowFactory(None, self)
@Lazy
def displayedColumns(self):
return Jeep(self.context.getActiveOutputFields())

View file

@ -1,52 +0,0 @@
#
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Rule instance and related classes.
$Id$
"""
from zope import component
from zope.component import adapts
from zope.interface import Interface, implements
from cybertools.composer.instance import Instance
from cybertools.composer.rule.interfaces import IRuleInstance, IActionHandler
class RuleInstance(Instance):
implements(IRuleInstance)
adapts(Interface)
template = None
event = None
def applyTemplate(self, **kw):
for c in self.template.conditions:
cond = component.getAdapter(self, ICondition, name=c)
if not cond():
continue
data = dict(request=self.event.request)
for action in self.template.actions:
handler = component.getAdapter(self, IActionHandler,
name=action.handlerName)
data = handler(data, action.parameters)
return data

View file

@ -1,55 +0,0 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Ordered container implementation.
$Id$
"""
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.container.browser.contents import JustContents
from zope.app.i18n import ZopeMessageFactory as _
from zope.cachedescriptors.property import Lazy
from zope.interface import Interface
contents_template = ViewPageTemplateFile('contents.pt')
class ContainerView(JustContents):
def checkMoveAction(self):
pass
orderable = False
# informations for the ajax.inner.html view (template):
template = contents_template
#@Lazy
#def template(self):
# basicView = zapi.getMultiAdapter((self.context, self.request),
# Interface, name=u'contents.html')
# return basicView.index
@Lazy
def macro(self):
return self.template.macros['contents']

6
cybertools/__init__.py Normal file
View file

@ -0,0 +1,6 @@
# package cybertools
# module aliases
import sys
import doctest
sys.modules['zope.testing.doctestunit'] = doctest

View file

@ -1,28 +1,10 @@
#
# Copyright (c) 2008 Helmut Merz - helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Embed Dojo using the cybertools.composer.layout procedure.
# cybertools.ajax.dojo.layout
$Id$
""" Embed Dojo using the cybertools.composer.layout procedure.
"""
from cStringIO import StringIO
from zope.app.pagetemplate import ViewPageTemplateFile
from io import StringIO
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from cybertools.browser.renderer import RendererFactory

View file

@ -1,38 +1,18 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# cybertools.brain.neuron
"""
A simple basic implementation of Neuron and Synapsis.
$Id$
""" A simple basic implementation of Neuron and Synapsis.
"""
from zope.interface import implements
from zope.interface import implementer
from cybertools.brain.interfaces import INeuron, ISynapsis
from cybertools.brain.state import State, Transition
@implementer(ISynapsis)
class Synapsis(object):
""" A synapsis connects two neurons.
"""
implements(ISynapsis)
def __init__(self, sender, receiver):
self.sender = sender
sender.receivers.append(self)
@ -46,10 +26,9 @@ class Synapsis(object):
receiver.notify(session)
@implementer(INeuron)
class Neuron(object):
implements(INeuron)
def __init__(self):
self.senders = []
self.receivers = []

View file

@ -0,0 +1,21 @@
# cybertools.brain.session
""" Transaction management.
"""
from zope.interface import implementer
from cybertools.brain.interfaces import ISession
implementer(ISession)
class Session(object):
def __init__(self):
self.states = {}
def setState(self, neuron, state):
self.states[neuron] = state
def getState(self, neuron):
return self.states.get(neuron, neuron.state)

34
cybertools/brain/state.py Normal file
View file

@ -0,0 +1,34 @@
# cybertools.brain.state
""" Base classes for state and state manipulations using a float-based state.
"""
from zope.interface import implementer
from cybertools.brain.interfaces import IState, ITransition
@implementer(IState)
class State(object):
""" The state of a neuron.
"""
def __init__(self, value=0.0):
self.value = value
def __repr__(self):
return '<State %0.1f>' % self.value
@implementer(ITransition)
class Transition(object):
def __init__(self, synapsis, factor=1.0):
self.synapsis = synapsis
self.factor = factor
def execute(self, session=None):
oldState = self.synapsis.receiver.getState(session)
senderState = self.synapsis.sender.getState(session)
return State(oldState.value + senderState.value * self.factor)

View file

@ -1,7 +1,6 @@
# $Id$
# cybertools.brain.tests
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from zope.interface.verify import verifyClass
from cybertools.brain.interfaces import INeuron, ISynapsis
@ -20,9 +19,8 @@ class TestBrain(unittest.TestCase):
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
unittest.makeSuite(TestBrain),
DocFileSuite('README.txt',
optionflags=flags,),
unittest.TestLoader().loadTestsFromTestCase(TestBrain),
doctest.DocFileSuite('README.txt', optionflags=flags,),
))
if __name__ == '__main__':

View file

@ -3,7 +3,7 @@ Browser View Tools
==================
>>> from zope import component, interface
>>> from zope.interface import Interface, implements
>>> from zope.interface import Interface, implementer
>>> from zope.publisher.interfaces.browser import IBrowserRequest
@ -17,8 +17,10 @@ the common and node modules there.)
Let's start with a dummy content object and create a view on it:
>>> #@implementer(Interface)
>>> class SomeObject(object):
... implements(Interface)
... pass
>>> SomeObject = implementer(Interface)(SomeObject)
>>> obj = SomeObject()
>>> from cybertools.browser.view import GenericView
@ -122,7 +124,7 @@ ZPT macros:
>>> len(cssMacros)
4
>>> m1 = cssMacros[0]
>>> print m1.name, m1.media, m1.resourceName
>>> print(m1.name, m1.media, m1.resourceName)
css all zope3_tablelayout.css
Calling a macro provided by Controller.macros[] returns the real ZPT macro:
@ -138,7 +140,7 @@ The pre-set collection of macros for a certain slot may be extended
>>> len(controller.macros['css'])
5
>>> m5 = controller.macros['css'][4]
>>> print m5.name, m5.media, m5.resourceName
>>> print(m5.name, m5.media, m5.resourceName)
css all node.css
If an identifier is given (the second parameter) a certain macro is only
@ -221,7 +223,7 @@ controller issues a redirect.
>>> from cybertools.browser.form import IFormController, FormController
>>> class MyController(FormController):
... def update(self):
... print 'updating...'
... print('updating...')
... return True
>>> component.provideAdapter(MyController, (View, IBrowserRequest),

View file

@ -1,31 +1,12 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# cybertools.browser.action
"""
Base classes (sort of views) for action portlet items.
$Id$
""" Base classes (sort of views) for action portlet items.
"""
from copy import copy
from urllib import urlencode
from urllib.parse import urlencode
from zope import component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.browserpage import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
action_macros = ViewPageTemplateFile('action_macros.pt')

Some files were not shown because too many files have changed in this diff Show more