home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / launchpadbugs / attachmentsbase.py < prev    next >
Encoding:
Python Source  |  2009-01-08  |  12.5 KB  |  374 lines

  1. import os
  2.  
  3. import exceptions
  4. import utils
  5.  
  6. from lphelper import change_obj, LateBindingProperty
  7. from lpconstants import ATTACHMENTS
  8.  
  9.  
  10. class LPAttachment(object):
  11.     """ Returns an 'Attachment'-object
  12.     
  13.     * editable attributes:
  14.         .description: any text
  15.         .contenttype: any text
  16.         .is_patch: True/False
  17.         
  18.     * read-only attributes:
  19.         .id: hash(local_filename) for local files,
  20.             launchpadlibrarian-id for files uploaded to launchpadlibrarian.net
  21.         .is_down: True if a file is downloaded to ATTACHMENTS.ATTACHMENT_PATH
  22.         .is_up: True if file is uploaded to launchpadlibrarian.net
  23.         ...
  24.     TODO: work on docstring
  25.     """
  26.     def __init__(self, connection, url=None, localfilename=None, localfileobject=None,
  27.                     description=None, is_patch=None, contenttype=None, comment=None):
  28.         if [localfilename, localfileobject].count(None) == 0:
  29.             raise exceptions.PythonLaunchpadBugsValueError(msg="Attachment can not take localfilename and localfileobject")
  30.         self.__connection = connection
  31.         self.__description = description
  32.         self.__is_patch = is_patch
  33.         if self.__is_patch:
  34.             self.__contenttype = "text/plain"
  35.         else:
  36.             self.__contenttype = contenttype
  37.             
  38.         self.__local_filename = localfilename
  39.         self.__open_in_object = False
  40.         if localfileobject:
  41.             if isinstance(localfileobject, file):
  42.                 self.__local_fileobject = localfileobject
  43.             else:
  44.                 raise TypeError, "Attachment: localfileobject needs to be a file-like object"
  45.         else:
  46.             if self.__local_filename:
  47.                 self.__local_fileobject = open(self.__local_filename, "r")
  48.                 self.__open_in_object = True
  49.             else:
  50.                 self.__local_fileobject = None
  51.                 
  52.         self.__url = url
  53.         self.__text = None
  54.         self.__download_location = None
  55.         try:
  56.             self.__comment = int(comment)
  57.         except:
  58.             self.__comment = None
  59.         self.__cache = {"description": self.description,
  60.             "is_patch": self.is_patch, "contenttype": self.contenttype}
  61.  
  62.     def __repr__(self):
  63.         s = list()
  64.         if self.is_up:
  65.             s.append("(up: #%s)" %self.lp_id)
  66.         if self.is_down:
  67.             s.append("(down: %s)" %self.local_filename)
  68.         return "<Attachment %s>" %", ".join(s)
  69.         
  70.     def __del__(self):
  71.         if self.__open_in_object and self.__local_fileobject:
  72.             self.__local_fileobject.close()
  73.            
  74.     @property
  75.     def id(self):
  76.         if self.is_up:
  77.             return self.lp_id
  78.         else:
  79.             if self.__local_fileobject:
  80.                 return hash(self.__local_fileobject)
  81.             elif self.local_filename:
  82.                 return hash(self.local_filename)
  83.             raise ValueError, "unable to get hash of the given file"
  84.         
  85.     @property
  86.     def is_down(self):
  87.         """checks if
  88.             * file is already in attachment cache => change .__local_filename to cache-location => returns True
  89.             * file is in old cache location => move it to new location => change .__local_filename to cache-location => returns true
  90.             * file is in any other location => return True
  91.             * else return False
  92.         """
  93.         if self.__download_location:
  94.             return True
  95.         if self.is_up:
  96.             filename_old = \
  97.                 os.path.expanduser(os.path.join(ATTACHMENTS.ATTACHMENT_PATH,
  98.                     str(self.lp_id), self.lp_filename))
  99.             cache_filename = \
  100.                 os.path.expanduser(os.path.join(ATTACHMENTS.ATTACHMENT_PATH,
  101.                     self.sourcepackage, str(self.bugnumber),
  102.                     str(self.lp_id), self.lp_filename))
  103.             if os.path.exists(filename_old):
  104.                 os.renames(filename_old, cache_filename)
  105.             if os.path.exists(cache_filename):
  106.                 self.__local_filename = cache_filename
  107.                 return True
  108.         return bool(self.local_filename)
  109.         
  110.     @property
  111.     def is_up(self):
  112.         return bool(self.url)
  113.         
  114.     @property
  115.     def url(self):
  116.         return self.__url or ""
  117.     
  118.     @property
  119.     def lp_id(self):
  120.         if self.is_up:
  121.             return self.url.split("/")[-2]
  122.         
  123.     @property
  124.     def lp_filename(self):
  125.         if self.is_up:
  126.             return self.url.split("/")[-1]
  127.         else:
  128.             return ""
  129.             
  130.     def get_bugnumber(self):
  131.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  132.     bugnumber = LateBindingProperty(get_bugnumber)
  133.                     
  134.     def get_sourcepackage(self):
  135.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  136.     sourcepackage = LateBindingProperty(get_sourcepackage)
  137.                     
  138.             
  139.     def get_edit_url(self):
  140.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  141.     edit_url = LateBindingProperty(get_edit_url)
  142.             
  143.     @property
  144.     def local_filename(self):
  145.         return self.__local_filename
  146.         
  147.     @property
  148.     def local_fileobject(self):
  149.         return self.__local_fileobject
  150.             
  151.     def get_contenttype(self):
  152.         return self.__contenttype
  153.         
  154.     def set_contenttype(self, type):
  155.         if not self.is_patch:
  156.             self.__contenttype = type
  157.     contenttype = property(get_contenttype, set_contenttype, doc="contenttype of an attachment")
  158.     
  159.     def get_description(self):
  160.         return self.__description
  161.         
  162.     def set_description(self, text):
  163.         self.__description = str(text)
  164.     description = property(get_description, set_description, doc="description of an attachment")
  165.     
  166.     def get_ispatch(self):
  167.         return self.__is_patch
  168.         
  169.     def set_ispatch(self, value):
  170.         self.__is_patch = bool(value)
  171.     is_patch = property(get_ispatch, set_ispatch, "type of an attachment")
  172.         
  173.     def get_changed(self):
  174.         changed = set()
  175.         for k in self.__cache:
  176.             if self.__cache[k] != getattr(self, k):
  177.                 changed.add(k)
  178.         return frozenset(changed)
  179.     changed = property(get_changed, doc="get a list of changed attributes")
  180.     
  181.     
  182.     @property
  183.     def text(self):
  184.         """get content of an attachment"""
  185.         if not self.is_down:
  186.             self.download()
  187.         else:
  188.             self.read_local()
  189.         return self.__text
  190.        
  191.     def read_local(self):
  192.         if self.is_down and self.__text == None:
  193.             if not self.local_fileobject:
  194.                 self.__local_fileobject = open(self.local_filename, "r")
  195.             self.__text = self.local_fileobject.read()
  196.        
  197.     def download(self, location=None):
  198.         """ save attachment in ATTACHMENTS.ATTACHMENT_PATH if not already done """            
  199.         if location is None:
  200.             self.__local_filename = \
  201.                 os.path.expanduser(os.path.join(ATTACHMENTS.ATTACHMENT_PATH,
  202.                     self.sourcepackage, str(self.bugnumber), str(self.lp_id), self.lp_filename))
  203.         else:
  204.             path = os.path.expanduser(location)
  205.             self.__download_location = path
  206.             self.__local_filename = path
  207.                 
  208.         if self.is_up:
  209.             try:
  210.                 attachment = self.__connection.get(self.url)
  211.             except IOError, e:
  212.                 bugnumber = getattr(self, "bugnumber", "unknown")
  213.                 msg = "Error while downloading attachments #%s (bug=#%s)\n" %(self.lp_id, bugnumber)
  214.                 msg += "original error message: %s" %e
  215.                 raise exceptions.PythonLaunchpadBugsIOError(msg)
  216.             self.__text = attachment.text
  217.             self.contenttype = attachment.contenttype
  218.             utils.lazy_makedir(os.path.dirname(self.local_filename))
  219.             attachment_file = open(self.local_filename, "w")
  220.             attachment_file.write(self.__text)
  221.             attachment_file.close()
  222.             
  223.     @property
  224.     def comment(self):
  225.         return self.__comment
  226.             
  227.             
  228. class LPAttachments(object):
  229.     def __init__(self, comments=None, attachments=None, parsed=False):
  230.         if comments is None:
  231.             self.parsed = True
  232.         else:
  233.             self.parsed = parsed
  234.             self.__comments = comments
  235.         self.__added = set()
  236.         self.__removed = set()
  237.         self._current = attachments or list()
  238.         self.__cache = self.__current[:]
  239.         
  240.     def _get_attachments(self):
  241.         r = list()
  242.         c = self.__comments
  243.         x = [i.component for i in c.changed]
  244.         for i in c:
  245.             if i in x:
  246.                 self.__added = self.__added.union(i.attachments)
  247.             else:
  248.                 r.extend(i.attachments)
  249.         r.extend(self.__added)
  250.         for i, a in enumerate(r[:]):
  251.             if a.id in self.__removed:
  252.                 del r[i]
  253.         return r        
  254.         
  255.     def _get_current(self):
  256.         if not self.parsed:
  257.             self.parse()
  258.         return self.__current
  259.         
  260.     def _set_current(self, list):
  261.         self.__current = list
  262.     _current = property(_get_current, _set_current)
  263.     
  264.         
  265.     def __getitem__(self, key):
  266.         if isinstance(key, LPAttachment):
  267.             key = key.id
  268.         list_id = [a.id for a in self._current]
  269.         if key in list_id:
  270.             return self._current[list_id.index(key)]
  271.         try:
  272.             return self._current[key]
  273.         except IndexError:
  274.             list_id = [a.id for a in self.__removed]
  275.             if key in list_id:
  276.                 raise ValueError, "This attachment has been removed"
  277.         raise IndexError, "could not find '%s' in attachments ('%s')" %(key, self._url or "unknown url")
  278.         
  279.     def __str__(self):
  280.         return str(self._current)
  281.         
  282.     def __repr__(self):
  283.         return "<Attachmentlist>"
  284.         
  285.     def __iter__(self):
  286.         for i in self._current:
  287.             yield i
  288.             
  289.     def __len__(self):
  290.         return len(self.__current)
  291.             
  292.     def filter(self, func):
  293.         for a in self._current:
  294.             if func(a):
  295.                 yield a
  296.         
  297.     def add(self, attachment):
  298.         if isinstance(attachment, LPAttachment):
  299.             self.__added.add(attachment)
  300.             self._current.append(attachment)
  301.         else:
  302.             #TODO: raise TypeError
  303.             raise IOError, "'attachment' must be an instance of 'Attachment'"
  304.         
  305.     def remove(self, key=None, func=None):
  306.         arg = [i for i in (key,func) if i is None]
  307.         assert len(arg) == 1, "exactly one argument needed"
  308.         if func:
  309.             key = list(self.filter(func))
  310.         def _isiterable(x):
  311.             try:
  312.                 i = iter(x)
  313.                 return not isinstance(x, str) or False
  314.             except TypeError:
  315.                 return False
  316.         if _isiterable(key):
  317.             for i in key:
  318.                 self.remove(key=i)
  319.             return True
  320.         try:
  321.             if hasattr(key, "id"):
  322.                 key = key.id
  323.             x = self[key]
  324.             self.__removed.add(x)
  325.             try:
  326.                 self.__current.remove(x)
  327.             except ValueError:
  328.                 raise RunTimeError, "this should not happen"
  329.         except (TypeError, IndexError):
  330.             raise TypeError, "unsupported type of 'key' in Attachment.remove()"
  331.         
  332.     def parse(self):
  333.         r = True
  334.         if self.parsed:
  335.             return True
  336.         if not self.__comments.parsed:
  337.             r = self.__comments.parse()
  338.         self.__cache = self._get_attachments()
  339.         self._current = self.__cache[:]
  340.         self.parsed = True
  341.         return r
  342.         
  343.     @property
  344.     def deleted(self):
  345.         return frozenset(self.__removed)
  346.     
  347.     @property
  348.     def added(self):
  349.         return frozenset(self.__added)
  350.     
  351.     @property
  352.     def changed(self):
  353.         changed = []
  354.         deleted = self.deleted
  355.         added = self.added
  356.         x = set()
  357.         for i in self.__comments.changed:
  358.             x = x.union(i.component.attachments)
  359.         for i in deleted:
  360.             changed.append(change_obj(i, action="deleted"))
  361.         for i in added:
  362.             if i in x:
  363.                 continue
  364.             changed.append(change_obj(i, action="added"))
  365.         for i in set(self.__cache) - added - deleted:
  366.             if i.changed:
  367.                 changed.append(change_obj(i))
  368.         return changed
  369.         
  370.     def commit(self, force_changes=False, ignore_lp_errors=True):
  371.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  372.         
  373.         
  374.