PageRenderTime 112ms CodeModel.GetById 35ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/galaxy/webapps/community/controllers/repository.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 1144 lines | 1081 code | 7 blank | 56 comment | 56 complexity | aa433671131278cfec4260685f873f4f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1import os, logging, urllib, ConfigParser, tempfile, shutil
  2from time import strftime
  3from datetime import date, datetime
  4from galaxy import util
  5from galaxy.datatypes.checkers import *
  6from galaxy.web.base.controller import *
  7from galaxy.web.form_builder import CheckboxField
  8from galaxy.webapps.community import model
  9from galaxy.webapps.community.model import directory_hash_id
 10from galaxy.web.framework.helpers import time_ago, iff, grids
 11from galaxy.util.json import from_json_string, to_json_string
 12from galaxy.model.orm import *
 13from common import *
 14from mercurial import hg, ui, patch, commands
 15
 16log = logging.getLogger( __name__ )
 17
 18# Characters that must be html escaped
 19MAPPED_CHARS = { '>' :'>', 
 20                 '<' :'&lt;',
 21                 '"' : '&quot;',
 22                 '&' : '&amp;',
 23                 '\'' : '&apos;' }
 24MAX_CONTENT_SIZE = 32768
 25VALID_CHARS = set( string.letters + string.digits + "'\"-=_.()/+*^,:?!#[]%\\$@;{}" )
 26VALID_REPOSITORYNAME_RE = re.compile( "^[a-z0-9\_]+$" )
 27    
 28class CategoryListGrid( grids.Grid ):
 29    class NameColumn( grids.TextColumn ):
 30        def get_value( self, trans, grid, category ):
 31            return category.name
 32    class DescriptionColumn( grids.TextColumn ):
 33        def get_value( self, trans, grid, category ):
 34            return category.description
 35    class RepositoriesColumn( grids.TextColumn ):
 36        def get_value( self, trans, grid, category ):
 37            if category.repositories:
 38                viewable_repositories = 0
 39                for rca in category.repositories:
 40                    viewable_repositories += 1
 41                return viewable_repositories
 42            return 0
 43
 44    # Grid definition
 45    webapp = "community"
 46    title = "Categories"
 47    model_class = model.Category
 48    template='/webapps/community/category/grid.mako'
 49    default_sort_key = "name"
 50    columns = [
 51        NameColumn( "Name",
 52                    key="Category.name",
 53                    link=( lambda item: dict( operation="repositories_by_category", id=item.id, webapp="community" ) ),
 54                    attach_popup=False ),
 55        DescriptionColumn( "Description",
 56                           key="Category.description",
 57                           attach_popup=False ),
 58        # Columns that are valid for filtering but are not visible.
 59        RepositoriesColumn( "Repositories",
 60                            model_class=model.Repository,
 61                            attach_popup=False )
 62    ]
 63    # Override these
 64    default_filter = {}
 65    global_actions = []
 66    operations = []
 67    standard_filters = []
 68    num_rows_per_page = 50
 69    preserve_state = False
 70    use_paging = True
 71
 72class RepositoryListGrid( grids.Grid ):
 73    class NameColumn( grids.TextColumn ):
 74        def get_value( self, trans, grid, repository ):
 75            return repository.name
 76    class RevisionColumn( grids.GridColumn ):
 77        def __init__( self, col_name ):
 78            grids.GridColumn.__init__( self, col_name )
 79        def get_value( self, trans, grid, repository ):
 80            """
 81            Display a SelectField whose options are the changeset_revision
 82            strings of all downloadable_revisions of this repository.
 83            """
 84            select_field = build_changeset_revision_select_field( trans, repository )
 85            if len( select_field.options ) > 1:
 86                return select_field.get_html()
 87            return repository.revision
 88    class DescriptionColumn( grids.TextColumn ):
 89        def get_value( self, trans, grid, repository ):
 90            return repository.description
 91    class CategoryColumn( grids.TextColumn ):
 92        def get_value( self, trans, grid, repository ):
 93            rval = '<ul>'
 94            if repository.categories:
 95                for rca in repository.categories:
 96                    rval += '<li><a href="browse_repositories?operation=repositories_by_category&id=%s&webapp=community">%s</a></li>' \
 97                        % ( trans.security.encode_id( rca.category.id ), rca.category.name )
 98            else:
 99                rval += '<li>not set</li>'
100            rval += '</ul>'
101            return rval
102    class RepositoryCategoryColumn( grids.GridColumn ):
103        def filter( self, trans, user, query, column_filter ):
104            """Modify query to filter by category."""
105            if column_filter == "All":
106                return query
107            return query.filter( model.Category.name == column_filter )
108    class UserColumn( grids.TextColumn ):
109        def get_value( self, trans, grid, repository ):
110            if repository.user:
111                return repository.user.username
112            return 'no user'
113    class EmailColumn( grids.TextColumn ):
114        def filter( self, trans, user, query, column_filter ):
115            if column_filter == 'All':
116                return query
117            return query.filter( and_( model.Repository.table.c.user_id == model.User.table.c.id,
118                                       model.User.table.c.email == column_filter ) )
119    class EmailAlertsColumn( grids.TextColumn ):
120        def get_value( self, trans, grid, repository ):
121            if trans.user and repository.email_alerts and trans.user.email in from_json_string( repository.email_alerts ):
122                return 'yes'
123            return ''
124    # Grid definition
125    title = "Repositories"
126    model_class = model.Repository
127    template='/webapps/community/repository/grid.mako'
128    default_sort_key = "name"
129    columns = [
130        NameColumn( "Name",
131                    key="name",
132                    link=( lambda item: dict( operation="view_or_manage_repository",
133                                              id=item.id,
134                                              webapp="community" ) ),
135                    attach_popup=True ),
136        DescriptionColumn( "Synopsis",
137                           key="description",
138                           attach_popup=False ),
139        RevisionColumn( "Revision" ),
140        CategoryColumn( "Category",
141                        model_class=model.Category,
142                        key="Category.name",
143                        attach_popup=False ),
144        UserColumn( "Owner",
145                     model_class=model.User,
146                     link=( lambda item: dict( operation="repositories_by_user", id=item.id, webapp="community" ) ),
147                     attach_popup=False,
148                     key="User.username" ),
149        grids.CommunityRatingColumn( "Average Rating", key="rating" ),
150        EmailAlertsColumn( "Alert", attach_popup=False ),
151        # Columns that are valid for filtering but are not visible.
152        EmailColumn( "Email",
153                     model_class=model.User,
154                     key="email",
155                     visible=False ),
156        RepositoryCategoryColumn( "Category",
157                                  model_class=model.Category,
158                                  key="Category.name",
159                                  visible=False ),
160        grids.DeletedColumn( "Deleted",
161                             key="deleted",
162                             visible=False,
163                             filterable="advanced" )
164    ]
165    columns.append( grids.MulticolFilterColumn( "Search repository name, description", 
166                                                cols_to_filter=[ columns[0], columns[1] ],
167                                                key="free-text-search",
168                                                visible=False,
169                                                filterable="standard" ) )
170    operations = [ grids.GridOperation( "Receive email alerts",
171                                        allow_multiple=False,
172                                        condition=( lambda item: not item.deleted ),
173                                        async_compatible=False ) ]
174    standard_filters = []
175    default_filter = dict( deleted="False" )
176    num_rows_per_page = 50
177    preserve_state = False
178    use_paging = True
179    def build_initial_query( self, trans, **kwd ):
180        return trans.sa_session.query( self.model_class ) \
181                               .join( model.User.table ) \
182                               .outerjoin( model.RepositoryCategoryAssociation.table ) \
183                               .outerjoin( model.Category.table )
184
185class DownloadableRepositoryListGrid( RepositoryListGrid ):
186    class RevisionColumn( grids.GridColumn ):
187        def __init__( self, col_name ):
188            grids.GridColumn.__init__( self, col_name )
189        def get_value( self, trans, grid, repository ):
190            """
191            Display a SelectField whose options are the changeset_revision
192            strings of all downloadable_revisions of this repository.
193            """
194            select_field = build_changeset_revision_select_field( trans, repository )
195            if len( select_field.options ) > 1:
196                return select_field.get_html()
197            return repository.revision
198    title = "Downloadable repositories"
199    columns = [
200        RepositoryListGrid.NameColumn( "Name",
201                                       key="name",
202                                       attach_popup=True ),
203        RepositoryListGrid.DescriptionColumn( "Synopsis",
204                                              key="description",
205                                              attach_popup=False ),
206        RevisionColumn( "Revision" ),
207        RepositoryListGrid.UserColumn( "Owner",
208                                       model_class=model.User,
209                                       attach_popup=False,
210                                       key="User.username" )
211    ]
212    columns.append( grids.MulticolFilterColumn( "Search repository name, description", 
213                                                cols_to_filter=[ columns[0], columns[1] ],
214                                                key="free-text-search",
215                                                visible=False,
216                                                filterable="standard" ) )
217    operations = []
218    def build_initial_query( self, trans, **kwd ):
219        return trans.sa_session.query( self.model_class ) \
220                               .join( model.RepositoryMetadata.table ) \
221                               .join( model.User.table )
222
223class RepositoryController( BaseUIController, ItemRatings ):
224
225    downloadable_repository_list_grid = DownloadableRepositoryListGrid()
226    repository_list_grid = RepositoryListGrid()
227    category_list_grid = CategoryListGrid()
228
229    @web.expose
230    def index( self, trans, **kwd ):
231        params = util.Params( kwd )
232        message = util.restore_text( params.get( 'message', ''  ) )
233        status = params.get( 'status', 'done' )
234        return trans.fill_template( '/webapps/community/index.mako', message=message, status=status )
235    @web.expose
236    def browse_categories( self, trans, **kwd ):
237        if 'f-free-text-search' in kwd:
238            # Trick to enable searching repository name, description from the CategoryListGrid.
239            # What we've done is rendered the search box for the RepositoryListGrid on the grid.mako
240            # template for the CategoryListGrid.  See ~/templates/webapps/community/category/grid.mako.
241            # Since we are searching repositories and not categories, redirect to browse_repositories().
242            if 'id' in kwd and 'f-free-text-search' in kwd and kwd[ 'id' ] == kwd[ 'f-free-text-search' ]:
243                # The value of 'id' has been set to the search string, which is a repository name.
244                # We'll try to get the desired encoded repository id to pass on.
245                try:
246                    repository = get_repository_by_name( trans, kwd[ 'id' ] )
247                    kwd[ 'id' ] = trans.security.encode_id( repository.id )
248                except:
249                    pass
250            return self.browse_repositories( trans, **kwd )
251        if 'operation' in kwd:
252            operation = kwd['operation'].lower()
253            if operation in [ "repositories_by_category", "repositories_by_user" ]:
254                # Eliminate the current filters if any exist.
255                for k, v in kwd.items():
256                    if k.startswith( 'f-' ):
257                        del kwd[ k ]
258                return trans.response.send_redirect( web.url_for( controller='repository',
259                                                                  action='browse_repositories',
260                                                                  **kwd ) )
261        # Render the list view
262        return self.category_list_grid( trans, **kwd )
263    @web.expose
264    def browse_downloadable_repositories( self, trans, **kwd ):
265        # Set the toolshedgalaxyurl cookie so we can get back
266        # to the calling local Galaxy instance.
267        galaxy_url = kwd.get( 'galaxy_url', None )
268        if galaxy_url:
269            trans.set_cookie( galaxy_url, name='toolshedgalaxyurl' )
270        repository_id = kwd.get( 'id', None )
271        if 'operation' in kwd:
272            operation = kwd[ 'operation' ].lower()
273            if operation == "preview_tools_in_changeset":
274                repository = get_repository( trans, repository_id )
275                return trans.response.send_redirect( web.url_for( controller='repository',
276                                                                  action='preview_tools_in_changeset',
277                                                                  repository_id=repository_id,
278                                                                  changeset_revision=repository.tip ) )
279
280        # The changeset_revision_select_field in the RepositoryListGrid performs a refresh_on_change
281        # which sends in request parameters like changeset_revison_1, changeset_revision_2, etc.  One
282        # of the many select fields on the grid performed the refresh_on_change, so we loop through 
283        # all of the received values to see which value is not the repository tip.  If we find it, we
284        # know the refresh_on_change occurred, and we have the necessary repository id and change set
285        # revision to pass on.
286        for k, v in kwd.items():
287            changset_revision_str = 'changeset_revision_'
288            if k.startswith( changset_revision_str ):
289                repository_id = trans.security.encode_id( int( k.lstrip( changset_revision_str ) ) )
290                repository = get_repository( trans, repository_id )
291                if repository.tip != v:
292                    return trans.response.send_redirect( web.url_for( controller='repository',
293                                                                      action='preview_tools_in_changeset',
294                                                                      repository_id=trans.security.encode_id( repository.id ),
295                                                                      changeset_revision=v ) )
296        url_args = dict( action='browse_downloadable_repositories',
297                         operation='preview_tools_in_changeset',
298                         repository_id=repository_id )
299        self.downloadable_repository_list_grid.operations = [ grids.GridOperation( "Preview and install tools",
300                                                                                   url_args=url_args,
301                                                                                   allow_multiple=False,
302                                                                                   async_compatible=False ) ]
303
304        # Render the list view
305        return self.downloadable_repository_list_grid( trans, **kwd )
306    @web.expose
307    def preview_tools_in_changeset( self, trans, repository_id, **kwd ):
308        params = util.Params( kwd )
309        message = util.restore_text( params.get( 'message', '' ) )
310        status = params.get( 'status', 'done' )
311        repository = get_repository( trans, repository_id )
312        changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
313        repository_metadata = get_repository_metadata_by_changeset_revision( trans, repository_id, changeset_revision )
314        if repository_metadata:
315            metadata = repository_metadata.metadata
316        else:
317            metadata = None
318        revision_label = get_revision_label( trans, repository, changeset_revision )
319        changeset_revision_select_field = build_changeset_revision_select_field( trans,
320                                                                                 repository,
321                                                                                 selected_value=changeset_revision,
322                                                                                 add_id_to_name=False )
323        return trans.fill_template( '/webapps/community/repository/preview_tools_in_changeset.mako',
324                                    repository=repository,
325                                    changeset_revision=changeset_revision,
326                                    revision_label=revision_label,
327                                    changeset_revision_select_field=changeset_revision_select_field,
328                                    metadata=metadata,
329                                    display_for_install=True,
330                                    message=message,
331                                    status=status )
332    @web.expose
333    def install_repository_revision( self, trans, repository_id, **kwd ):
334        params = util.Params( kwd )
335        message = util.restore_text( params.get( 'message', ''  ) )
336        status = params.get( 'status', 'done' )
337        galaxy_url = trans.get_cookie( name='toolshedgalaxyurl' )
338        repository = get_repository( trans, repository_id )
339        changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
340        # Redirect back to local Galaxy to perform install.
341        tool_shed_url = trans.request.host
342        repository_clone_url = generate_clone_url( trans, repository_id )
343        # TODO: support https in the following url.
344        url = 'http://%s/admin/install_tool_shed_repository?tool_shed_url=%s&repository_name=%s&repository_clone_url=%s&changeset_revision=%s' % \
345            ( galaxy_url, tool_shed_url, repository.name, repository_clone_url, changeset_revision )
346        return trans.response.send_redirect( url )
347    @web.expose
348    def browse_repositories( self, trans, **kwd ):
349        # We add params to the keyword dict in this method in order to rename the param
350        # with an "f-" prefix, simulating filtering by clicking a search link.  We have
351        # to take this approach because the "-" character is illegal in HTTP requests.
352        if 'operation' in kwd:
353            operation = kwd['operation'].lower()
354            if operation == "view_or_manage_repository":
355                repository_id = kwd[ 'id' ]
356                repository = get_repository( trans, repository_id )
357                is_admin = trans.user_is_admin()
358                if is_admin or repository.user == trans.user:
359                    return trans.response.send_redirect( web.url_for( controller='repository',
360                                                                      action='manage_repository',
361                                                                      **kwd ) )
362                else:
363                    return trans.response.send_redirect( web.url_for( controller='repository',
364                                                                      action='view_repository',
365                                                                      **kwd ) )
366            elif operation == "edit_repository":
367                return trans.response.send_redirect( web.url_for( controller='repository',
368                                                                  action='edit_repository',
369                                                                  **kwd ) )
370            elif operation == "repositories_by_user":
371                # Eliminate the current filters if any exist.
372                for k, v in kwd.items():
373                    if k.startswith( 'f-' ):
374                        del kwd[ k ]
375                if 'user_id' in kwd:
376                    user = get_user( trans, kwd[ 'user_id' ] )
377                    kwd[ 'f-email' ] = user.email
378                    del kwd[ 'user_id' ]
379                else:
380                    # The received id is the repository id, so we need to get the id of the user
381                    # that uploaded the repository.
382                    repository_id = kwd.get( 'id', None )
383                    repository = get_repository( trans, repository_id )
384                    kwd[ 'f-email' ] = repository.user.email
385            elif operation == "my_repositories":
386                # Eliminate the current filters if any exist.
387                for k, v in kwd.items():
388                    if k.startswith( 'f-' ):
389                        del kwd[ k ]
390                kwd[ 'f-email' ] = trans.user.email
391            elif operation == "repositories_by_category":
392                # Eliminate the current filters if any exist.
393                for k, v in kwd.items():
394                    if k.startswith( 'f-' ):
395                        del kwd[ k ]
396                category_id = kwd.get( 'id', None )
397                category = get_category( trans, category_id )
398                kwd[ 'f-Category.name' ] = category.name
399            elif operation == "receive email alerts":
400                if trans.user:
401                    if kwd[ 'id' ]:
402                        return trans.response.send_redirect( web.url_for( controller='repository',
403                                                                          action='set_email_alerts',
404                                                                          **kwd ) )
405                else:
406                    kwd[ 'message' ] = 'You must be logged in to set email alerts.'
407                    kwd[ 'status' ] = 'error'
408                    del kwd[ 'operation' ]
409        # The changeset_revision_select_field in the RepositoryListGrid performs a refresh_on_change
410        # which sends in request parameters like changeset_revison_1, changeset_revision_2, etc.  One
411        # of the many select fields on the grid performed the refresh_on_change, so we loop through 
412        # all of the received values to see which value is not the repository tip.  If we find it, we
413        # know the refresh_on_change occurred, and we have the necessary repository id and change set
414        # revision to pass on.
415        for k, v in kwd.items():
416            changset_revision_str = 'changeset_revision_'
417            if k.startswith( changset_revision_str ):
418                repository_id = trans.security.encode_id( int( k.lstrip( changset_revision_str ) ) )
419                repository = get_repository( trans, repository_id )
420                if repository.tip != v:
421                    return trans.response.send_redirect( web.url_for( controller='repository',
422                                                                      action='browse_repositories',
423                                                                      operation='view_or_manage_repository',
424                                                                      id=trans.security.encode_id( repository.id ),
425                                                                      changeset_revision=v ) )
426        # Render the list view
427        return self.repository_list_grid( trans, **kwd )
428    @web.expose
429    def create_repository( self, trans, **kwd ):
430        params = util.Params( kwd )
431        message = util.restore_text( params.get( 'message', ''  ) )
432        status = params.get( 'status', 'done' )
433        categories = get_categories( trans )
434        if not categories:
435            message = 'No categories have been configured in this instance of the Galaxy Tool Shed.  ' + \
436                'An administrator needs to create some via the Administrator control panel before creating repositories.',
437            status = 'error'
438            return trans.response.send_redirect( web.url_for( controller='repository',
439                                                              action='browse_repositories',
440                                                              message=message,
441                                                              status=status ) )
442        name = util.restore_text( params.get( 'name', '' ) )
443        description = util.restore_text( params.get( 'description', '' ) )
444        long_description = util.restore_text( params.get( 'long_description', '' ) )
445        category_ids = util.listify( params.get( 'category_id', '' ) )
446        selected_categories = [ trans.security.decode_id( id ) for id in category_ids ]
447        if params.get( 'create_repository_button', False ):
448            error = False
449            message = self.__validate_repository_name( name, trans.user )
450            if message:
451                error = True
452            if not description:
453                message = 'Enter a description.'
454                error = True
455            if not error:
456                # Add the repository record to the db
457                repository = trans.app.model.Repository( name=name,
458                                                         description=description,
459                                                         long_description=long_description,
460                                                         user_id=trans.user.id )
461                # Flush to get the id
462                trans.sa_session.add( repository )
463                trans.sa_session.flush()
464                # Determine the repository's repo_path on disk
465                dir = os.path.join( trans.app.config.file_path, *directory_hash_id( repository.id ) )
466                # Create directory if it does not exist
467                if not os.path.exists( dir ):
468                    os.makedirs( dir )
469                # Define repo name inside hashed directory
470                repository_path = os.path.join( dir, "repo_%d" % repository.id )
471                # Create local repository directory
472                if not os.path.exists( repository_path ):
473                    os.makedirs( repository_path )
474                # Create the local repository
475                repo = hg.repository( get_configured_ui(), repository_path, create=True )
476                # Add an entry in the hgweb.config file for the local repository
477                # This enables calls to repository.repo_path
478                self.__add_hgweb_config_entry( trans, repository, repository_path )
479                # Create a .hg/hgrc file for the local repository
480                self.__create_hgrc_file( repository )
481                flush_needed = False
482                if category_ids:
483                    # Create category associations
484                    for category_id in category_ids:
485                        category = trans.app.model.Category.get( trans.security.decode_id( category_id ) )
486                        rca = trans.app.model.RepositoryCategoryAssociation( repository, category )
487                        trans.sa_session.add( rca )
488                        flush_needed = True
489                if flush_needed:
490                    trans.sa_session.flush()
491                message = "Repository '%s' has been created." % repository.name
492                trans.response.send_redirect( web.url_for( controller='repository',
493                                                           action='view_repository',
494                                                           message=message,
495                                                           id=trans.security.encode_id( repository.id ) ) )
496        return trans.fill_template( '/webapps/community/repository/create_repository.mako',
497                                    name=name,
498                                    description=description,
499                                    long_description=long_description,
500                                    selected_categories=selected_categories,
501                                    categories=categories,
502                                    message=message,
503                                    status=status )
504    def __validate_repository_name( self, name, user ):
505        # Repository names must be unique for each user, must be at least four characters
506        # in length and must contain only lower-case letters, numbers, and the '_' character.
507        if name in [ 'None', None, '' ]:
508            return 'Enter the required repository name.'
509        for repository in user.active_repositories:
510            if repository.name == name:
511                return "You already have a repository named '%s', so choose a different name." % name
512        if len( name ) < 4:
513            return "Repository names must be at least 4 characters in length."
514        if len( name ) > 80:
515            return "Repository names cannot be more than 80 characters in length."
516        if not( VALID_REPOSITORYNAME_RE.match( name ) ):
517            return "Repository names must contain only lower-case letters, numbers and underscore '_'."
518        return ''
519    def __make_hgweb_config_copy( self, trans, hgweb_config ):
520        # Make a backup of the hgweb.config file
521        today = date.today()
522        backup_date = today.strftime( "%Y_%m_%d" )
523        hgweb_config_copy = '%s/hgweb.config_%s_backup' % ( trans.app.config.root, backup_date )
524        shutil.copy( os.path.abspath( hgweb_config ), os.path.abspath( hgweb_config_copy ) )
525    def __add_hgweb_config_entry( self, trans, repository, repository_path ):
526        # Add an entry in the hgweb.config file for a new repository.
527        # An entry looks something like:
528        # repos/test/mira_assembler = database/community_files/000/repo_123.
529        hgweb_config = "%s/hgweb.config" %  trans.app.config.root
530        # Make a backup of the hgweb.config file since we're going to be changing it.
531        self.__make_hgweb_config_copy( trans, hgweb_config )
532        entry = "repos/%s/%s = %s" % ( repository.user.username, repository.name, repository_path.lstrip( './' ) )
533        if os.path.exists( hgweb_config ):
534            output = open( hgweb_config, 'a' )
535        else:
536            output = open( hgweb_config, 'w' )
537            output.write( '[paths]\n' )
538        output.write( "%s\n" % entry )
539        output.close()
540    def __change_hgweb_config_entry( self, trans, repository, old_repository_name, new_repository_name ):
541        # Change an entry in the hgweb.config file for a repository.  This only happens when
542        # the owner changes the name of the repository.  An entry looks something like:
543        # repos/test/mira_assembler = database/community_files/000/repo_123.
544        hgweb_config = "%s/hgweb.config" % trans.app.config.root
545        # Make a backup of the hgweb.config file since we're going to be changing it.
546        self.__make_hgweb_config_copy( trans, hgweb_config )
547        repo_dir = repository.repo_path
548        old_lhs = "repos/%s/%s" % ( repository.user.username, old_repository_name )
549        old_entry = "%s = %s" % ( old_lhs, repo_dir )
550        new_entry = "repos/%s/%s = %s\n" % ( repository.user.username, new_repository_name, repo_dir )
551        tmp_fd, tmp_fname = tempfile.mkstemp()
552        new_hgweb_config = open( tmp_fname, 'wb' )
553        for i, line in enumerate( open( hgweb_config ) ):
554            if line.startswith( old_lhs ):
555                new_hgweb_config.write( new_entry )
556            else:
557                new_hgweb_config.write( line )
558        shutil.move( tmp_fname, os.path.abspath( hgweb_config ) )
559    def __create_hgrc_file( self, repository ):
560        # At this point, an entry for the repository is required to be in the hgweb.config
561        # file so we can call repository.repo_path.
562        # Create a .hg/hgrc file that looks something like this:
563        # [web]
564        # allow_push = test
565        # name = convert_characters1
566        # push_ssl = False
567        # Since we support both http and https, we set push_ssl to False to override
568        # the default (which is True) in the mercurial api.
569        repo = hg.repository( get_configured_ui(), path=repository.repo_path )
570        fp = repo.opener( 'hgrc', 'wb' )
571        fp.write( '[paths]\n' )
572        fp.write( 'default = .\n' )
573        fp.write( 'default-push = .\n' )
574        fp.write( '[web]\n' )
575        fp.write( 'allow_push = %s\n' % repository.user.username )
576        fp.write( 'name = %s\n' % repository.name )
577        fp.write( 'push_ssl = false\n' )
578        fp.close()
579    @web.expose
580    def browse_repository( self, trans, id, **kwd ):
581        params = util.Params( kwd )
582        message = util.restore_text( params.get( 'message', ''  ) )
583        status = params.get( 'status', 'done' )
584        commit_message = util.restore_text( params.get( 'commit_message', 'Deleted selected files' ) )
585        repository = get_repository( trans, id )
586        repo = hg.repository( get_configured_ui(), repository.repo_path )
587        current_working_dir = os.getcwd()
588        # Update repository files for browsing.
589        update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
590        is_malicious = change_set_is_malicious( trans, id, repository.tip )
591        return trans.fill_template( '/webapps/community/repository/browse_repository.mako',
592                                    repo=repo,
593                                    repository=repository,
594                                    commit_message=commit_message,
595                                    is_malicious=is_malicious,
596                                    message=message,
597                                    status=status )
598    @web.expose
599    def contact_owner( self, trans, id, **kwd ):
600        params = util.Params( kwd )
601        message = util.restore_text( params.get( 'message', ''  ) )
602        status = params.get( 'status', 'done' )
603        repository = get_repository( trans, id )
604        if trans.user and trans.user.email:
605            return trans.fill_template( "/webapps/community/repository/contact_owner.mako",
606                                        repository=repository,
607                                        message=message,
608                                        status=status )
609        else:
610            # Do all we can to eliminate spam.
611            return trans.show_error_message( "You must be logged in to contact the owner of a repository." )
612    @web.expose
613    def send_to_owner( self, trans, id, message='' ):
614        repository = get_repository( trans, id )
615        if not message:
616            message = 'Enter a message'
617            status = 'error'
618        elif trans.user and trans.user.email:
619            smtp_server = trans.app.config.smtp_server
620            from_address = trans.app.config.email_from
621            if smtp_server is None or from_address is None:
622                return trans.show_error_message( "Mail is not configured for this Galaxy tool shed instance" )
623            to_address = repository.user.email
624            # Get the name of the server hosting the tool shed instance.
625            host = trans.request.host
626            # Build the email message
627            body = string.Template( contact_owner_template ) \
628                .safe_substitute( username=trans.user.username,
629                                  repository_name=repository.name,
630                                  email=trans.user.email,
631                                  message=message,
632                                  host=host )
633            subject = "Regarding your tool shed repository named %s" % repository.name
634            # Send it
635            try:
636                util.send_mail( from_address, to_address, subject, body, trans.app.config )
637                message = "Your message has been sent"
638                status = "done"
639            except Exception, e:
640                message = "An error occurred sending your message by email: %s" % str( e )
641                status = "error"
642        else:
643            # Do all we can to eliminate spam.
644            return trans.show_error_message( "You must be logged in to contact the owner of a repository." )
645        return trans.response.send_redirect( web.url_for( controller='repository',
646                                                          action='contact_owner',
647                                                          id=id,
648                                                          message=message,
649                                                          status=status ) )
650    @web.expose
651    def select_files_to_delete( self, trans, id, **kwd ):
652        params = util.Params( kwd )
653        message = util.restore_text( params.get( 'message', '' ) )
654        status = params.get( 'status', 'done' )
655        commit_message = util.restore_text( params.get( 'commit_message', 'Deleted selected files' ) )
656        repository = get_repository( trans, id )
657        repo_dir = repository.repo_path
658        repo = hg.repository( get_configured_ui(), repo_dir )
659        selected_files_to_delete = util.restore_text( params.get( 'selected_files_to_delete', '' ) )
660        if params.get( 'select_files_to_delete_button', False ):
661            if selected_files_to_delete:
662                selected_files_to_delete = selected_files_to_delete.split( ',' )
663                current_working_dir = os.getcwd()
664                # Get the current repository tip.
665                tip = repository.tip
666                for selected_file in selected_files_to_delete:
667                    try:
668                        commands.remove( repo.ui, repo, repo_file, force=True )
669                    except Exception, e:
670                        # I never have a problem with commands.remove on a Mac, but in the test/production
671                        # tool shed environment, it throws an exception whenever I delete all files from a
672                        # repository.  If this happens, we'll try the following.
673                        relative_selected_file = selected_file.split( 'repo_%d' % repository.id )[1].lstrip( '/' )
674                        repo.dirstate.remove( relative_selected_file )
675                        repo.dirstate.write()
676                        absolute_selected_file = os.path.abspath( selected_file )
677                        if os.path.isdir( absolute_selected_file ):
678                            try:
679                                os.rmdir( absolute_selected_file )
680                            except OSError, e:
681                                # The directory is not empty
682                                pass
683                        elif os.path.isfile( absolute_selected_file ):
684                            os.remove( absolute_selected_file )
685                            dir = os.path.split( absolute_selected_file )[0]
686                            try:
687                                os.rmdir( dir )
688                            except OSError, e:
689                                # The directory is not empty
690                                pass
691                # Commit the change set.
692                if not commit_message:
693                    commit_message = 'Deleted selected files'
694                try:
695                    commands.commit( repo.ui, repo, repo_dir, user=trans.user.username, message=commit_message )
696                except Exception, e:
697                    # I never have a problem with commands.commit on a Mac, but in the test/production
698                    # tool shed environment, it occasionally throws a "TypeError: array item must be char"
699                    # exception.  If this happens, we'll try the following.
700                    repo.dirstate.write()
701                    repo.commit( user=trans.user.username, text=commit_message )
702                handle_email_alerts( trans, repository )
703                # Update the repository files for browsing.
704                update_for_browsing( trans, repository, current_working_dir, commit_message=commit_message )
705                # Get the new repository tip.
706                repo = hg.repository( get_configured_ui(), repo_dir )
707                if tip != repository.tip:
708                    message = "The selected files were deleted from the repository."
709                else:
710                    message = 'No changes to repository.'
711                # Set metadata on the repository tip
712                error_message, status = set_repository_metadata( trans, id, repository.tip, **kwd )
713                if error_message:
714                    message = '%s<br/>%s' % ( message, error_message )
715                    return trans.response.send_redirect( web.url_for( controller='repository',
716                                                                      action='manage_repository',
717                                                                      id=id,
718                                                                      message=message,
719                                                                      status=status ) )
720            else:
721                message = "Select at least 1 file to delete from the repository before clicking <b>Delete selected files</b>."
722                status = "error"
723        is_malicious = change_set_is_malicious( trans, id, repository.tip )
724        return trans.fill_template( '/webapps/community/repository/browse_repository.mako',
725                                    repo=repo,
726                                    repository=repository,
727                                    commit_message=commit_message,
728                                    is_malicious=is_malicious,
729                                    message=message,
730                                    status=status )
731    @web.expose
732    def view_repository( self, trans, id, **kwd ):
733        params = util.Params( kwd )
734        message = util.restore_text( params.get( 'message', ''  ) )
735        status = params.get( 'status', 'done' )
736        repository = get_repository( trans, id )
737        repo = hg.repository( get_configured_ui(), repository.repo_path )
738        avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model )
739        changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
740        display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
741        alerts = params.get( 'alerts', '' )
742        alerts_checked = CheckboxField.is_checked( alerts )
743        if repository.email_alerts:
744            email_alerts = from_json_string( repository.email_alerts )
745        else:
746            email_alerts = []
747        user = trans.user
748        if user and params.get( 'receive_email_alerts_button', False ):
749            flush_needed = False
750            if alerts_checked:
751                if user.email not in email_alerts:
752                    email_alerts.append( user.email )
753                    repository.email_alerts = to_json_string( email_alerts )
754                    flush_needed = True
755            else:
756                if user.email in email_alerts:
757                    email_alerts.remove( user.email )
758                    repository.email_alerts = to_json_string( email_alerts )
759                    flush_needed = True
760            if flush_needed:
761                trans.sa_session.add( repository )
762                trans.sa_session.flush()
763        checked = alerts_checked or ( user and user.email in email_alerts )
764        alerts_check_box = CheckboxField( 'alerts', checked=checked )
765        changeset_revision_select_field = build_changeset_revision_select_field( trans,
766                                                                                 repository,
767                                                                                 selected_value=changeset_revision,
768                                                                                 add_id_to_name=False )
769        revision_label = get_revision_label( trans, repository, changeset_revision )
770        repository_metadata = get_repository_metadata_by_changeset_revision( trans, id, changeset_revision )
771        if repository_metadata:
772            metadata = repository_metadata.metadata
773        else:
774            metadata = None
775        is_malicious = change_set_is_malicious( trans, id, repository.tip )
776        if is_malicious:
777            if trans.app.security_agent.can_push( trans.user, repository ):
778                message += malicious_error_can_push
779            else:
780                message += malicious_error
781            status = 'error'
782        return trans.fill_template( '/webapps/community/repository/view_repository.mako',
783                                    repo=repo,
784                                    repository=repository,
785                                    metadata=metadata,
786                                    avg_rating=avg_rating,
787                                    display_reviews=display_reviews,
788                                    num_ratings=num_ratings,
789                                    alerts_check_box=alerts_check_box,
790                                    changeset_revision=changeset_revision,
791                                    changeset_revision_select_field=changeset_revision_select_field,
792                                    revision_label=revision_label,
793                                    is_malicious=is_malicious,
794                                    message=message,
795                                    status=status )
796    @web.expose
797    @web.require_login( "manage repository" )
798    def manage_repository( self, trans, id, **kwd ):
799        params = util.Params( kwd )
800        message = util.restore_text( params.get( 'message', ''  ) )
801        status = params.get( 'status', 'done' )
802        repository = get_repository( trans, id )
803        repo_dir = repository.repo_path
804        repo = hg.repository( get_configured_ui(), repo_dir )
805        repo_name = util.restore_text( params.get( 'repo_name', repository.name ) )
806        changeset_revision = util.restore_text( params.get( 'changeset_revision', repository.tip ) )
807        description = util.restore_text( params.get( 'description', repository.description ) )
808        long_description = util.restore_text( params.get( 'long_description', repository.long_description ) )
809        avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model )
810        display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) )
811        alerts = params.get( 'alerts', '' )
812        alerts_checked = CheckboxField.is_checked( alerts )
813        category_ids = util.listify( params.get( 'category_id', '' ) )
814        if repository.email_alerts:
815            email_alerts = from_json_string( repository.email_alerts )
816        else:
817            email_alerts = []
818        allow_push = params.get( 'allow_push', '' )
819        error = False
820        user = trans.user
821        if params.get( 'edit_repository_button', False ):
822            flush_needed = False
823            # TODO: add a can_manage in the security agent.
824            if user != repository.user:
825                message = "You are not the owner of this repository, so you cannot manage it."
826                status = error
827                return trans.response.send_redirect( web.url_for( controller='repository',
828                                                                  action='view_repository',
829                                                                  id=id,
830                                                                  message=message,
831                                                                  status=status ) )
832            if repo_name != repository.name:
833                message = self.__validate_repository_name( repo_name, user )
834                if message:
835                    error = True
836                else:
837                    self.__change_hgweb_config_entry( trans, repository, repository.name, repo_name )
838                    repository.name = repo_name
839                    flush_needed = True
840            if description != repository.description:
841                repository.description = description
842                flush_needed = True
843            if long_description != repository.long_description:
844                repository.long_description = long_description
845                flush_needed = True
846            if flush_needed:
847                trans.sa_session.add( repository )
848                trans.sa_session.flush()
849            message = "The repository information has been updated."
850        elif params.get( 'manage_categories_button', False ):
851            flush_needed = False
852            # Delete all currently existing categories.
853            for rca in repository.categories:
854                trans.sa_session.delete( rca )
855                trans.sa_session.flush()
856            if category_ids:
857                # Create category associations
858                for category_id in category_ids:
859                    category = trans.app.model.Category.get( trans.security.decode_id( category_id ) )
860                    rca = trans.app.model.RepositoryCategoryAssociation( repository, category )
861                    trans.sa_session.add( rca )
862                    trans.sa_session.flush()
863            message = "The repository information has been updated."
864        elif params.get( 'user_access_button', False ):
865            if allow_push not in [ 'none' ]:
866                remove_auth = params.get( 'remove_auth', '' )
867                if remove_auth:
868                    usernames = ''
869                else:
870                    user_ids = util.listify( allow_push )
871                    usernames = []
872                    for user_id in user_ids:
873                        user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
874                        usernames.append( user.username )
875                    usernames = ','.join( usernames )
876                repository.set_allow_push( usernames, remove_auth=

Large files files are truncated, but you can click here to view the full file