<commit_before>import os

URLS = {
    'user':   os.environ.get('MARKETSIGHT_USER_ENDPOINT', 'https://application.marketsight.com/MarketSightWebServices/DatasetUploadAuthorizationService.asmx?WSDL'),
    'upload': os.environ.get('MARKETSIGHT_UPLOAD_ENDPOINT', 'https://application.marketsight.com/MarketSightWebServices/DatasetUploadService.asmx?WSDL'),
    'reports': os.environ.get('MARKETSIGHT_REVIEW_ENDPOINT', 'https://application.marketsight.com/MktgWorksite/ItemView.aspx'),
}

import ipdb; ipdb.set_trace()<commit_msg>removed debug<commit_after>import os

URLS = {
    'user':   os.environ.get('MARKETSIGHT_USER_ENDPOINT', 'https://application.marketsight.com/MarketSightWebServices/DatasetUploadAuthorizationService.asmx?WSDL'),
    'upload': os.environ.get('MARKETSIGHT_UPLOAD_ENDPOINT', 'https://application.marketsight.com/MarketSightWebServices/DatasetUploadService.asmx?WSDL'),
    'reports': os.environ.get('MARKETSIGHT_REVIEW_ENDPOINT', 'https://application.marketsight.com/MktgWorksite/ItemView.aspx'),
}
<|endoftext|>


<commit_before>#encoding: cinje

: from .admintemplate import page
: from .. import table_args, caption_args
: import os

: def selectfile title, ctx, files=[]
    : using page title, ctx, lang="en"

        <form action='/admin/updatedatabase/unpack' method='post'>
         <fieldset>
          <legend>File Selection</legend>

           Select a file to unpack
           <select class="form-control" id="fileselection" name="fileselection">
           : for f in files
            <option value='${os.path.split(f)[-1]}'>${os.path.split(f)[-1]}</option>
           : end
           : if len(files) == 0
            <option value='uncompressed'>I uploaded an uncompressed file</option>
           : end
           </select>
           Backup your database? (recommended)
           <input type='checkbox' name='backupdb' value='1' checked>

         </fieldset>
         <button type="submit" class="btn btn-primary" >Unpack</button>
        </form>

    : end
: end

: def selectdatabasefile title, ctx, files=[]
    : using page title, ctx, lang='en'

     <form id='updatedatabase-form' class='ajax' style='#{'display: none' if ctx.queries.is_updating() else ''}' action='/admin/updatedatabase/updatedatabase' method='post'>
      <fieldset>
       <legend>Update Database</legend>

        Select the database main or xml file
        <select class='form-control' id='fileselection' name='fileselection'>
        : for f in files
         <option value='${os.path.split(f)[-1]}'>${os.path.split(f)[-1]}</option>
        : end
        </select>

      </fieldset>
      <button type='submit' class='btn btn-primary'>Update Database</button>
     </form>
     : use updateprogress title, ctx

    : end
: end

: def updateprogress title, ctx
    #: using page title, ctx, lang='en'
    #

      0%

Update Statistics

Ave. Time	Tracks	Deleted

Total	Checked	Added	Updated	Tracks	Played	Requests	Mistags

0	0	0	0	0
           0	0	0	0

File	Artist	Album	Title

New Tracks 0

File	Artist	Album	Title

Updated Tracks 0

File	Field	Original	Updated

    #: end
: end

: def updatecomplete ctx
    : print('Update complete:', ctx)
    : ctx.session.databaseupdating = False
    : return ctx
: end
<commit_msg>Changed the stage header to start with a default message, with the spinner on.<commit_after>#encoding: cinje

: from .admintemplate import page
: from .. import table_args, caption_args
: import os

: def selectfile title, ctx, files=[]
    : using page title, ctx, lang="en"

        <form action='/admin/updatedatabase/unpack' method='post'>
         <fieldset>
          <legend>File Selection</legend>

           Select a file to unpack
           <select class="form-control" id="fileselection" name="fileselection">
           : for f in files
            <option value='${os.path.split(f)[-1]}'>${os.path.split(f)[-1]}</option>
           : end
           : if len(files) == 0
            <option value='uncompressed'>I uploaded an uncompressed file</option>
           : end
           </select>
           Backup your database? (recommended)
           <input type='checkbox' name='backupdb' value='1' checked>

         </fieldset>
         <button type="submit" class="btn btn-primary" >Unpack</button>
        </form>

    : end
: end

: def selectdatabasefile title, ctx, files=[]
    : using page title, ctx, lang='en'

     <form id='updatedatabase-form' class='ajax' style='#{'display: none' if ctx.queries.is_updating() else ''}' action='/admin/updatedatabase/updatedatabase' method='post'>
      <fieldset>
       <legend>Update Database</legend>

        Select the database main or xml file
        <select class='form-control' id='fileselection' name='fileselection'>
        : for f in files
         <option value='${os.path.split(f)[-1]}'>${os.path.split(f)[-1]}</option>
        : end
        </select>

      </fieldset>
      <button type='submit' class='btn btn-primary'>Update Database</button>
     </form>
     : use updateprogress title, ctx

    : end
: end

: def updateprogress title, ctx
    #: using page title, ctx, lang='en'
    #

Prepairing To Update

      0%

Update Statistics

Ave. Time	Tracks	Deleted

Total	Checked	Added	Updated	Tracks	Played	Requests	Mistags

0	0	0	0	0
           0	0	0	0

File	Artist	Album	Title

New Tracks 0

File	Artist	Album	Title

Updated Tracks 0

File	Field	Original	Updated

    #: end
: end

: def updatecomplete ctx
    : print('Update complete:', ctx)
    : ctx.session.databaseupdating = False
    : return ctx
: end
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from urllib.parse import quote_plus

from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject

class Audio(PlexPartialObject):
    """ Base class for audio :class:`~plexapi.audio.Artist`, :class:`~plexapi.audio.Album`
        and :class:`~plexapi.audio.Track` objects.

        Attributes:
            addedAt (datetime): Datetime this item was added to the library.
            index (sting): Index Number (often the track number).
            key (str): API URL (/library/metadata/<ratingkey>).
            lastViewedAt (datetime): Datetime item was last accessed.
            librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
            listType (str): Hardcoded as 'audio' (useful for search filters).
            ratingKey (int): Unique key identifying this item.
            summary (str): Summary of the artist, track, or album.
            thumb (str): URL to thumbnail image.
            title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.)
            titleSort (str): Title to use when sorting (defaults to title).
            type (str): 'artist', 'album', or 'track'.
            updatedAt (datatime): Datetime this item was updated.
            viewCount (int): Count of times this item was accessed.
    """

    METADATA_TYPE = 'track'

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        self._data = data
        self.listType = 'audio'
        self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
        self.index = data.attrib.get('index')
        self.key = data.attrib.get('key')
        self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
        self.librarySectionID = data.attrib.get('librarySectionID')
        self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
        self.summary = data.attrib.get('summary')
        self.thumb = data.attrib.get('thumb')
        self.title = data.attrib.get('title')
        self.titleSort = data.attrib.get('titleSort', self.title)
        self.type = data.attrib.get('type')
        self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
        self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0))

    @property
    def thumbUrl(self):
        """ Return url to for the thumbnail image. """
        key = self.firstAttr('thumb', 'parentThumb', 'granparentThumb')
        return self._server.url(key, includeToken=True) if key else None

    @property
    def artUrl(self):
        """ Return the first art url starting on the most specific for that item."""
        art = self.firstAttr('art', 'grandparentArt')
        return self._server.url(art, includeToken=True) if art else None

    def url(self, part):
        """ Returns the full URL for this audio item. Typically used for getting a specific track. """
        return self._server.url(part, includeToken=True) if part else None

    def _defaultSyncTitle(self):
        """ Returns str, default title for a new syncItem. """
        return self.title

    def sync(self, bitrate, client=None, clientId=None, limit=None, title=None):
        """ Add current audio (artist, album or track) as sync item for specified device.
            See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions.

            Parameters:
                bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the
                               module :mod:`plexapi.sync`.
                client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see
                                                               :func:`plexapi.myplex.MyPlexAccount.sync`.
                clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`.
                limit (int): maximum count of items to sync, unlimited if `None`.
                title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be
                             generated from metadata of current media.

            Returns:
                :class:`plexapi.sync.SyncItem`: an instance of created syncItem.
        """

        from plexapi.sync import SyncItem, Policy, MediaSettings

        myplex = self._server.myPlexAccount()
        sync_item = SyncItem(self._server, None)
        sync_item.title = title if title else self._defaultSyncTitle()
        sync_item.rootTitle = self.title
        sync_item.contentType = self.listType
        sync_item.metadataType = self.METADATA_TYPE
        sync_item.machineIdentifier = self._server.machineIdentifier

        section = self._server.library.sectionByID(self.librarySectionID)

        sync_item.location = 'library://%s/item/%s' % (section.uuid, quote_plus(self.key))
        sync_item.policy = Policy.create(limit)
        sync_item.mediaSettings = MediaSettings.createMusic(bitrate)

        return myplex.sync(sync_item, client=client, clientId=clientId)

@utils.registerPlexObject
class Artist(Audio):
    """ Represents a single audio artist.

        Attributes:
            TAG (str): 'Directory'
            TYPE (str): 'artist'
            art (str): Artist artwork (/library/metadata/<ratingkey>/art/<artid>)
            countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents.
            genres (list): List of :class:`~plexapi.media.Genre` objects this artist respresents.
            guid (str): Unknown (unique ID; com.plexapp.agents.plexmusic://gracenote/artist/05517B8701668D28?lang=en)
            key (str): API URL (/library/metadata/<ratingkey>).
            location (str): Filepath this artist is found on disk.
            similar (list): List of :class:`~plexapi.media.Similar` artists.
    """
    TAG = 'Directory'
    TYPE = 'artist'

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        Audio._loadData(self, data)
        self.art = data.attrib.get('art')
        self.guid = data.attrib.get('guid')
        self.key = self.key.replace('/children', '')  # FIX_BUG_50
        self.locations = self.listAttrs(data, 'path', etag='Location')
        self.countries = self.findItems(data, media.Country)
        self.genres = self.findItems(data, media.Genre)
        self.similar = self.findItems(data, media.Similar)
        self.collections = self.findItems(data, media.Collection)

    def __iter__(self):
        for album in self.albums():
            yield album

    def album(self, title):
        """ Returns the :class:`~plexapi.audio.Album` that matches the specified title.

            Parameters:
                title (str): Title of the album to return.
        """
        key = '%s/children' % self.key
        return self.fetchItem(key, title__iexact=title)

    def albums(self, **kwargs):
        """ Returns a list of :class:`~plexapi.audio.Album` objects by this artist. """
        key = '%s/children' % self.key
        return self.fetchItems(key, **kwargs)

    def track(self, title):
        """ Returns the :class:`~plexapi.audio.Track` that matches the specified title.

            Parameters:
                title (str): Title of the track to return.
        """
        key = '%s/allLeaves' % self.key
        return self.fetchItem(key, title__iexact=title)

    def tracks(self, **kwargs):
        """ Returns a list of :class:`~plexapi.audio.Track` objects by this artist. """
        key = '%s/allLeaves' % self.key
        return self.fetchItems(key, **kwargs)

    def get(self, title):
        """ Alias of :func:`~plexapi.audio.Artist.track`. """
        return self.track(title)

    def download(self, savepath=None, keep_original_name=False, **kwargs):
        """ Downloads all tracks for this artist to the specified location.

            Parameters:
                savepath (str): Title of the track to return.
                keep_original_name (bool): Set True to keep the original filename as stored in
                    the Plex server. False will create a new filename with the format
                    "<Atrist> - <Album> <Track>".
                kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
                    be returned and the additional arguments passed in will be sent to that
                    function. If kwargs is not specified, the media items will be downloaded
                    and saved to disk.
        """
        filepaths = []
        for album in self.albums():
            for track in album.tracks():
                filepaths += track.download(savepath, keep_original_name, **kwargs)
        return filepaths

@utils.registerPlexObject
class Album(Audio):
    """ Represents a single audio album.

        Attributes:
            TAG (str): 'Directory'
            TYPE (str): 'album'
            art (str): Album artwork (/library/metadata/<ratingkey>/art/<artid>)
            genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents.
            key (str): API URL (/library/metadata/<ratingkey>).
            originallyAvailableAt (datetime): Datetime this album was released.
            parentKey (str): API URL of this artist.
            parentRatingKey (int): Unique key identifying artist.
            parentThumb (str): URL to artist thumbnail image.
            parentTitle (str): Name of the artist for this album.
            studio (str): Studio that released this album.
            year (int): Year this album was released.
    """
    TAG = 'Directory'
    TYPE = 'album'

    def __iter__(self):
        for track in self.tracks:
            yield track

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        Audio._loadData(self, data)
        self.art = data.attrib.get('art')
        self.key = self.key.replace('/children', '')  # fixes bug #50
        self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
        self.parentKey = data.attrib.get('parentKey')
        self.parentRatingKey = data.attrib.get('parentRatingKey')
        self.parentThumb = data.attrib.get('parentThumb')
        self.parentTitle = data.attrib.get('parentTitle')
        self.studio = data.attrib.get('studio')
        self.year = utils.cast(int, data.attrib.get('year'))
        self.genres = self.findItems(data, media.Genre)
        self.collections = self.findItems(data, media.Collection)
        self.labels = self.findItems(data, media.Label)

    def track(self, title):
        """ Returns the :class:`~plexapi.audio.Track` that matches the specified title.

            Parameters:
                title (str): Title of the track to return.
        """
        key = '%s/children' % self.key
        return self.fetchItem(key, title__iexact=title)

    def tracks(self, **kwargs):
        """ Returns a list of :class:`~plexapi.audio.Track` objects in this album. """
        key = '%s/children' % self.key
        return self.fetchItems(key, **kwargs)

    def get(self, title):
        """ Alias of :func:`~plexapi.audio.Album.track`. """
        return self.track(title)

    def artist(self):
        """ Return :func:`~plexapi.audio.Artist` of this album. """
        return self.fetchItem(self.parentKey)

    def download(self, savepath=None, keep_original_name=False, **kwargs):
        """ Downloads all tracks for this artist to the specified location.

            Parameters:
                savepath (str): Title of the track to return.
                keep_original_name (bool): Set True to keep the original filename as stored in
                    the Plex server. False will create a new filename with the format
                    "<Atrist> - <Album> <Track>".
                kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
                    be returned and the additional arguments passed in will be sent to that
                    function. If kwargs is not specified, the media items will be downloaded
                    and saved to disk.
        """
        filepaths = []
        for track in self.tracks():
            filepaths += track.download(savepath, keep_original_name, **kwargs)
        return filepaths

    def _defaultSyncTitle(self):
        """ Returns str, default title for a new syncItem. """
        return '%s - %s' % (self.parentTitle, self.title)

@utils.registerPlexObject
class Track(Audio, Playable):
    """ Represents a single audio track.

        Attributes:
            TAG (str): 'Directory'
            TYPE (str): 'track'
            art (str): Track artwork (/library/metadata/<ratingkey>/art/<artid>)
            chapterSource (TYPE): Unknown
            duration (int): Length of this album in seconds.
            grandparentArt (str): Album artist artwork.
            grandparentKey (str): Album artist API URL.
            grandparentRatingKey (str): Unique key identifying album artist.
            grandparentThumb (str): URL to album artist thumbnail image.
            grandparentTitle (str): Name of the album artist for this track.
            guid (str): Unknown (unique ID).
            media (list): List of :class:`~plexapi.media.Media` objects for this track.
            moods (list): List of :class:`~plexapi.media.Mood` objects for this track.
            originalTitle (str): Track artist.
            parentIndex (int): Album index.
            parentKey (str): Album API URL.
            parentRatingKey (int): Unique key identifying album.
            parentThumb (str): URL to album thumbnail image.
            parentTitle (str): Name of the album for this track.
            primaryExtraKey (str): Unknown
            ratingCount (int): Unknown
            userRating (float): Rating of this track (0.0 - 10.0) equaling (0 stars - 5 stars)
            viewOffset (int): Unknown
            year (int): Year this track was released.
            sessionKey (int): Session Key (active sessions only).
            usernames (str): Username of person playing this track (active sessions only).
            player (str): :class:`~plexapi.client.PlexClient` for playing track (active sessions only).
            transcodeSessions (None): :class:`~plexapi.media.TranscodeSession` for playing
                track (active sessions only).
    """
    TAG = 'Track'
    TYPE = 'track'

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        Audio._loadData(self, data)
        Playable._loadData(self, data)
        self.art = data.attrib.get('art')
        self.chapterSource = data.attrib.get('chapterSource')
        self.duration = utils.cast(int, data.attrib.get('duration'))
        self.grandparentArt = data.attrib.get('grandparentArt')
        self.grandparentKey = data.attrib.get('grandparentKey')
        self.grandparentRatingKey = data.attrib.get('grandparentRatingKey')
        self.grandparentThumb = data.attrib.get('grandparentThumb')
        self.grandparentTitle = data.attrib.get('grandparentTitle')
        self.guid = data.attrib.get('guid')
        self.originalTitle = data.attrib.get('originalTitle')
        self.parentIndex = data.attrib.get('parentIndex')
        self.parentKey = data.attrib.get('parentKey')
        self.parentRatingKey = data.attrib.get('parentRatingKey')
        self.parentThumb = data.attrib.get('parentThumb')
        self.parentTitle = data.attrib.get('parentTitle')
        self.primaryExtraKey = data.attrib.get('primaryExtraKey')
        self.ratingCount = utils.cast(int, data.attrib.get('ratingCount'))
        self.userRating = utils.cast(float, data.attrib.get('userRating', 0))
        self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
        self.year = utils.cast(int, data.attrib.get('year'))
        self.media = self.findItems(data, media.Media)
        self.moods = self.findItems(data, media.Mood)

    def _prettyfilename(self):
        """ Returns a filename for use in download. """
        return '%s - %s %s' % (self.grandparentTitle, self.parentTitle, self.title)

    def album(self):
        """ Return this track's :class:`~plexapi.audio.Album`. """
        return self.fetchItem(self.parentKey)

    def artist(self):
        """ Return this track's :class:`~plexapi.audio.Artist`. """
        return self.fetchItem(self.grandparentKey)

    def _defaultSyncTitle(self):
        """ Returns str, default title for a new syncItem. """
        return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title)
<commit_msg>adding fields attribute to audio.Audio docstring update<commit_after># -*- coding: utf-8 -*-
from urllib.parse import quote_plus

from plexapi import media, utils
from plexapi.base import Playable, PlexPartialObject

class Audio(PlexPartialObject):
    """ Base class for audio :class:`~plexapi.audio.Artist`, :class:`~plexapi.audio.Album`
        and :class:`~plexapi.audio.Track` objects.

        Attributes:
            addedAt (datetime): Datetime this item was added to the library.
            fields (list): List of :class:`~plexapi.media.Field`.
            index (sting): Index Number (often the track number).
            key (str): API URL (/library/metadata/<ratingkey>).
            lastViewedAt (datetime): Datetime item was last accessed.
            librarySectionID (int): :class:`~plexapi.library.LibrarySection` ID.
            listType (str): Hardcoded as 'audio' (useful for search filters).
            ratingKey (int): Unique key identifying this item.
            summary (str): Summary of the artist, track, or album.
            thumb (str): URL to thumbnail image.
            title (str): Artist, Album or Track title. (Jason Mraz, We Sing, Lucky, etc.)
            titleSort (str): Title to use when sorting (defaults to title).
            type (str): 'artist', 'album', or 'track'.
            updatedAt (datatime): Datetime this item was updated.
            viewCount (int): Count of times this item was accessed.
    """

    METADATA_TYPE = 'track'

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        self._data = data
        self.listType = 'audio'
        self.addedAt = utils.toDatetime(data.attrib.get('addedAt'))
        self.fields = self.findItems(data, etag='Field')
        self.index = data.attrib.get('index')
        self.key = data.attrib.get('key')
        self.lastViewedAt = utils.toDatetime(data.attrib.get('lastViewedAt'))
        self.librarySectionID = data.attrib.get('librarySectionID')
        self.ratingKey = utils.cast(int, data.attrib.get('ratingKey'))
        self.summary = data.attrib.get('summary')
        self.thumb = data.attrib.get('thumb')
        self.title = data.attrib.get('title')
        self.titleSort = data.attrib.get('titleSort', self.title)
        self.type = data.attrib.get('type')
        self.updatedAt = utils.toDatetime(data.attrib.get('updatedAt'))
        self.viewCount = utils.cast(int, data.attrib.get('viewCount', 0))

    @property
    def thumbUrl(self):
        """ Return url to for the thumbnail image. """
        key = self.firstAttr('thumb', 'parentThumb', 'granparentThumb')
        return self._server.url(key, includeToken=True) if key else None

    @property
    def artUrl(self):
        """ Return the first art url starting on the most specific for that item."""
        art = self.firstAttr('art', 'grandparentArt')
        return self._server.url(art, includeToken=True) if art else None

    def url(self, part):
        """ Returns the full URL for this audio item. Typically used for getting a specific track. """
        return self._server.url(part, includeToken=True) if part else None

    def _defaultSyncTitle(self):
        """ Returns str, default title for a new syncItem. """
        return self.title

    def sync(self, bitrate, client=None, clientId=None, limit=None, title=None):
        """ Add current audio (artist, album or track) as sync item for specified device.
            See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions.

            Parameters:
                bitrate (int): maximum bitrate for synchronized music, better use one of MUSIC_BITRATE_* values from the
                               module :mod:`plexapi.sync`.
                client (:class:`plexapi.myplex.MyPlexDevice`): sync destination, see
                                                               :func:`plexapi.myplex.MyPlexAccount.sync`.
                clientId (str): sync destination, see :func:`plexapi.myplex.MyPlexAccount.sync`.
                limit (int): maximum count of items to sync, unlimited if `None`.
                title (str): descriptive title for the new :class:`plexapi.sync.SyncItem`, if empty the value would be
                             generated from metadata of current media.

            Returns:
                :class:`plexapi.sync.SyncItem`: an instance of created syncItem.
        """

        from plexapi.sync import SyncItem, Policy, MediaSettings

        myplex = self._server.myPlexAccount()
        sync_item = SyncItem(self._server, None)
        sync_item.title = title if title else self._defaultSyncTitle()
        sync_item.rootTitle = self.title
        sync_item.contentType = self.listType
        sync_item.metadataType = self.METADATA_TYPE
        sync_item.machineIdentifier = self._server.machineIdentifier

        section = self._server.library.sectionByID(self.librarySectionID)

        sync_item.location = 'library://%s/item/%s' % (section.uuid, quote_plus(self.key))
        sync_item.policy = Policy.create(limit)
        sync_item.mediaSettings = MediaSettings.createMusic(bitrate)

        return myplex.sync(sync_item, client=client, clientId=clientId)

@utils.registerPlexObject
class Artist(Audio):
    """ Represents a single audio artist.

        Attributes:
            TAG (str): 'Directory'
            TYPE (str): 'artist'
            art (str): Artist artwork (/library/metadata/<ratingkey>/art/<artid>)
            countries (list): List of :class:`~plexapi.media.Country` objects this artist respresents.
            genres (list): List of :class:`~plexapi.media.Genre` objects this artist respresents.
            guid (str): Unknown (unique ID; com.plexapp.agents.plexmusic://gracenote/artist/05517B8701668D28?lang=en)
            key (str): API URL (/library/metadata/<ratingkey>).
            location (str): Filepath this artist is found on disk.
            similar (list): List of :class:`~plexapi.media.Similar` artists.
    """
    TAG = 'Directory'
    TYPE = 'artist'

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        Audio._loadData(self, data)
        self.art = data.attrib.get('art')
        self.guid = data.attrib.get('guid')
        self.key = self.key.replace('/children', '')  # FIX_BUG_50
        self.locations = self.listAttrs(data, 'path', etag='Location')
        self.countries = self.findItems(data, media.Country)
        self.genres = self.findItems(data, media.Genre)
        self.similar = self.findItems(data, media.Similar)
        self.collections = self.findItems(data, media.Collection)

    def __iter__(self):
        for album in self.albums():
            yield album

    def album(self, title):
        """ Returns the :class:`~plexapi.audio.Album` that matches the specified title.

            Parameters:
                title (str): Title of the album to return.
        """
        key = '%s/children' % self.key
        return self.fetchItem(key, title__iexact=title)

    def albums(self, **kwargs):
        """ Returns a list of :class:`~plexapi.audio.Album` objects by this artist. """
        key = '%s/children' % self.key
        return self.fetchItems(key, **kwargs)

    def track(self, title):
        """ Returns the :class:`~plexapi.audio.Track` that matches the specified title.

            Parameters:
                title (str): Title of the track to return.
        """
        key = '%s/allLeaves' % self.key
        return self.fetchItem(key, title__iexact=title)

    def tracks(self, **kwargs):
        """ Returns a list of :class:`~plexapi.audio.Track` objects by this artist. """
        key = '%s/allLeaves' % self.key
        return self.fetchItems(key, **kwargs)

    def get(self, title):
        """ Alias of :func:`~plexapi.audio.Artist.track`. """
        return self.track(title)

    def download(self, savepath=None, keep_original_name=False, **kwargs):
        """ Downloads all tracks for this artist to the specified location.

            Parameters:
                savepath (str): Title of the track to return.
                keep_original_name (bool): Set True to keep the original filename as stored in
                    the Plex server. False will create a new filename with the format
                    "<Atrist> - <Album> <Track>".
                kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
                    be returned and the additional arguments passed in will be sent to that
                    function. If kwargs is not specified, the media items will be downloaded
                    and saved to disk.
        """
        filepaths = []
        for album in self.albums():
            for track in album.tracks():
                filepaths += track.download(savepath, keep_original_name, **kwargs)
        return filepaths

@utils.registerPlexObject
class Album(Audio):
    """ Represents a single audio album.

        Attributes:
            TAG (str): 'Directory'
            TYPE (str): 'album'
            art (str): Album artwork (/library/metadata/<ratingkey>/art/<artid>)
            genres (list): List of :class:`~plexapi.media.Genre` objects this album respresents.
            key (str): API URL (/library/metadata/<ratingkey>).
            originallyAvailableAt (datetime): Datetime this album was released.
            parentKey (str): API URL of this artist.
            parentRatingKey (int): Unique key identifying artist.
            parentThumb (str): URL to artist thumbnail image.
            parentTitle (str): Name of the artist for this album.
            studio (str): Studio that released this album.
            year (int): Year this album was released.
    """
    TAG = 'Directory'
    TYPE = 'album'

    def __iter__(self):
        for track in self.tracks:
            yield track

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        Audio._loadData(self, data)
        self.art = data.attrib.get('art')
        self.key = self.key.replace('/children', '')  # fixes bug #50
        self.originallyAvailableAt = utils.toDatetime(data.attrib.get('originallyAvailableAt'), '%Y-%m-%d')
        self.parentKey = data.attrib.get('parentKey')
        self.parentRatingKey = data.attrib.get('parentRatingKey')
        self.parentThumb = data.attrib.get('parentThumb')
        self.parentTitle = data.attrib.get('parentTitle')
        self.studio = data.attrib.get('studio')
        self.year = utils.cast(int, data.attrib.get('year'))
        self.genres = self.findItems(data, media.Genre)
        self.collections = self.findItems(data, media.Collection)
        self.labels = self.findItems(data, media.Label)

    def track(self, title):
        """ Returns the :class:`~plexapi.audio.Track` that matches the specified title.

            Parameters:
                title (str): Title of the track to return.
        """
        key = '%s/children' % self.key
        return self.fetchItem(key, title__iexact=title)

    def tracks(self, **kwargs):
        """ Returns a list of :class:`~plexapi.audio.Track` objects in this album. """
        key = '%s/children' % self.key
        return self.fetchItems(key, **kwargs)

    def get(self, title):
        """ Alias of :func:`~plexapi.audio.Album.track`. """
        return self.track(title)

    def artist(self):
        """ Return :func:`~plexapi.audio.Artist` of this album. """
        return self.fetchItem(self.parentKey)

    def download(self, savepath=None, keep_original_name=False, **kwargs):
        """ Downloads all tracks for this artist to the specified location.

            Parameters:
                savepath (str): Title of the track to return.
                keep_original_name (bool): Set True to keep the original filename as stored in
                    the Plex server. False will create a new filename with the format
                    "<Atrist> - <Album> <Track>".
                kwargs (dict): If specified, a :func:`~plexapi.audio.Track.getStreamURL()` will
                    be returned and the additional arguments passed in will be sent to that
                    function. If kwargs is not specified, the media items will be downloaded
                    and saved to disk.
        """
        filepaths = []
        for track in self.tracks():
            filepaths += track.download(savepath, keep_original_name, **kwargs)
        return filepaths

    def _defaultSyncTitle(self):
        """ Returns str, default title for a new syncItem. """
        return '%s - %s' % (self.parentTitle, self.title)

@utils.registerPlexObject
class Track(Audio, Playable):
    """ Represents a single audio track.

        Attributes:
            TAG (str): 'Directory'
            TYPE (str): 'track'
            art (str): Track artwork (/library/metadata/<ratingkey>/art/<artid>)
            chapterSource (TYPE): Unknown
            duration (int): Length of this album in seconds.
            grandparentArt (str): Album artist artwork.
            grandparentKey (str): Album artist API URL.
            grandparentRatingKey (str): Unique key identifying album artist.
            grandparentThumb (str): URL to album artist thumbnail image.
            grandparentTitle (str): Name of the album artist for this track.
            guid (str): Unknown (unique ID).
            media (list): List of :class:`~plexapi.media.Media` objects for this track.
            moods (list): List of :class:`~plexapi.media.Mood` objects for this track.
            originalTitle (str): Track artist.
            parentIndex (int): Album index.
            parentKey (str): Album API URL.
            parentRatingKey (int): Unique key identifying album.
            parentThumb (str): URL to album thumbnail image.
            parentTitle (str): Name of the album for this track.
            primaryExtraKey (str): Unknown
            ratingCount (int): Unknown
            userRating (float): Rating of this track (0.0 - 10.0) equaling (0 stars - 5 stars)
            viewOffset (int): Unknown
            year (int): Year this track was released.
            sessionKey (int): Session Key (active sessions only).
            usernames (str): Username of person playing this track (active sessions only).
            player (str): :class:`~plexapi.client.PlexClient` for playing track (active sessions only).
            transcodeSessions (None): :class:`~plexapi.media.TranscodeSession` for playing
                track (active sessions only).
    """
    TAG = 'Track'
    TYPE = 'track'

    def _loadData(self, data):
        """ Load attribute values from Plex XML response. """
        Audio._loadData(self, data)
        Playable._loadData(self, data)
        self.art = data.attrib.get('art')
        self.chapterSource = data.attrib.get('chapterSource')
        self.duration = utils.cast(int, data.attrib.get('duration'))
        self.grandparentArt = data.attrib.get('grandparentArt')
        self.grandparentKey = data.attrib.get('grandparentKey')
        self.grandparentRatingKey = data.attrib.get('grandparentRatingKey')
        self.grandparentThumb = data.attrib.get('grandparentThumb')
        self.grandparentTitle = data.attrib.get('grandparentTitle')
        self.guid = data.attrib.get('guid')
        self.originalTitle = data.attrib.get('originalTitle')
        self.parentIndex = data.attrib.get('parentIndex')
        self.parentKey = data.attrib.get('parentKey')
        self.parentRatingKey = data.attrib.get('parentRatingKey')
        self.parentThumb = data.attrib.get('parentThumb')
        self.parentTitle = data.attrib.get('parentTitle')
        self.primaryExtraKey = data.attrib.get('primaryExtraKey')
        self.ratingCount = utils.cast(int, data.attrib.get('ratingCount'))
        self.userRating = utils.cast(float, data.attrib.get('userRating', 0))
        self.viewOffset = utils.cast(int, data.attrib.get('viewOffset', 0))
        self.year = utils.cast(int, data.attrib.get('year'))
        self.media = self.findItems(data, media.Media)
        self.moods = self.findItems(data, media.Mood)

    def _prettyfilename(self):
        """ Returns a filename for use in download. """
        return '%s - %s %s' % (self.grandparentTitle, self.parentTitle, self.title)

    def album(self):
        """ Return this track's :class:`~plexapi.audio.Album`. """
        return self.fetchItem(self.parentKey)

    def artist(self):
        """ Return this track's :class:`~plexapi.audio.Artist`. """
        return self.fetchItem(self.grandparentKey)

    def _defaultSyncTitle(self):
        """ Returns str, default title for a new syncItem. """
        return '%s - %s - %s' % (self.grandparentTitle, self.parentTitle, self.title)<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import hashlib
import hmac
import logging
from unicodedata import normalize
import psycopg2
import werkzeug

from odoo import http, _
from odoo.http import request
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, consteq, ustr
from datetime import datetime, timedelta

_logger = logging.getLogger(__name__)

class PaymentProcessing(http.Controller):

    @staticmethod
    def remove_payment_transaction(transactions):
        tx_ids_list = request.session.get("__payment_tx_ids__", [])
        if transactions:
            for tx in transactions:
                if tx.id in tx_ids_list:
                    tx_ids_list.remove(tx.id)
        else:
            return False
        request.session["__payment_tx_ids__"] = tx_ids_list
        return True

    @staticmethod
    def add_payment_transaction(transactions):
        if not transactions:
            return False
        tx_ids_list = set(request.session.get("__payment_tx_ids__", [])) | set(transactions.ids)
        request.session["__payment_tx_ids__"] = list(tx_ids_list)
        return True

    @staticmethod
    def get_payment_transaction_ids():
        # return the ids and not the recordset, since we might need to
        # sudo the browse to access all the record
        # I prefer to let the controller chose when to access to payment.transaction using sudo
        return request.session.get("__payment_tx_ids__", [])

    @http.route(['/payment/process'], type="http", auth="public", website=True, sitemap=False)
    def payment_status_page(self, **kwargs):
        # When the customer is redirect to this website page,
        # we retrieve the payment transaction list from his session
        tx_ids_list = self.get_payment_transaction_ids()
        payment_transaction_ids = request.env['payment.transaction'].sudo().browse(tx_ids_list).exists()

        render_ctx = {
            'payment_tx_ids': payment_transaction_ids.ids,
        }
        return request.render("payment.payment_process_page", render_ctx)

    @http.route(['/payment/process/poll'], type="json", auth="public")
    def payment_status_poll(self):
        # retrieve the transactions
        tx_ids_list = self.get_payment_transaction_ids()

        payment_transaction_ids = request.env['payment.transaction'].sudo().search([
            ('id', 'in', list(tx_ids_list)),
            ('date', '>=', (datetime.now() - timedelta(days=1)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
        ])
        if not payment_transaction_ids:
            return {
                'success': False,
                'error': 'no_tx_found',
            }

        processed_tx = payment_transaction_ids.filtered('is_processed')
        self.remove_payment_transaction(processed_tx)

        # create the returned dictionnary
        result = {
            'success': True,
            'transactions': [],
        }
        # populate the returned dictionnary with the transactions data
        for tx in payment_transaction_ids:
            message_to_display = tx.acquirer_id[tx.state + '_msg'] if tx.state in ['done', 'pending', 'cancel'] else None
            tx_info = {
                'reference': tx.reference,
                'state': tx.state,
                'return_url': tx.return_url,
                'is_processed': tx.is_processed,
                'state_message': tx.state_message,
                'message_to_display': message_to_display,
                'amount': tx.amount,
                'currency': tx.currency_id.name,
                'acquirer_provider': tx.acquirer_id.provider,
            }
            tx_info.update(tx._get_processing_info())
            result['transactions'].append(tx_info)

        tx_to_process = payment_transaction_ids.filtered(lambda x: x.state == 'done' and x.is_processed is False)
        try:
            tx_to_process._post_process_after_done()
        except psycopg2.OperationalError as e:
            request.env.cr.rollback()
            result['success'] = False
            result['error'] = "tx_process_retry"
        except Exception as e:
            request.env.cr.rollback()
            result['success'] = False
            result['error'] = str(e)
            _logger.exception("Error while processing transaction(s) %s, exception \"%s\"", tx_to_process.ids, str(e))

        return result

class WebsitePayment(http.Controller):
    @http.route(['/my/payment_method'], type='http', auth="user", website=True)
    def payment_method(self, **kwargs):
        acquirers = list(request.env['payment.acquirer'].search([
            ('state', 'in', ['enabled', 'test']), ('registration_view_template_id', '!=', False),
            ('payment_flow', '=', 's2s'), ('company_id', '=', request.env.company.id)
        ]))
        partner = request.env.user.partner_id
        payment_tokens = partner.payment_token_ids
        payment_tokens |= partner.commercial_partner_id.sudo().payment_token_ids
        return_url = request.params.get('redirect', '/my/payment_method')
        values = {
            'pms': payment_tokens,
            'acquirers': acquirers,
            'error_message': [kwargs['error']] if kwargs.get('error') else False,
            'return_url': return_url,
            'bootstrap_formatting': True,
            'partner_id': partner.id
        }
        return request.render("payment.pay_methods", values)

    @http.route(['/website_payment/pay'], type='http', auth='public', website=True, sitemap=False)
    def pay(self, reference='', order_id=None, amount=False, currency_id=None, acquirer_id=None, partner_id=False, access_token=None, **kw):
        """
        Generic payment page allowing public and logged in users to pay an arbitrary amount.

        In the case of a public user access, we need to ensure that the payment is made anonymously - e.g. it should not be
        possible to pay for a specific partner simply by setting the partner_id GET param to a random id. In the case where
        a partner_id is set, we do an access_token check based on the payment.link.wizard model (since links for specific
        partners should be created from there and there only). Also noteworthy is the filtering of s2s payment methods -
        we don't want to create payment tokens for public users.

        In the case of a logged in user, then we let access rights and security rules do their job.
        """
        env = request.env
        user = env.user.sudo()
        reference = normalize('NFKD', reference).encode('ascii','ignore').decode('utf-8')
        if partner_id and not access_token:
            raise werkzeug.exceptions.NotFound
        if partner_id and access_token:
            token_ok = request.env['payment.link.wizard'].check_token(access_token, int(partner_id), float(amount), int(currency_id))
            if not token_ok:
                raise werkzeug.exceptions.NotFound

        # Default values
        values = {
            'amount': 0.0,
            'currency': user.company_id.currency_id,
        }

        # Check sale order
        if order_id:
            try:
                order_id = int(order_id)
                order = env['sale.order'].browse(order_id)
                values.update({
                    'currency': order.currency_id,
                    'amount': order.amount_total,
                    'order_id': order_id
                })
            except:
                order_id = None

        # Check currency
        if currency_id:
            try:
                currency_id = int(currency_id)
                values['currency'] = env['res.currency'].browse(currency_id)
            except:
                pass

        # Check amount
        if amount:
            try:
                amount = float(amount)
                values['amount'] = amount
            except:
                pass

        # Check reference
        reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
        values['reference'] = env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)

        # Check acquirer
        acquirers = None
        if acquirer_id:
            acquirers = env['payment.acquirer'].browse(int(acquirer_id))
        if not acquirers:
            acquirers = env['payment.acquirer'].search([('state', 'in', ['enabled', 'test']), ('company_id', '=', user.company_id.id)])

        # Check partner
        if not user._is_public():
            # NOTE: this means that if the partner was set in the GET param, it gets overwritten here
            # This is something we want, since security rules are based on the partner - assuming the
            # access_token checked out at the start, this should have no impact on the payment itself
            # existing besides making reconciliation possibly more difficult (if the payment partner is
            # not the same as the invoice partner, for example)
            partner_id = user.partner_id.id
        elif partner_id:
            partner_id = int(partner_id)

        values.update({
            'partner_id': partner_id,
            'bootstrap_formatting': True,
            'error_msg': kw.get('error_msg')
        })

        # s2s mode will always generate a token, which we don't want for public users
        valid_flows = ['form', 's2s'] if not user._is_public() else ['form']
        values['acquirers'] = [acq for acq in acquirers if acq.payment_flow in valid_flows]
        values['pms'] = request.env['payment.token'].search([('acquirer_id', 'in', acquirers.ids)])

        return request.render('payment.pay', values)

    @http.route(['/website_payment/transaction/<string:reference>/<string:amount>/<string:currency_id>',
                '/website_payment/transaction/v2/<string:amount>/<string:currency_id>/<path:reference>',
                '/website_payment/transaction/v2/<string:amount>/<string:currency_id>/<path:reference>/<int:partner_id>'], type='json', auth='public')
    def transaction(self, acquirer_id, reference, amount, currency_id, partner_id=False, **kwargs):
        acquirer = request.env['payment.acquirer'].browse(acquirer_id)
        order_id = kwargs.get('order_id')

        reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
        reference = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)

        values = {
            'acquirer_id': int(acquirer_id),
            'reference': reference,
            'amount': float(amount),
            'currency_id': int(currency_id),
            'partner_id': partner_id,
            'type': 'form_save' if acquirer.save_token != 'none' and partner_id else 'form',
        }

        if order_id:
            values['sale_order_ids'] = [(6, 0, [order_id])]

        reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
        reference_values.update(acquirer_id=int(acquirer_id))
        values['reference'] = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)
        tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
        secret = request.env['ir.config_parameter'].sudo().get_param('database.secret')
        token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount)
        token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest()
        tx.return_url = '/website_payment/confirm?tx_id=%d&access_token=%s' % (tx.id, token)

        PaymentProcessing.add_payment_transaction(tx)

        render_values = {
            'partner_id': partner_id,
        }

        return acquirer.sudo().render(tx.reference, float(amount), int(currency_id), values=render_values)

    @http.route(['/website_payment/token/<string:reference>/<string:amount>/<string:currency_id>',
                '/website_payment/token/v2/<string:amount>/<string:currency_id>/<path:reference>',
                '/website_payment/token/v2/<string:amount>/<string:currency_id>/<path:reference>/<int:partner_id>'], type='http', auth='public', website=True)
    def payment_token(self, pm_id, reference, amount, currency_id, partner_id=False, return_url=None, **kwargs):
        token = request.env['payment.token'].browse(int(pm_id))
        order_id = kwargs.get('order_id')

        if not token:
            return request.redirect('/website_payment/pay?error_msg=%s' % _('Cannot setup the payment.'))

        values = {
            'acquirer_id': token.acquirer_id.id,
            'reference': reference,
            'amount': float(amount),
            'currency_id': int(currency_id),
            'partner_id': int(partner_id),
            'payment_token_id': int(pm_id),
            'type': 'server2server',
            'return_url': return_url,
        }

        if order_id:
            values['sale_order_ids'] = [(6, 0, [int(order_id)])]

        tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
        PaymentProcessing.add_payment_transaction(tx)

        try:
            tx.s2s_do_transaction()
            secret = request.env['ir.config_parameter'].sudo().get_param('database.secret')
            token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount)
            token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest()
            tx.return_url = return_url or '/website_payment/confirm?tx_id=%d&access_token=%s' % (tx.id, token)
        except Exception as e:
            _logger.exception(e)
        return request.redirect('/payment/process')

    @http.route(['/website_payment/confirm'], type='http', auth='public', website=True, sitemap=False)
    def confirm(self, **kw):
        tx_id = int(kw.get('tx_id', 0))
        access_token = kw.get('access_token')
        if tx_id:
            if access_token:
                tx = request.env['payment.transaction'].sudo().browse(tx_id)
                secret = request.env['ir.config_parameter'].sudo().get_param('database.secret')
                valid_token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount)
                valid_token = hmac.new(secret.encode('utf-8'), valid_token_str.encode('utf-8'), hashlib.sha256).hexdigest()
                if not consteq(ustr(valid_token), access_token):
                    raise werkzeug.exceptions.NotFound
            else:
                tx = request.env['payment.transaction'].browse(tx_id)
            if tx.state in ['done', 'authorized']:
                status = 'success'
                message = tx.acquirer_id.done_msg
            elif tx.state == 'pending':
                status = 'warning'
                message = tx.acquirer_id.pending_msg
            PaymentProcessing.remove_payment_transaction(tx)
            return request.render('payment.confirm', {'tx': tx, 'status': status, 'message': message})
        else:
            return request.redirect('/my/home')
<commit_msg>[FIX] payment: don't show unwanted payment token<commit_after># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import hashlib
import hmac
import logging
from unicodedata import normalize
import psycopg2
import werkzeug

from odoo import http, _
from odoo.http import request
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, consteq, ustr
from datetime import datetime, timedelta

_logger = logging.getLogger(__name__)

class PaymentProcessing(http.Controller):

    @staticmethod
    def remove_payment_transaction(transactions):
        tx_ids_list = request.session.get("__payment_tx_ids__", [])
        if transactions:
            for tx in transactions:
                if tx.id in tx_ids_list:
                    tx_ids_list.remove(tx.id)
        else:
            return False
        request.session["__payment_tx_ids__"] = tx_ids_list
        return True

    @staticmethod
    def add_payment_transaction(transactions):
        if not transactions:
            return False
        tx_ids_list = set(request.session.get("__payment_tx_ids__", [])) | set(transactions.ids)
        request.session["__payment_tx_ids__"] = list(tx_ids_list)
        return True

    @staticmethod
    def get_payment_transaction_ids():
        # return the ids and not the recordset, since we might need to
        # sudo the browse to access all the record
        # I prefer to let the controller chose when to access to payment.transaction using sudo
        return request.session.get("__payment_tx_ids__", [])

    @http.route(['/payment/process'], type="http", auth="public", website=True, sitemap=False)
    def payment_status_page(self, **kwargs):
        # When the customer is redirect to this website page,
        # we retrieve the payment transaction list from his session
        tx_ids_list = self.get_payment_transaction_ids()
        payment_transaction_ids = request.env['payment.transaction'].sudo().browse(tx_ids_list).exists()

        render_ctx = {
            'payment_tx_ids': payment_transaction_ids.ids,
        }
        return request.render("payment.payment_process_page", render_ctx)

    @http.route(['/payment/process/poll'], type="json", auth="public")
    def payment_status_poll(self):
        # retrieve the transactions
        tx_ids_list = self.get_payment_transaction_ids()

        payment_transaction_ids = request.env['payment.transaction'].sudo().search([
            ('id', 'in', list(tx_ids_list)),
            ('date', '>=', (datetime.now() - timedelta(days=1)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
        ])
        if not payment_transaction_ids:
            return {
                'success': False,
                'error': 'no_tx_found',
            }

        processed_tx = payment_transaction_ids.filtered('is_processed')
        self.remove_payment_transaction(processed_tx)

        # create the returned dictionnary
        result = {
            'success': True,
            'transactions': [],
        }
        # populate the returned dictionnary with the transactions data
        for tx in payment_transaction_ids:
            message_to_display = tx.acquirer_id[tx.state + '_msg'] if tx.state in ['done', 'pending', 'cancel'] else None
            tx_info = {
                'reference': tx.reference,
                'state': tx.state,
                'return_url': tx.return_url,
                'is_processed': tx.is_processed,
                'state_message': tx.state_message,
                'message_to_display': message_to_display,
                'amount': tx.amount,
                'currency': tx.currency_id.name,
                'acquirer_provider': tx.acquirer_id.provider,
            }
            tx_info.update(tx._get_processing_info())
            result['transactions'].append(tx_info)

        tx_to_process = payment_transaction_ids.filtered(lambda x: x.state == 'done' and x.is_processed is False)
        try:
            tx_to_process._post_process_after_done()
        except psycopg2.OperationalError as e:
            request.env.cr.rollback()
            result['success'] = False
            result['error'] = "tx_process_retry"
        except Exception as e:
            request.env.cr.rollback()
            result['success'] = False
            result['error'] = str(e)
            _logger.exception("Error while processing transaction(s) %s, exception \"%s\"", tx_to_process.ids, str(e))

        return result

class WebsitePayment(http.Controller):
    @http.route(['/my/payment_method'], type='http', auth="user", website=True)
    def payment_method(self, **kwargs):
        acquirers = list(request.env['payment.acquirer'].search([
            ('state', 'in', ['enabled', 'test']), ('registration_view_template_id', '!=', False),
            ('payment_flow', '=', 's2s'), ('company_id', '=', request.env.company.id)
        ]))
        partner = request.env.user.partner_id
        payment_tokens = partner.payment_token_ids
        payment_tokens |= partner.commercial_partner_id.sudo().payment_token_ids
        return_url = request.params.get('redirect', '/my/payment_method')
        values = {
            'pms': payment_tokens,
            'acquirers': acquirers,
            'error_message': [kwargs['error']] if kwargs.get('error') else False,
            'return_url': return_url,
            'bootstrap_formatting': True,
            'partner_id': partner.id
        }
        return request.render("payment.pay_methods", values)

    @http.route(['/website_payment/pay'], type='http', auth='public', website=True, sitemap=False)
    def pay(self, reference='', order_id=None, amount=False, currency_id=None, acquirer_id=None, partner_id=False, access_token=None, **kw):
        """
        Generic payment page allowing public and logged in users to pay an arbitrary amount.

        In the case of a public user access, we need to ensure that the payment is made anonymously - e.g. it should not be
        possible to pay for a specific partner simply by setting the partner_id GET param to a random id. In the case where
        a partner_id is set, we do an access_token check based on the payment.link.wizard model (since links for specific
        partners should be created from there and there only). Also noteworthy is the filtering of s2s payment methods -
        we don't want to create payment tokens for public users.

        In the case of a logged in user, then we let access rights and security rules do their job.
        """
        env = request.env
        user = env.user.sudo()
        reference = normalize('NFKD', reference).encode('ascii','ignore').decode('utf-8')
        if partner_id and not access_token:
            raise werkzeug.exceptions.NotFound
        if partner_id and access_token:
            token_ok = request.env['payment.link.wizard'].check_token(access_token, int(partner_id), float(amount), int(currency_id))
            if not token_ok:
                raise werkzeug.exceptions.NotFound

        # Default values
        values = {
            'amount': 0.0,
            'currency': user.company_id.currency_id,
        }

        # Check sale order
        if order_id:
            try:
                order_id = int(order_id)
                order = env['sale.order'].browse(order_id)
                values.update({
                    'currency': order.currency_id,
                    'amount': order.amount_total,
                    'order_id': order_id
                })
            except:
                order_id = None

        # Check currency
        if currency_id:
            try:
                currency_id = int(currency_id)
                values['currency'] = env['res.currency'].browse(currency_id)
            except:
                pass

        # Check amount
        if amount:
            try:
                amount = float(amount)
                values['amount'] = amount
            except:
                pass

        # Check reference
        reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
        values['reference'] = env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)

        # Check acquirer
        acquirers = None
        if acquirer_id:
            acquirers = env['payment.acquirer'].browse(int(acquirer_id))
        if not acquirers:
            acquirers = env['payment.acquirer'].search([('state', 'in', ['enabled', 'test']), ('company_id', '=', user.company_id.id)])

        # Check partner
        if not user._is_public():
            # NOTE: this means that if the partner was set in the GET param, it gets overwritten here
            # This is something we want, since security rules are based on the partner - assuming the
            # access_token checked out at the start, this should have no impact on the payment itself
            # existing besides making reconciliation possibly more difficult (if the payment partner is
            # not the same as the invoice partner, for example)
            partner_id = user.partner_id.id
        elif partner_id:
            partner_id = int(partner_id)

        values.update({
            'partner_id': partner_id,
            'bootstrap_formatting': True,
            'error_msg': kw.get('error_msg')
        })

        # s2s mode will always generate a token, which we don't want for public users
        valid_flows = ['form', 's2s'] if not user._is_public() else ['form']
        values['acquirers'] = [acq for acq in acquirers if acq.payment_flow in valid_flows]
        if partner_id:
            values['pms'] = request.env['payment.token'].search([
                ('acquirer_id', 'in', acquirers.ids),
                ('partner_id', '=', partner_id)
            ])
        else:
            values['pms'] = []

        return request.render('payment.pay', values)

    @http.route(['/website_payment/transaction/<string:reference>/<string:amount>/<string:currency_id>',
                '/website_payment/transaction/v2/<string:amount>/<string:currency_id>/<path:reference>',
                '/website_payment/transaction/v2/<string:amount>/<string:currency_id>/<path:reference>/<int:partner_id>'], type='json', auth='public')
    def transaction(self, acquirer_id, reference, amount, currency_id, partner_id=False, **kwargs):
        acquirer = request.env['payment.acquirer'].browse(acquirer_id)
        order_id = kwargs.get('order_id')

        reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
        reference = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)

        values = {
            'acquirer_id': int(acquirer_id),
            'reference': reference,
            'amount': float(amount),
            'currency_id': int(currency_id),
            'partner_id': partner_id,
            'type': 'form_save' if acquirer.save_token != 'none' and partner_id else 'form',
        }

        if order_id:
            values['sale_order_ids'] = [(6, 0, [order_id])]

        reference_values = order_id and {'sale_order_ids': [(4, order_id)]} or {}
        reference_values.update(acquirer_id=int(acquirer_id))
        values['reference'] = request.env['payment.transaction']._compute_reference(values=reference_values, prefix=reference)
        tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
        secret = request.env['ir.config_parameter'].sudo().get_param('database.secret')
        token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount)
        token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest()
        tx.return_url = '/website_payment/confirm?tx_id=%d&access_token=%s' % (tx.id, token)

        PaymentProcessing.add_payment_transaction(tx)

        render_values = {
            'partner_id': partner_id,
        }

        return acquirer.sudo().render(tx.reference, float(amount), int(currency_id), values=render_values)

    @http.route(['/website_payment/token/<string:reference>/<string:amount>/<string:currency_id>',
                '/website_payment/token/v2/<string:amount>/<string:currency_id>/<path:reference>',
                '/website_payment/token/v2/<string:amount>/<string:currency_id>/<path:reference>/<int:partner_id>'], type='http', auth='public', website=True)
    def payment_token(self, pm_id, reference, amount, currency_id, partner_id=False, return_url=None, **kwargs):
        token = request.env['payment.token'].browse(int(pm_id))
        order_id = kwargs.get('order_id')

        if not token:
            return request.redirect('/website_payment/pay?error_msg=%s' % _('Cannot setup the payment.'))

        values = {
            'acquirer_id': token.acquirer_id.id,
            'reference': reference,
            'amount': float(amount),
            'currency_id': int(currency_id),
            'partner_id': int(partner_id),
            'payment_token_id': int(pm_id),
            'type': 'server2server',
            'return_url': return_url,
        }

        if order_id:
            values['sale_order_ids'] = [(6, 0, [int(order_id)])]

        tx = request.env['payment.transaction'].sudo().with_context(lang=None).create(values)
        PaymentProcessing.add_payment_transaction(tx)

        try:
            tx.s2s_do_transaction()
            secret = request.env['ir.config_parameter'].sudo().get_param('database.secret')
            token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount)
            token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest()
            tx.return_url = return_url or '/website_payment/confirm?tx_id=%d&access_token=%s' % (tx.id, token)
        except Exception as e:
            _logger.exception(e)
        return request.redirect('/payment/process')

    @http.route(['/website_payment/confirm'], type='http', auth='public', website=True, sitemap=False)
    def confirm(self, **kw):
        tx_id = int(kw.get('tx_id', 0))
        access_token = kw.get('access_token')
        if tx_id:
            if access_token:
                tx = request.env['payment.transaction'].sudo().browse(tx_id)
                secret = request.env['ir.config_parameter'].sudo().get_param('database.secret')
                valid_token_str = '%s%s%s' % (tx.id, tx.reference, tx.amount)
                valid_token = hmac.new(secret.encode('utf-8'), valid_token_str.encode('utf-8'), hashlib.sha256).hexdigest()
                if not consteq(ustr(valid_token), access_token):
                    raise werkzeug.exceptions.NotFound
            else:
                tx = request.env['payment.transaction'].browse(tx_id)
            if tx.state in ['done', 'authorized']:
                status = 'success'
                message = tx.acquirer_id.done_msg
            elif tx.state == 'pending':
                status = 'warning'
                message = tx.acquirer_id.pending_msg
            PaymentProcessing.remove_payment_transaction(tx)
            return request.render('payment.confirm', {'tx': tx, 'status': status, 'message': message})
        else:
            return request.redirect('/my/home')
<|endoftext|>


<commit_before>
__version__ = (0, 0, 18)
<commit_msg>Bump to 0.0.19<commit_after>
__version__ = (0, 0, 19)
<|endoftext|>


<commit_before>#!/usr/bin/python -OO
# This file is part of Archivematica.
#
# Copyright 2010-2011 Artefactual Systems Inc. <http://artefactual.com>
#
# Archivematica 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.
#
# Archivematica 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 Archivematica.  If not, see <http://www.gnu.org/licenses/>.

# @package Archivematica
# @subpackage transcoder
# @author Joseph Perry <joseph@artefactual.com>
# @version svn: $Id$

import os
import uuid
import sys
import databaseInterface
from databaseFunctions import insertIntoFiles
from executeOrRunSubProcess import executeOrRun
from externals.checksummingTools import sha_for_file
from databaseFunctions import insertIntoEvents
import MySQLdb

def updateSizeAndChecksum(fileUUID, filePath, date, eventIdentifierUUID):
    fileSize = os.path.getsize(filePath).__str__()
    checksum = sha_for_file(filePath).__str__()

    sql = "UPDATE Files " + \
        "SET fileSize='" + fileSize +"', checksum='" + checksum +  "' " + \
        "WHERE fileUUID='" + fileUUID + "'"
    databaseInterface.runSQL(sql)

    insertIntoEvents(fileUUID=fileUUID, \
                     eventIdentifierUUID=eventIdentifierUUID, \
                     eventType="message digest calculation", \
                     eventDateTime=date, \
                     eventDetail="program=\"python\"; module=\"hashlib.sha256()\"", \
                     eventOutcomeDetailNote=checksum)

    insertIntoEvents(fileUUID=fileUUID, \
                 eventIdentifierUUID=uuid.uuid4().__str__(), \
                 eventType="file size calculation", \
                 eventDateTime=date, \
                 eventDetail="program=\"python\"; module=\"os.path.getsize()\"", \
                 eventOutcomeDetailNote=fileSize)

def addFileToTransfer(filePathRelativeToSIP, fileUUID, transferUUID, taskUUID, date, sourceType="ingestion", eventDetail="", use="original"):
    insertIntoFiles(fileUUID, filePathRelativeToSIP, date, transferUUID=transferUUID, use=use)
    insertIntoEvents(fileUUID=fileUUID, \
                   eventIdentifierUUID=taskUUID, \
                   eventType=sourceType, \
                   eventDateTime=date, \
                   eventDetail=eventDetail, \
                   eventOutcome="", \
                   eventOutcomeDetailNote=filePathRelativeToSIP)

def addFileToSIP(filePathRelativeToSIP, fileUUID, sipUUID, taskUUID, date, sourceType="ingestion", use="original"):
    insertIntoFiles(fileUUID, filePathRelativeToSIP, date, sipUUID=sipUUID, use=use)
    insertIntoEvents(fileUUID=fileUUID, \
                   eventIdentifierUUID=taskUUID, \
                   eventType=sourceType, \
                   eventDateTime=date, \
                   eventDetail="", \
                   eventOutcome="", \
                   eventOutcomeDetailNote=filePathRelativeToSIP)

#Used to write to file
#@output - the text to append to the file
#@fileName - The name of the file to create, or append to.
#@returns - 0 if ok, non zero if error occured.
def writeToFile(output, fileName, writeWhite=False):
    if not writeWhite and output.isspace():
        return 0
    if fileName and output:
        print "writing to: " + fileName
        if fileName.startswith("<^Not allowed to write to file^> "):
            return -1
        try:
            f = open(fileName, 'a')
            f.write(output.__str__())
            f.close()
            os.chmod(fileName, 488)
        except OSError, ose:
            print >>sys.stderr, "output Error", ose
            return -2
        except IOError as (errno, strerror):
            print "I/O error({0}): {1}".format(errno, strerror)
            return -3
    else:
        print "No output, or file specified"
    return 0

def checksumFile(filePath, fileUUID):
    global transferDirectory
    truePath = filePath.replace("transfer/", transferDirectory, 1)
    checksum = sha_for_file(truePath)
    utcDate = databaseInterface.getUTCDate()

    #Create Event
    eventIdentifierUUID = uuid.uuid4().__str__()
    eventType = "message digest calculation"
    eventDateTime = utcDate
    eventDetail = 'program="python"; module="hashlib.sha256()" ; file="/usr/lib/python2.6/hashlib.pyc"'
    eventOutcome = ""
    eventOutcomeDetailNote = checksum.__str__()

    databaseInterface.insertIntoEvents(fileUUID=fileUUID, \
                                       eventIdentifierUUID=eventIdentifierUUID, \
                                       eventType=eventType, \
                                       eventDateTime=eventDateTime, \
                                       eventDetail=eventDetail, \
                                       eventOutcome=eventOutcome, \
                                       eventOutcomeDetailNote=eventOutcomeDetailNote)

def removeFile(filePath, utcDate = databaseInterface.getUTCDate()):
    global separator
    print "removing: ", filePath
    filesWithMatchingPath = []

    sqlLoggingLock.acquire()
    #Find the file pk/UUID
    c=MCPloggingSQL.database.cursor()
    sql = """SELECT fileUUID FROM Files WHERE removedTime = 0 AND Files.currentLocation = '""" + MySQLdb.escape_string(filePath) + """';"""
    c.execute(sql)
    row = c.fetchone()
    while row != None:
        filesWithMatchingPath.append(row[0])
        row = c.fetchone()
    sqlLoggingLock.release()
    #Update the database
    for file in filesWithMatchingPath:
        eventIdentifierUUID = uuid.uuid4().__str__()
        eventType = "file removed"
        eventDateTime = utcDate
        eventDetail = ""
        eventOutcomeDetailNote = "removed from: " + filePath

        databaseInterface.insertIntoEvents(fileUUID=fileUUID, \
                                       eventIdentifierUUID=eventIdentifierUUID, \
                                       eventType=eventType, \
                                       eventDateTime=eventDateTime, \
                                       eventDetail=eventDetail, \
                                       eventOutcome=eventOutcome, \
                                       eventOutcomeDetailNote=eventOutcomeDetailNote)

        databaseInterface.runSQL("UPDATE Files " + \
           "SET removedTime='" + utcDate + "', currentLocation=NULL " + \
           "WHERE fileUUID='" + file + "'" )

def renameAsSudo(source, destination):
    """Used to move/rename Directories that the archivematica user may or may not have writes to move"""
    command = "sudo mv \"" + source + "\"   \"" + destination + "\""
    exitCode, stdOut, stdError = executeOrRun("command", command, "", printing=False)
    if exitCode:
        print stdOut
        print >>sys.stderr, stdError
        exit(exitCode)

#import lxml.etree as etree
def updateFileLocation(src, dst, eventType, eventDateTime, eventDetail, eventIdentifierUUID = uuid.uuid4().__str__(), fileUUID="None", sipUUID = None, transferUUID=None, eventOutcomeDetailNote = ""):
    """If the file uuid is not provided, will use the sip uuid and old path to find the file uuid"""

    if not fileUUID or fileUUID == "None":
        sql = "Need to define transferUUID or sipUUID"
        if sipUUID:
            sql = "SELECT Files.fileUUID FROM Files WHERE removedTime = 0 AND Files.currentLocation = '" + MySQLdb.escape_string(src) + "' AND Files.sipUUID = '" + sipUUID + "';"
        elif transferUUID:
            sql = "SELECT Files.fileUUID FROM Files WHERE removedTime = 0 AND Files.currentLocation = '" + MySQLdb.escape_string(src) + "' AND Files.transferUUID = '" + transferUUID + "';"
        c, sqlLock = databaseInterface.querySQL(sql)
        row = c.fetchone()
        while row != None:
            print row
            fileUUID = row[0]
            row = c.fetchone()
        sqlLock.release()

    if eventOutcomeDetailNote == "":
        eventOutcomeDetailNote = "Original name=\"" + src + "\"; cleaned up name=\"" + dst + "\""
        #eventOutcomeDetailNote = eventOutcomeDetailNote.decode('utf-8')

    #CREATE THE EVENT
    if not fileUUID:
        print >>sys.stderr, "Unable to find file uuid for: ", src, " -> ", dst
        exit(6)
    insertIntoEvents(fileUUID=fileUUID, eventIdentifierUUID=eventIdentifierUUID, eventType=eventType, eventDateTime=eventDateTime, eventDetail=eventDetail, eventOutcome="", eventOutcomeDetailNote=eventOutcomeDetailNote)

    #UPDATE THE CURRENT FILE PATH
    sql =  """UPDATE Files SET currentLocation='""" + dst + """' WHERE fileUUID='""" + fileUUID + """';"""
    databaseInterface.runSQL(sql)
        <commit_msg>Issue 801: 	Remove "file size calculation" from PREMIS events<commit_after>#!/usr/bin/python -OO
# This file is part of Archivematica.
#
# Copyright 2010-2011 Artefactual Systems Inc. <http://artefactual.com>
#
# Archivematica 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.
#
# Archivematica 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 Archivematica.  If not, see <http://www.gnu.org/licenses/>.

# @package Archivematica
# @subpackage transcoder
# @author Joseph Perry <joseph@artefactual.com>
# @version svn: $Id$

import os
import uuid
import sys
import databaseInterface
from databaseFunctions import insertIntoFiles
from executeOrRunSubProcess import executeOrRun
from externals.checksummingTools import sha_for_file
from databaseFunctions import insertIntoEvents
import MySQLdb

def updateSizeAndChecksum(fileUUID, filePath, date, eventIdentifierUUID):
    fileSize = os.path.getsize(filePath).__str__()
    checksum = sha_for_file(filePath).__str__()

    sql = "UPDATE Files " + \
        "SET fileSize='" + fileSize +"', checksum='" + checksum +  "' " + \
        "WHERE fileUUID='" + fileUUID + "'"
    databaseInterface.runSQL(sql)

    insertIntoEvents(fileUUID=fileUUID, \
                     eventIdentifierUUID=eventIdentifierUUID, \
                     eventType="message digest calculation", \
                     eventDateTime=date, \
                     eventDetail="program=\"python\"; module=\"hashlib.sha256()\"", \
                     eventOutcomeDetailNote=checksum)

def addFileToTransfer(filePathRelativeToSIP, fileUUID, transferUUID, taskUUID, date, sourceType="ingestion", eventDetail="", use="original"):
    insertIntoFiles(fileUUID, filePathRelativeToSIP, date, transferUUID=transferUUID, use=use)
    insertIntoEvents(fileUUID=fileUUID, \
                   eventIdentifierUUID=taskUUID, \
                   eventType=sourceType, \
                   eventDateTime=date, \
                   eventDetail=eventDetail, \
                   eventOutcome="", \
                   eventOutcomeDetailNote=filePathRelativeToSIP)

def addFileToSIP(filePathRelativeToSIP, fileUUID, sipUUID, taskUUID, date, sourceType="ingestion", use="original"):
    insertIntoFiles(fileUUID, filePathRelativeToSIP, date, sipUUID=sipUUID, use=use)
    insertIntoEvents(fileUUID=fileUUID, \
                   eventIdentifierUUID=taskUUID, \
                   eventType=sourceType, \
                   eventDateTime=date, \
                   eventDetail="", \
                   eventOutcome="", \
                   eventOutcomeDetailNote=filePathRelativeToSIP)

#Used to write to file
#@output - the text to append to the file
#@fileName - The name of the file to create, or append to.
#@returns - 0 if ok, non zero if error occured.
def writeToFile(output, fileName, writeWhite=False):
    if not writeWhite and output.isspace():
        return 0
    if fileName and output:
        print "writing to: " + fileName
        if fileName.startswith("<^Not allowed to write to file^> "):
            return -1
        try:
            f = open(fileName, 'a')
            f.write(output.__str__())
            f.close()
            os.chmod(fileName, 488)
        except OSError, ose:
            print >>sys.stderr, "output Error", ose
            return -2
        except IOError as (errno, strerror):
            print "I/O error({0}): {1}".format(errno, strerror)
            return -3
    else:
        print "No output, or file specified"
    return 0

def checksumFile(filePath, fileUUID):
    global transferDirectory
    truePath = filePath.replace("transfer/", transferDirectory, 1)
    checksum = sha_for_file(truePath)
    utcDate = databaseInterface.getUTCDate()

    #Create Event
    eventIdentifierUUID = uuid.uuid4().__str__()
    eventType = "message digest calculation"
    eventDateTime = utcDate
    eventDetail = 'program="python"; module="hashlib.sha256()" ; file="/usr/lib/python2.6/hashlib.pyc"'
    eventOutcome = ""
    eventOutcomeDetailNote = checksum.__str__()

    databaseInterface.insertIntoEvents(fileUUID=fileUUID, \
                                       eventIdentifierUUID=eventIdentifierUUID, \
                                       eventType=eventType, \
                                       eventDateTime=eventDateTime, \
                                       eventDetail=eventDetail, \
                                       eventOutcome=eventOutcome, \
                                       eventOutcomeDetailNote=eventOutcomeDetailNote)

def removeFile(filePath, utcDate = databaseInterface.getUTCDate()):
    global separator
    print "removing: ", filePath
    filesWithMatchingPath = []

    sqlLoggingLock.acquire()
    #Find the file pk/UUID
    c=MCPloggingSQL.database.cursor()
    sql = """SELECT fileUUID FROM Files WHERE removedTime = 0 AND Files.currentLocation = '""" + MySQLdb.escape_string(filePath) + """';"""
    c.execute(sql)
    row = c.fetchone()
    while row != None:
        filesWithMatchingPath.append(row[0])
        row = c.fetchone()
    sqlLoggingLock.release()
    #Update the database
    for file in filesWithMatchingPath:
        eventIdentifierUUID = uuid.uuid4().__str__()
        eventType = "file removed"
        eventDateTime = utcDate
        eventDetail = ""
        eventOutcomeDetailNote = "removed from: " + filePath

        databaseInterface.insertIntoEvents(fileUUID=fileUUID, \
                                       eventIdentifierUUID=eventIdentifierUUID, \
                                       eventType=eventType, \
                                       eventDateTime=eventDateTime, \
                                       eventDetail=eventDetail, \
                                       eventOutcome=eventOutcome, \
                                       eventOutcomeDetailNote=eventOutcomeDetailNote)

        databaseInterface.runSQL("UPDATE Files " + \
           "SET removedTime='" + utcDate + "', currentLocation=NULL " + \
           "WHERE fileUUID='" + file + "'" )

def renameAsSudo(source, destination):
    """Used to move/rename Directories that the archivematica user may or may not have writes to move"""
    command = "sudo mv \"" + source + "\"   \"" + destination + "\""
    exitCode, stdOut, stdError = executeOrRun("command", command, "", printing=False)
    if exitCode:
        print stdOut
        print >>sys.stderr, stdError
        exit(exitCode)

#import lxml.etree as etree
def updateFileLocation(src, dst, eventType, eventDateTime, eventDetail, eventIdentifierUUID = uuid.uuid4().__str__(), fileUUID="None", sipUUID = None, transferUUID=None, eventOutcomeDetailNote = ""):
    """If the file uuid is not provided, will use the sip uuid and old path to find the file uuid"""

    if not fileUUID or fileUUID == "None":
        sql = "Need to define transferUUID or sipUUID"
        if sipUUID:
            sql = "SELECT Files.fileUUID FROM Files WHERE removedTime = 0 AND Files.currentLocation = '" + MySQLdb.escape_string(src) + "' AND Files.sipUUID = '" + sipUUID + "';"
        elif transferUUID:
            sql = "SELECT Files.fileUUID FROM Files WHERE removedTime = 0 AND Files.currentLocation = '" + MySQLdb.escape_string(src) + "' AND Files.transferUUID = '" + transferUUID + "';"
        c, sqlLock = databaseInterface.querySQL(sql)
        row = c.fetchone()
        while row != None:
            print row
            fileUUID = row[0]
            row = c.fetchone()
        sqlLock.release()

    if eventOutcomeDetailNote == "":
        eventOutcomeDetailNote = "Original name=\"" + src + "\"; cleaned up name=\"" + dst + "\""
        #eventOutcomeDetailNote = eventOutcomeDetailNote.decode('utf-8')

    #CREATE THE EVENT
    if not fileUUID:
        print >>sys.stderr, "Unable to find file uuid for: ", src, " -> ", dst
        exit(6)
    insertIntoEvents(fileUUID=fileUUID, eventIdentifierUUID=eventIdentifierUUID, eventType=eventType, eventDateTime=eventDateTime, eventDetail=eventDetail, eventOutcome="", eventOutcomeDetailNote=eventOutcomeDetailNote)

    #UPDATE THE CURRENT FILE PATH
    sql =  """UPDATE Files SET currentLocation='""" + dst + """' WHERE fileUUID='""" + fileUUID + """';"""
    databaseInterface.runSQL(sql)
        <|endoftext|>


<commit_before>'''
Crash Signature

Represents a crash signature as specified in https://wiki.mozilla.org/Security/CrashSignatures

@author:     Christian Holler (:decoder)

@license:

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

@contact:    choller@mozilla.com
'''

import json
from FTB.Signatures import JSONHelper
from FTB.Signatures.Symptom import Symptom, TestcaseSymptom, StackFramesSymptom

class CrashSignature():
    def __init__(self, rawSignature):
        '''
        Constructor

        @type rawSignature: string
        @param rawSignature: A JSON-formatted string representing the crash signature
        '''

        # For now, we store the original raw signature and hand it out for
        # conversion to String. This is fine as long as our Signature object
        # is immutable. Later, we should implement a method that actually
        # serializes this object back to JSON as it is.
        #
        self.rawSignature = rawSignature
        self.symptoms = []

        try:
            obj = json.loads(rawSignature)
        except ValueError, e:
            raise RuntimeError("Invalid JSON: %s" % e)

        # Get the symptoms objects (mandatory)
        if "symptoms" in obj:
            symptoms = JSONHelper.getArrayChecked(obj, "symptoms", True)
            if len(symptoms) == 0:
                raise RuntimeError("Signature must have at least one symptom.")

            for rawSymptomsObj in symptoms:
                self.symptoms.append(Symptom.fromJSONObject(rawSymptomsObj))
        else:
            raise RuntimeError('Missing mandatory top-level key "symptoms".')

        # Get some optional lists
        self.platforms = JSONHelper.getArrayChecked(obj, "platforms")
        self.operatingSystems = JSONHelper.getArrayChecked(obj, "operatingSystems")
        self.products = JSONHelper.getArrayChecked(obj, "products")

    @staticmethod
    def fromFile(signatureFile):
        with open(signatureFile, 'r') as sigFd:
            return CrashSignature(sigFd.read())

    def __str__(self):
        return self.rawSignature

    def matches(self, crashInfo):
        '''
        Match this signature against the given crash information

        @type crashInfo: CrashInfo
        @param crashInfo: The crash info to match the signature against

        @rtype: bool
        @return: True if the signature matches, False otherwise
        '''
        if self.platforms != None and not crashInfo.platform in self.platforms:
            return False

        if self.operatingSystems != None and not crashInfo.os in self.operatingSystems:
            return False

        if self.products != None and not crashInfo.product in self.products:
            return False

        for symptom in self.symptoms:
            if not symptom.matches(crashInfo):
                return False

        return True

    def matchRequiresTest(self):
        '''
        Check if the signature requires a testcase to match.

        This method can be used to avoid attaching a testcase to the crashInfo
        before matching, avoiding unnecessary I/O on testcase files.

        @rtype: bool
        @return: True if the signature requires a testcase to match
        '''
        for symptom in self.symptoms:
            if isinstance(symptom, TestcaseSymptom):
                return True

        return False

    def getDistance(self, crashInfo):
        total = len(self.symptoms)
        distance = 0

        for symptom in self.symptoms:
            if isinstance(symptom, StackFramesSymptom):
                symptomDistance = symptom.diff(crashInfo)[0]
                if symptomDistance != None:
                    distance += symptomDistance
                else:
                    # If we can't find the distance, assume worst-case
                    distance += len(symptom.functionNames)
                total += len(symptom.functionNames)
            else:
                total += 1
                if not symptom.matches(crashInfo):
                    distance +=1

        return (distance, total)

    def fit(self, crashInfo):
        sigObj = {}
        sigSymptoms = []

        sigObj['symptoms'] = sigSymptoms

        if self.platforms:
            sigObj['platforms'] = self.platforms

        if self.operatingSystems:
            sigObj['operatingSystems'] = self.operatingSystems

        if self.products:
            sigObj['products'] = self.products

        symptomsDiff = self.getSymptomsDiff(crashInfo)

        for symptomDiff in symptomsDiff:
            if symptomDiff['offending']:
                if symptomDiff['proposed']:
                    sigSymptoms.append(symptomDiff['proposed'].jsonobj)
            else:
                sigSymptoms.append(symptomDiff['symptom'].jsonobj)

        return CrashSignature(json.dumps(sigObj, indent=2))

    def getSymptomsDiff(self, crashInfo):
        symptomsDiff = []
        for symptom in self.symptoms:
            if symptom.matches(crashInfo):
                symptomsDiff.append({ 'offending' : False, 'symptom' : symptom })
            else:
                # Special-case StackFramesSymptom because we would like to get a fine-grained
                # view on the offending parts *inside* that symptom. By calling matchWithDiff,
                # we annotate internals of the symptom with distance information to display.
                if isinstance(symptom, StackFramesSymptom):
                    proposedSymptom  = symptom.diff(crashInfo)[1]
                    symptomsDiff.append({ 'offending' : True, 'symptom' : symptom, 'proposed' : proposedSymptom })
                else:
                    symptomsDiff.append({ 'offending' : True, 'symptom' : symptom })
        return symptomsDiff
        <commit_msg>Fix distance problem with StackFramesSymptom (#47)<commit_after>'''
Crash Signature

Represents a crash signature as specified in https://wiki.mozilla.org/Security/CrashSignatures

@author:     Christian Holler (:decoder)

@license:

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

@contact:    choller@mozilla.com
'''

import json
from FTB.Signatures import JSONHelper
from FTB.Signatures.Symptom import Symptom, TestcaseSymptom, StackFramesSymptom

class CrashSignature():
    def __init__(self, rawSignature):
        '''
        Constructor

        @type rawSignature: string
        @param rawSignature: A JSON-formatted string representing the crash signature
        '''

        # For now, we store the original raw signature and hand it out for
        # conversion to String. This is fine as long as our Signature object
        # is immutable. Later, we should implement a method that actually
        # serializes this object back to JSON as it is.
        #
        self.rawSignature = rawSignature
        self.symptoms = []

        try:
            obj = json.loads(rawSignature)
        except ValueError, e:
            raise RuntimeError("Invalid JSON: %s" % e)

        # Get the symptoms objects (mandatory)
        if "symptoms" in obj:
            symptoms = JSONHelper.getArrayChecked(obj, "symptoms", True)
            if len(symptoms) == 0:
                raise RuntimeError("Signature must have at least one symptom.")

            for rawSymptomsObj in symptoms:
                self.symptoms.append(Symptom.fromJSONObject(rawSymptomsObj))
        else:
            raise RuntimeError('Missing mandatory top-level key "symptoms".')

        # Get some optional lists
        self.platforms = JSONHelper.getArrayChecked(obj, "platforms")
        self.operatingSystems = JSONHelper.getArrayChecked(obj, "operatingSystems")
        self.products = JSONHelper.getArrayChecked(obj, "products")

    @staticmethod
    def fromFile(signatureFile):
        with open(signatureFile, 'r') as sigFd:
            return CrashSignature(sigFd.read())

    def __str__(self):
        return self.rawSignature

    def matches(self, crashInfo):
        '''
        Match this signature against the given crash information

        @type crashInfo: CrashInfo
        @param crashInfo: The crash info to match the signature against

        @rtype: bool
        @return: True if the signature matches, False otherwise
        '''
        if self.platforms != None and not crashInfo.platform in self.platforms:
            return False

        if self.operatingSystems != None and not crashInfo.os in self.operatingSystems:
            return False

        if self.products != None and not crashInfo.product in self.products:
            return False

        for symptom in self.symptoms:
            if not symptom.matches(crashInfo):
                return False

        return True

    def matchRequiresTest(self):
        '''
        Check if the signature requires a testcase to match.

        This method can be used to avoid attaching a testcase to the crashInfo
        before matching, avoiding unnecessary I/O on testcase files.

        @rtype: bool
        @return: True if the signature requires a testcase to match
        '''
        for symptom in self.symptoms:
            if isinstance(symptom, TestcaseSymptom):
                return True

        return False

    def getDistance(self, crashInfo):
        total = len(self.symptoms)
        distance = 0

        for symptom in self.symptoms:
            if isinstance(symptom, StackFramesSymptom):
                symptomDistance = symptom.diff(crashInfo)[0]
                if symptomDistance != None:
                    distance += symptomDistance
                else:
                    # If we can't find the distance, assume worst-case
                    distance += len(symptom.functionNames)

                # Weight the StackFramesSymptom not as a single Symptom but
                # by the number of stack frames it has
                total += len(symptom.functionNames)
                total -= 1
            else:
                if not symptom.matches(crashInfo):
                    distance +=1

        return (distance, total)

    def fit(self, crashInfo):
        sigObj = {}
        sigSymptoms = []

        sigObj['symptoms'] = sigSymptoms

        if self.platforms:
            sigObj['platforms'] = self.platforms

        if self.operatingSystems:
            sigObj['operatingSystems'] = self.operatingSystems

        if self.products:
            sigObj['products'] = self.products

        symptomsDiff = self.getSymptomsDiff(crashInfo)

        for symptomDiff in symptomsDiff:
            if symptomDiff['offending']:
                if symptomDiff['proposed']:
                    sigSymptoms.append(symptomDiff['proposed'].jsonobj)
            else:
                sigSymptoms.append(symptomDiff['symptom'].jsonobj)

        return CrashSignature(json.dumps(sigObj, indent=2))

    def getSymptomsDiff(self, crashInfo):
        symptomsDiff = []
        for symptom in self.symptoms:
            if symptom.matches(crashInfo):
                symptomsDiff.append({ 'offending' : False, 'symptom' : symptom })
            else:
                # Special-case StackFramesSymptom because we would like to get a fine-grained
                # view on the offending parts *inside* that symptom. By calling matchWithDiff,
                # we annotate internals of the symptom with distance information to display.
                if isinstance(symptom, StackFramesSymptom):
                    proposedSymptom  = symptom.diff(crashInfo)[1]
                    symptomsDiff.append({ 'offending' : True, 'symptom' : symptom, 'proposed' : proposedSymptom })
                else:
                    symptomsDiff.append({ 'offending' : True, 'symptom' : symptom })
        return symptomsDiff
        <|endoftext|>