source: indico/indico/MaKaC/plugins/Collaboration/RecordingManager/common.py @ 6bf36b

burotelhello-world-walkthroughipv6new-webexv0.97-seriesv0.98-seriesv0.98.2v0.98.3v0.98b1v0.98b2v0.99v1.0v1.1
Last change on this file since 6bf36b was 6bf36b, checked in by Jeremy Herr <jeremy.herr@…>, 3 years ago

[IMP] Check for existing link to CDS

calls the getAllMaterialList() method for conference, session, contribution, subcontribution objects,
and checks for the existence of material titled "Video in CDS" accompanied by a Link. If so,
this info is displayed on the RecordingManager? interface.

  • Property mode set to 100644
File size: 20.5 KB
Line 
1# -*- coding: utf-8 -*-
2##
3## $Id: common.py,v 1.4 2009/04/25 13:56:17 dmartinc Exp $
4##
5## This file is par{t of CDS Indico.
6## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
7##
8## CDS Indico is free software; you can redistribute it and/or
9## modify it under the terms of the GNU General Public License as
10## published by the Free Software Foundation; either version 2 of the
11## License, or (at your option) any later version.
12##
13## CDS Indico is distributed in the hope that it will be useful, but
14## WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16## General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
20## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21
22from MaKaC.plugins.Collaboration.base import CollaborationServiceException,\
23    CSErrorBase
24from MaKaC.common.PickleJar import Retrieves
25from MaKaC.webinterface.common.contribFilters import PosterFilterField
26from MaKaC.conference import Contribution, ConferenceHolder
27import MySQLdb
28from MaKaC.plugins.Collaboration.collaborationTools import CollaborationTools
29from MaKaC.conference import Contribution
30from MaKaC.conference import Session
31
32from MaKaC.common.logger import Logger
33
34from time import mktime
35from MaKaC.common.xmlGen import XMLGen
36from MaKaC.export.oai2 import DataInt
37from MaKaC.common.output import outputGenerator
38from MaKaC.conference import Link
39
40from urllib2 import Request, urlopen
41import re
42
43def getTalks(conference, oneIsEnough = False, sort = False):
44    """ oneIsEnough: the algorithm will stop at the first contribution found
45                     it will then return a list with a single element
46        sort: if True, contributions are sorted by start date (non scheduled contributions at the end)
47    """
48
49    # max length for title string
50    title_length = 39
51
52    # recordable_events is my own list of tags for each recordable event
53    # which will be used by the tpl file.
54    recordable_events = []
55    talks = []
56
57    event_info = {}
58    event_info["speakers"]   = ""
59    event_info["type"]       = "conference"
60    event_info["IndicoID"]   = generateIndicoID(conference = conference.getId(),
61                                              session         = None,
62                                              contribution    = None,
63                                              subcontribution = None)
64    event_info["title"]      = conference.getTitle()
65    event_info["titleshort"] = truncate_str(event_info["title"], 40)
66    # this always comes first, so just pretend it's 0 seconds past the epoch
67    event_info["date"]       = 0
68    event_info["LOID"]       = ""
69    event_info["IndicoLink"] = doesExistIndicoLink(conference)
70
71#    event_info["CDS"]        = getCDSRecord(event_info["IndicoID"])
72
73    recordable_events.append(event_info)
74
75    # Posters are contributions that are not recordable,
76    # so filter them out here
77    filter = PosterFilterField(conference, False, False)
78    for contribution in conference.getContributionList():
79        if filter.satisfies(contribution):
80
81            talks.append(contribution)
82            speaker_str = ""
83            speaker_list = contribution.getSpeakerList()
84            if speaker_list is not None:
85                tag_first_iter = True
86                for speaker in speaker_list:
87                    if tag_first_iter:
88                        speaker_str = "%s %s" % (speaker_list[0].getFirstName(),
89                                                 speaker_list[0].getFamilyName())
90                    else:
91                        speaker_str = "%s, %s %s" % \
92                            (speaker_str, speaker.getFirstName(), speaker.getFamilyName())
93
94            event_info = {}
95            event_info["speakers"]   = speaker_str
96            event_info["type"]       = "contribution"
97            event_info["IndicoID"]   = generateIndicoID(conference = conference.getId(),
98                                              session         = None,
99                                              contribution    = contribution.getId(),
100                                              subcontribution = None)
101            event_info["title"]      = contribution.getTitle()
102            event_info["titleshort"] = truncate_str(event_info["title"], title_length)
103            # NOTE: Sometimes there is no start date?! e.g. 21917. I guess I should deal with this
104            try:
105                event_info["date"]       = int(mktime(contribution.getStartDate().timetuple()))
106            except AttributeError:
107                event_info["date"]       = 1
108
109            event_info["LOID"]       = ""
110            event_info["IndicoLink"] = doesExistIndicoLink(contribution)
111
112#            event_info["CDS"]        = getCDSRecord(event_info["IndicoID"])
113
114            recordable_events.append(event_info)
115            ctr_sc = 0
116            for subcontribution in contribution.getSubContributionList():
117                ctr_sc += 1
118                event_info = {}
119                speaker_str = ""
120                speaker_list = subcontribution.getSpeakerList()
121
122                if speaker_list is not None:
123                    tag_first_iter = True
124                    for speaker in speaker_list:
125                        if tag_first_iter:
126                            speaker_str = "%s %s" % (speaker_list[0].getFirstName(),
127                                                     speaker_list[0].getFamilyName())
128                        else:
129                            speaker_str = "%s, %s %s" % \
130                            (speaker_str, speaker.getFirstName(), speaker.getFamilyName())
131
132                event_info["speakers"]   = speaker_str
133                event_info["type"]       = "subcontribution"
134                event_info["IndicoID"]   = generateIndicoID(conference = conference.getId(),
135                                              session         = None,
136                                              contribution    = contribution.getId(),
137                                              subcontribution = subcontribution.getId())
138                event_info["title"]      = subcontribution.getTitle()
139                event_info["titleshort"] = truncate_str(event_info["title"], title_length)
140                # Subcontribution objects don't have start dates,
141                # so get the owner contribution's start date
142                # and add the counter ctr_sc to that
143                event_info["date"]     = int(mktime(subcontribution.getOwner().getStartDate().timetuple()) + ctr_sc)
144                event_info["LOID"]       = ""
145                event_info["IndicoLink"] = doesExistIndicoLink(subcontribution)
146
147#                event_info["CDS"]        = getCDSRecord(event_info["IndicoID"])
148
149                recordable_events.append(event_info)
150
151            if oneIsEnough:
152                break
153    if sort and not oneIsEnough:
154        talks.sort(key = Contribution.contributionStartDateForSort)
155
156    for session in conference.getSessionList():
157        event_info = {}
158        event_info["speakers"] = ""
159        event_info["type"]     = "session"
160        event_info["IndicoID"] = generateIndicoID(conference = conference.getId(),
161                                                  session         = session.getId(),
162                                                  contribution    = None,
163                                                  subcontribution = None)
164        event_info["title"]    = session.getTitle()
165        event_info["titleshort"] = truncate_str(event_info["title"], title_length)
166        # Get start time as seconds since the epoch so we can sort
167        event_info["date"]     = int(mktime(session.getStartDate().timetuple()))
168        event_info["LOID"]       = ""
169        event_info["IndicoLink"] = doesExistIndicoLink(session)
170#        event_info["CDS"]        = getCDSRecord(event_info["IndicoID"])
171
172        recordable_events.append(event_info)
173
174    # Get list of matching IndicoIDs and CDS records from CDS
175    cds_indico_matches = getCDSRecords(conference.getId())
176
177    for event_info in recordable_events:
178        try:
179            event_info["CDS"] = cds_indico_matches[event_info["IndicoID"]]
180        except KeyError:
181            event_info["CDS"] = ""
182            pass
183
184
185    # Get list of matching IndicoID's and LOIDs from the Micala database
186    existing_matches = getMatches(conference.getId())
187
188    # insert any existing matches into the recordable_events array
189    for talk in recordable_events:
190        try:
191            matching_LOID = existing_matches[talk["IndicoID"]]
192        except KeyError:
193            matching_LOID = ""
194
195        if matching_LOID != "":
196            talk["LOID"] = matching_LOID
197
198    # Next, sort the list of events by startDate for display purposes
199    recordable_events.sort(startTimeCompare)
200
201    return recordable_events
202
203def startTimeCompare(a, b):
204    '''This subroutine is for sorting the events, sessions,
205    contributions and subcontributions correctly.
206    Note: if a session and contribution have the exact same start time,
207    then it must be the first contribution in the session, and we
208    want to display the session first, so return the appropriate value to do that.'''
209
210    if a["date"] > b["date"]:
211        return 1
212    elif a["date"] == b["date"]:
213        if a["type"] == "contribution" and b["type"] == "session":
214            return 1
215        elif a["type"] == "session" and b["type"] == "contribution":
216            return -1
217        else:
218            return 0
219    else:  #a < b
220        return -1
221
222def truncate_str(str, length):
223    '''Truncates given string to the desired length and if it
224    was longer than that, sticks ellipses on the end'''
225
226    if len(str) < length:
227        return str
228    else:
229        return str[:length] + "..."
230
231def generateIndicoID(conference     = None,
232                    session         = None,
233                    contribution    = None,
234                    subcontribution = None):
235    """Given the conference, session, contribution and subcontribution IDs,
236    return an "Indico ID" of the form:
237    12345
238    12345s1
239    12345c0
240    12345c0sc3
241    """
242    IndicoID = ""
243
244    if session is not None:
245        IndicoID = "%ds%d" % (int(conference), int(session))
246    elif contribution is None:
247        IndicoID = "%d" % (int(conference),)
248    elif subcontribution is not None:
249        IndicoID = "%dc%dsc%d" % (int(conference), int(contribution), int(subcontribution))
250    else:
251        IndicoID = "%dc%d" % (int(conference), int(contribution))
252
253    return IndicoID
254
255def parseIndicoID(IndicoID):
256    """Given an "Indico ID" of the form shown above, determine whether it is
257    a conference, subcontribution etc, and return that info with the individual IDs."""
258
259    pConference      = re.compile('^(\d+)$')
260    pSession         = re.compile('^(\d+)s(\d+)$')
261    pContribution    = re.compile('^(\d+)c(\d+)$')
262    pSubcontribution = re.compile('^(\d+)s(\d+)sc(\d+)$')
263
264    mE  = pConference.search(IndicoID)
265    mS  = pSession.search(IndicoID)
266    mC  = pContribution.search(IndicoID)
267    mSC = pSubcontribution.search(IndicoID)
268
269    if mE:
270        return {'type':           'conference',
271                'conference':     mE.group(1),
272                'session':        '',
273                'contribution':   '',
274                'subcontribution':''}
275    elif mS:
276        return {'type':           'session',
277                'conference':     mS.group(1),
278                'session':        mS.group(2),
279                'contribution':   '',
280                'subcontribution':''}
281    elif mC:
282        return {'type':           'contribution',
283                'conference':     mC.group(1),
284                'session':        '',
285                'contribution':   mC.group(2),
286                'subcontribution':''}
287    elif mSC:
288        return {'type':           'subcontribution',
289                'conference':     mSC.group(1),
290                'session':        '',
291                'contribution':   mSC.group(2),
292                'subcontribution':mSC.group(3)}
293    else:
294        return None
295
296def getMatches(confID):
297    """For the current conference, get list from the database of IndicoID's already matched to Lecture Object."""
298
299    try:
300        connection = MySQLdb.connect(host   = CollaborationTools.getOptionValue("RecordingManager", "micalaDBServer"),
301                                     port   = int(CollaborationTools.getOptionValue("RecordingManager", "micalaDBPort")),
302                                     user   = CollaborationTools.getOptionValue("RecordingManager", "micalaDBReaderUser"),
303                                     passwd = CollaborationTools.getOptionValue("RecordingManager", "micalaDBReaderPW"),
304                                     db     = CollaborationTools.getOptionValue("RecordingManager", "micalaDBName"))
305    except MySQLdb.Error, e:
306        raise RecordingManagerException("MySQL database error %d: %s" % (e.args[0], e.args[1]))
307
308    cursor = connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
309    cursor.execute('''SELECT IndicoID, LOID FROM Lectures WHERE IndicoID LIKE "%s%%"''' % confID)
310    connection.commit()
311    rows = cursor.fetchall()
312    cursor.close()
313    connection.close()
314
315    match_array = {}
316    for row in rows:
317        match_array[row["IndicoID"]] = row["LOID"]
318
319    return (match_array)
320
321def getOrphans():
322    """Get list of Lecture Objects in the database that have no IndicoID assigned"""
323
324    try:
325        connection = MySQLdb.connect(host   = CollaborationTools.getOptionValue("RecordingManager", "micalaDBServer"),
326                                     port   = int(CollaborationTools.getOptionValue("RecordingManager", "micalaDBPort")),
327                                     user   = CollaborationTools.getOptionValue("RecordingManager", "micalaDBReaderUser"),
328                                     passwd = CollaborationTools.getOptionValue("RecordingManager", "micalaDBReaderPW"),
329                                     db     = CollaborationTools.getOptionValue("RecordingManager", "micalaDBName"))
330    except MySQLdb.Error, e:
331        raise RecordingManagerException("MySQL database error %d: %s" % (e.args[0], e.args[1]))
332
333    cursor = connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
334    cursor.execute("SELECT id, LOID, IndicoID, Title, Creator FROM Lectures WHERE NOT IndicoID")
335    connection.commit()
336    rows = cursor.fetchall()
337    cursor.close()
338    connection.close()
339
340    for lecture in rows:
341        lecture["time"] = formatLOID(lecture["LOID"])[0]
342        lecture["date"] = formatLOID(lecture["LOID"])[1]
343        lecture["box"]  = formatLOID(lecture["LOID"])[2]
344
345    return (rows)
346
347def updateMicala(IndicoID, LOID):
348    """Submit Indico ID to the micala DB"""
349
350#    Logger.get('RecMan').exception("inside updateMicala.")
351
352    try:
353        connection = MySQLdb.connect(host   = CollaborationTools.getOptionValue("RecordingManager", "micalaDBServer"),
354                                     port   = int(CollaborationTools.getOptionValue("RecordingManager", "micalaDBPort")),
355                                     user   = CollaborationTools.getOptionValue("RecordingManager", "micalaDBUser"),
356                                     passwd = CollaborationTools.getOptionValue("RecordingManager", "micalaDBPW"),
357                                     db     = CollaborationTools.getOptionValue("RecordingManager", "micalaDBName"))
358    except MySQLdb.Error, e:
359        raise RecordingManagerException("MySQL database error %d: %s" % (e.args[0], e.args[1]))
360
361    cursor = connection.cursor(cursorclass=MySQLdb.cursors.DictCursor)
362
363    try:
364        cursor.execute("UPDATE Lectures SET IndicoID=%s WHERE id=%s",
365                       (IndicoID, LOID))
366        connection.commit()
367    except MySQLdb.Error, e:
368        raise RecordingManagerException("MySQL database error %d: %s" % (e.args[0], e.args[1]))
369
370    cursor.close()
371    connection.close()
372
373def createCDSRecord(confId, aw):
374    '''Retrieve a MARC XML string for the given conference, then package it up and send it to CDS.'''
375
376    # I don't understand what some of the following lines do. Pedro did some of it for me.
377    xmlGen = XMLGen()
378    di = DataInt(xmlGen)
379    og = outputGenerator(aw, dataInt=di)
380
381    xmlGen.openTag("iconf")
382    conf = ConferenceHolder().getById(confId)
383    # setting source='RecordingManager' is how we identify ourselves to the outputGenerator
384    # Nobody else should need to know this access information, and it shouldn't be accessible
385    # to e.g. OAI harvesters outside CERN.
386    og.confToXMLMarc21(conf, 1, 1, 1, forceCache=True, out=xmlGen, source='RecordingManager')
387    xmlGen.closeTag("iconf")
388
389    # Get the MARC XML
390    marc = xmlGen.getXml()
391
392    # temporary, for my own debugging
393    f = open('/tmp/marc.xml', 'w')
394    f.write(marc)
395    f.close()
396
397    # Next step: submit this marc xml to CDS
398
399    return True
400
401def getCDSRecords(confId):
402    '''Query CDS to see if it has an entry for the given event as well as all its sessions, contributions and subcontributions.
403    If yes return a dictionary pairing CDS IDs with Indico IDs.'''
404
405    # Slap a wildcard on the end of the ID to find the conference itself as well as all children.
406    id_plus_wildcard = confId + "*"
407
408    # Here is a help page describing the GET args,
409    # if this CDS query needs to be changed (thanks jerome.caffaro@cern.ch):
410    # http://invenio-demo.cern.ch/help/hacking/search-engine-api
411    url = "http://cdsweb.cern.ch/search?p=sysno%%3A%%22INDICO.%s%%22&f=&action_search=Search&sf=&so=d&rm=&rg=1000&sc=1&ot=970&of=t&ap=0" \
412        % (id_plus_wildcard)
413
414    # This dictionary will contain CDS ID's, referenced by Indico ID's
415    results = {}
416
417    # perform the URL query to CDS. It contains one line for each CDS record,
418    # of the form:
419    # 001121974 970__ $$aINDICO.21917c22
420    # The first number is the CDS record ID, with leading zeros
421    # The second number is the MARC XML tag
422    # The third string contains the Indico ID
423    request = Request(url)
424    f = urlopen(request)
425    lines = f.readlines()
426    f.close()
427
428    for line in lines:
429        print line,
430        result = line.strip()
431        if result != "":
432
433            bigcdsid = result.split(" ")[0]
434            CDSID = bigcdsid.lstrip("0")
435
436            p = re.compile('INDICO\.([sc\d]+)')
437            m = p.search(line)
438
439            if m:
440                IndicoID = m.group(1)
441                results[IndicoID] = CDSID
442            else:
443                # If we end up here then probably something's wrong with the URL
444                pass
445
446#            results.append({"CDSID": CDSID, "IndicoID": IndicoID})
447
448    return results
449
450def createIndicoLink(IndicoID):
451    """Create a link in Indico to the CDS record."""
452
453    eventInfo = parseIndicoID(IndicoID)
454
455
456def doesExistIndicoLink(obj):
457    """This function will be called with a conference, session, contribution or subcontribution object.
458    Each of those has a getAllMaterialList() method. Call that method and search for a title "Video in CDS"
459    and make sure it has a link."""
460
461    flagLinkFound = False
462
463    if obj == 'conference':
464        materials = obj.getAllMaterialList()
465        if materials is not None and len(materials) > 0:
466            for mat in materials:
467                if mat.getTitle() == "Video in CDS" and isinstance(mat.getMainResource, Link):
468                    flagLinkFound = True
469
470    return flagLinkFound
471
472def formatLOID(LOID):
473    """Given a LOID of the form YYYYMMDD-recordingDev-HHMMSS, split it up into nicely readable parts: time, date, recording_device."""
474
475    rawdate = LOID.split('-')[0]
476    rawtime = LOID.split('-')[2]
477
478    date = rawdate[6:8] + '.' + rawdate[4:6] + '.' + rawdate[0:4]
479    time = rawtime[0:2] + ':' + rawtime[2:4] + ':' + rawtime[4:8]
480
481    recording_device = LOID.split('-')[1]
482
483    return(time, date, recording_device)
484
485class RecordingManagerError(CSErrorBase):
486    def __init__(self, operation, inner):
487        CSErrorBase.__init__(self)
488        self._operation = operation
489        self._inner = inner
490
491    @Retrieves(['MaKaC.plugins.Collaboration.RecordingManager.common.RecordingManagerError'], 'origin')
492    def getOrigin(self):
493        return 'RecordingManager'
494
495    @Retrieves(['MaKaC.plugins.Collaboration.RecordingManager.common.RecordingManagerError'],
496               'operation')
497    def getOperation(self):
498        return self._operation
499
500    @Retrieves(['MaKaC.plugins.Collaboration.RecordingManager.common.RecordingManagerError'],
501               'inner')
502    def getInner(self):
503        return str(self._inner)
504
505    def getUserMessage(self):
506        return ''
507
508    def getLogMessage(self):
509        return "Recording Request error for operation: " + str(self._operation) + ", inner exception: " + str(self._inner)
510
511
512class RecordingManagerException(CollaborationServiceException):
513    pass
Note: See TracBrowser for help on using the repository browser.