Skip to content

Commit

Permalink
Move new trunk into place.
Browse files Browse the repository at this point in the history
svn path=/feedfeeder/trunk/; revision=65293
  • Loading branch information
mauritsvanrees committed May 20, 2008
0 parents commit 3714d0f
Show file tree
Hide file tree
Showing 77 changed files with 6,525 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Products/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
23 changes: 23 additions & 0 deletions Products/feedfeeder/Extensions/AppInstall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from StringIO import StringIO
from Products.CMFCore.utils import getToolByName


def install(site):
out = StringIO()
applyGenericSetupProfile(site, out)


def applyGenericSetupProfile(site, out):
"""Just apply our own extension profile.
"""

setup_tool = getToolByName(site, 'portal_setup')
setup_tool.setImportContext('profile-feedfeeder:default')
print >> out, "Applying the generic setup profile for feedfeeder..."
setup_tool.runAllImportSteps(purge_old=False)
try:
setup_tool.setImportContext('profile-CMFPlone:plone')
except KeyError:
# Plone 3.0 has a different profile name
setup_tool.setImportContext('profile-Products.CMFPlone:plone')
print >> out, "Applied the generic setup profile for feedfeeder"
214 changes: 214 additions & 0 deletions Products/feedfeeder/Extensions/Install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-

import os.path
import sys
from StringIO import StringIO
from sets import Set
from App.Common import package_home
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import manage_addTool
from Products.ExternalMethod.ExternalMethod import ExternalMethod
from zExceptions import NotFound, BadRequest

from Products.Archetypes.Extensions.utils import installTypes
from Products.Archetypes.Extensions.utils import install_subskin
from Products.Archetypes.config import TOOL_NAME as ARCHETYPETOOLNAME
from Products.Archetypes.atapi import listTypes
from Products.feedfeeder.config import PROJECTNAME
from Products.feedfeeder.config import product_globals as GLOBALS

def install(self, reinstall=False):
""" External Method to install feedfeeder """
out = StringIO()
print >> out, "Installation log of %s:" % PROJECTNAME

# If the config contains a list of dependencies, try to install
# them. Add a list called DEPENDENCIES to your custom
# AppConfig.py (imported by config.py) to use it.
try:
from Products.feedfeeder.config import DEPENDENCIES
except:
DEPENDENCIES = []
portal = getToolByName(self,'portal_url').getPortalObject()
quickinstaller = portal.portal_quickinstaller
for dependency in DEPENDENCIES:
print >> out, "Installing dependency %s:" % dependency
quickinstaller.installProduct(dependency)
import transaction
transaction.savepoint(optimistic=True)

classes = listTypes(PROJECTNAME)
installTypes(self, out,
classes,
PROJECTNAME)
install_subskin(self, out, GLOBALS)


# try to call a workflow install method
# in 'InstallWorkflows.py' method 'installWorkflows'
try:
installWorkflows = ExternalMethod('temp', 'temp',
PROJECTNAME+'.InstallWorkflows',
'installWorkflows').__of__(self)
except NotFound:
installWorkflows = None

if installWorkflows:
print >>out,'Workflow Install:'
res = installWorkflows(self,out)
print >>out,res or 'no output'
else:
print >>out,'no workflow install'


# enable portal_factory for given types
factory_tool = getToolByName(self,'portal_factory')
factory_types=[
"FeedConsumer",
"StandardContentHandler",
"FeedfeederFolder",
"FeedFeederItem",
] + factory_tool.getFactoryTypes().keys()
factory_tool.manage_setPortalFactoryTypes(listOfTypeIds=factory_types)

from Products.feedfeeder.config import STYLESHEETS
try:
portal_css = getToolByName(portal, 'portal_css')
for stylesheet in STYLESHEETS:
try:
portal_css.unregisterResource(stylesheet['id'])
except:
pass
defaults = {'id': '',
'media': 'all',
'enabled': True}
defaults.update(stylesheet)
portal_css.manage_addStylesheet(**defaults)
except:
# No portal_css registry
pass
from Products.feedfeeder.config import JAVASCRIPTS
try:
portal_javascripts = getToolByName(portal, 'portal_javascripts')
for javascript in JAVASCRIPTS:
try:
portal_javascripts.unregisterResource(javascript['id'])
except:
pass
defaults = {'id': ''}
defaults.update(javascript)
portal_javascripts.registerScript(**defaults)
except:
# No portal_javascripts registry
pass

# try to call a custom install method
# in 'AppInstall.py' method 'install'
try:
install = ExternalMethod('temp', 'temp',
PROJECTNAME+'.AppInstall', 'install')
except NotFound:
install = None

if install:
print >>out,'Custom Install:'
try:
res = install(self, reinstall)
except TypeError:
res = install(self)
if res:
print >>out,res
else:
print >>out,'no output'
else:
print >>out,'no custom install'
return out.getvalue()

def uninstall(self, reinstall=False):
out = StringIO()

# try to call a workflow uninstall method
# in 'InstallWorkflows.py' method 'uninstallWorkflows'
try:
uninstallWorkflows = ExternalMethod('temp', 'temp',
PROJECTNAME+'.InstallWorkflows',
'uninstallWorkflows').__of__(self)
except NotFound:
uninstallWorkflows = None

if uninstallWorkflows:
print >>out, 'Workflow Uninstall:'
res = uninstallWorkflows(self, out)
print >>out, res or 'no output'
else:
print >>out,'no workflow uninstall'

# try to call a custom uninstall method
# in 'AppInstall.py' method 'uninstall'
try:
uninstall = ExternalMethod('temp', 'temp',
PROJECTNAME+'.AppInstall', 'uninstall')
except:
uninstall = None

if uninstall:
print >>out,'Custom Uninstall:'
try:
res = uninstall(self, reinstall)
except TypeError:
res = uninstall(self)
if res:
print >>out,res
else:
print >>out,'no output'
else:
print >>out,'no custom uninstall'

return out.getvalue()

def beforeUninstall(self, reinstall, product, cascade):
""" try to call a custom beforeUninstall method in 'AppInstall.py'
method 'beforeUninstall'
"""
out = StringIO()
try:
beforeuninstall = ExternalMethod('temp', 'temp',
PROJECTNAME+'.AppInstall', 'beforeUninstall')
except:
beforeuninstall = []

if beforeuninstall:
print >>out, 'Custom beforeUninstall:'
res = beforeuninstall(self, reinstall=reinstall
, product=product
, cascade=cascade)
if res:
print >>out, res
else:
print >>out, 'no output'
else:
print >>out, 'no custom beforeUninstall'
return (out,cascade)

def afterInstall(self, reinstall, product):
""" try to call a custom afterInstall method in 'AppInstall.py' method
'afterInstall'
"""
out = StringIO()
try:
afterinstall = ExternalMethod('temp', 'temp',
PROJECTNAME+'.AppInstall', 'afterInstall')
except:
afterinstall = None

if afterinstall:
print >>out, 'Custom afterInstall:'
res = afterinstall(self, product=None
, reinstall=None)
if res:
print >>out, res
else:
print >>out, 'no output'
else:
print >>out, 'no custom afterInstall'
return out
1 change: 1 addition & 0 deletions Products/feedfeeder/Extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# make me a python module
26 changes: 26 additions & 0 deletions Products/feedfeeder/HISTORY.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
1.0 beta 4 (unreleased)
-----------------------

- Eggification: you can now install it as the Products.feedfeeder
egg. [maurits]


1.0 beta 3 (13 May 2008)
------------------------

- In the tests, use plone_workflow explicitly, so it is easier to test
on both Plone 2.5 and 3.0. [maurits]

- Make update_feed_items available in the object_buttons for Plone 3,
using new small @@is_feedcontainer as condition. [maurits]

- Avoid deprecation warnings for events and interfaces. [maurits]

- Remove semicolon in page template that broke in Plone 3. [maurits]

- Fix imports so they work in Plone 3 as well, without deprecation
warnings. [derstappenit]


1.0 beta 2 (2 January 2008)
---------------------------
91 changes: 91 additions & 0 deletions Products/feedfeeder/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
Feedfeeder
==========

Feedfeeder has just a few things it needs to do:

- Read in a few ATOM feeds (not too many).

- Create FeedfeederItems out of the entries pulled from the ATOM feeds.
Any feed items that contain enclosures will have the enclosures
pulled down and added as File items to the feed item.

- This means figuring out which items are new, which also means having
a good ID generating mechanism.


Wait, no existing product?
--------------------------

There's a whole slew of RSS/ATOM reading products for zope and
plone. None of them seemed to be a good fit. There was only one
product that actually stored the entries in the zope database, but
that was aimed at a lot of users individually adding a lot of feeds,
so it needed either a separate ZEO process (old version) or a
standalone mysql database (new version).

All the other products didn't store the entries in the database, were
old/unmaintained/etc.

In a sense, we're using an existing product as we use Mark Pilgrim's
excellent feedparser (http://feedparser.org) that'll do the actual
ATOM reading for us.


Product name
------------

The product feeds the content of ATOM feeds to plone as document/file
content types. So "feedfeeder" sort of suggested itself as a funny
name. Fun is important :-)


Product structure
-----------------

I'm using archgenxml to generate the boiler plate stuff. There's a
'generate.sh' shell script that'll call archgenxml for you. Nothing
fancy.

The feedfeeder's content types are:
- folder.FeedfeederFolder
- item.FeedfeederItem


How it works
------------

A feedfeeder is a folder which contains all the previously-added feed
entries as documents or files. It has a 'feeds' attribute that
contains a list of feeds to read.

Feedparser is called periodically (through a cron job?) to parse the
feeds. The UID of the items in the feed are converted to a suitable
filename (md5 hex hash of the atom id of the entry), that way you can
detect whether there are new items.

New items are turned into feed items.

Scheduled updates for feed folders

Zope can be configured to periodically trigger a url call.
In zope.conf you can use the <clock-server> directive to define a schedule and url
with the following data.
<clock-server>
method /path_to_feedfolder/update_feed_items
period 3600 # seconds
user admin
password 123
host localhost:8080
</clock-server>


Tests
-----

The look-here-first test is the doctest at 'doc/testDocIntegrationTests.txt'.

Testing is best done with zope's zopectl. 'instancemanager
<projectname> --test feedfeeder' will do that for you if you've set
up instancemanager. Otherwise 'bin/zopectl test -s
Products.feedfeeder'.

Loading

0 comments on commit 3714d0f

Please sign in to comment.