Index: /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/api.py
===================================================================
--- /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/api.py	(revision 2822)
+++ /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/api.py	(working copy)
@@ -3,7 +3,7 @@
 import os, shutil, time, re, mimetypes
 
 from trac.core import *
-from trac.config import Option
+from trac.config import Option, BoolOption
 from trac.wiki import wiki_to_html, wiki_to_oneliner
 from trac.util import format_datetime, pretty_timedelta, pretty_size
 from trac.util.text import unicode_unquote
@@ -46,6 +46,11 @@
       ' '.join(all_fields), 'List of downloads table fields that should'
       ' be visible to users on Downloads section.')
 
+    filename_key = BoolOption('downloads', 'filename_key', False, 
+                              'Use filenames as unique key for uploaded file. '
+                              +'This also has the effect that the download links'
+                              +' are generated with filenames and not with ids')
+
     # Get list functions.
 
     def get_versions(self, req, cursor):
@@ -96,6 +101,10 @@
             row['size'] = pretty_size(row['size'])
             row['time'] = pretty_timedelta(row['time'])
             row['count'] = row['count'] or 0
+            if self.filename_key:
+                row['dlname'] = row['file']
+            else:
+                row['dlname'] = row['id']
             downloads.append(row)
 
         # Replace field ids with apropriate objects.
@@ -394,6 +403,8 @@
         elif context == 'core':
             if action == 'get-file':
                 return ['get-file']
+            elif action == 'get-file-by-name':
+                return ['get-file-by-name']
             elif action == 'edit':
                 return ['description-edit', 'downloads-list']
             elif action == 'post-edit':
@@ -403,44 +414,56 @@
         else:
             pass
 
-    def _do_action(self, req, cursor, modes):
-        for mode in modes:
-            if mode == 'get-file':
-                req.perm.assert_permission('DOWNLOADS_VIEW')
+    def _do_get_file(self, req, cursor, download_id):
+        # Get download.
+        download = self.get_download(cursor, download_id)
 
-                # Get form values.
-                download_id = req.args.get('id') or 0
+        if download:
+            path = os.path.join(self.path, unicode(download['id']),
+              download['file'])
+            self.log.debug('path: %s' % (path,))
 
-                # Get download.
-                download = self.get_download(cursor, download_id)
+            # Increase downloads count.
+            new_download = {'count' : download['count'] + 1}
 
-                if download:
-                    path = os.path.join(self.path, unicode(download['id']),
-                      download['file'])
-                    self.log.debug('path: %s' % (path,))
+            # Edit download.
+            db = self.env.get_db_cnx()
+            cursor = db.cursor()
+            self.edit_download(cursor, download['id'], new_download)
+            db.commit()
 
-                    # Increase downloads count.
-                    new_download = {'count' : download['count'] + 1}
+            # Notify change listeners.
+            for listener in self.change_listeners:
+                listener.download_changed(new_download, download)
 
-                    # Edit download.
-                    db = self.env.get_db_cnx()
-                    cursor = db.cursor()
-                    self.edit_download(cursor, download['id'], new_download)
-                    db.commit()
+            # Return uploaded file to request.
+            self.log.debug(download['file'])
+            req.send_header('Content-Disposition',
+              'attachment;filename=%s' % (download['file']))
+            req.send_header('Content-Description',
+              download['description'])
+            req.send_file(path, mimetypes.guess_type(path)[0])
+        else:
+            raise TracError('File not found.')
 
-                    # Notify change listeners.
-                    for listener in self.change_listeners:
-                        listener.download_changed(new_download, download)
 
-                    # Return uploaded file to request.
-                    self.log.debug(download['file'])
-                    req.send_header('Content-Disposition',
-                      'attachment;filename=%s' % (download['file']))
-                    req.send_header('Content-Description',
-                      download['description'])
-                    req.send_file(path, mimetypes.guess_type(path)[0])
-                else:
-                    raise TracError('File not found.')
+    def _do_action(self, req, cursor, modes):
+        for mode in modes:
+            if mode == 'get-file-by-name':
+                req.perm.assert_permission('DOWNLOADS_VIEW')
+                filename = req.args.get('filename') or ''
+
+                download_id = self._get_id_from_filename(filename,cursor) 
+
+                self._do_get_file(req, cursor, download_id)
+                
+            elif mode == 'get-file':
+                req.perm.assert_permission('DOWNLOADS_VIEW')
+
+                # Get form values.
+                download_id = req.args.get('id') or 0
+                
+                self._do_get_file(req, cursor, download_id)
 
             elif mode == 'downloads-list':
                 req.perm.assert_permission('DOWNLOADS_VIEW')
@@ -502,6 +525,9 @@
 
                 # Get form values.
                 file, filename, file_size = self._get_file_from_req(req)
+                if self.filename_key and not self._is_unique_filename(filename, cursor):
+                    raise TracError("Filename should be unique (filename_key is set to true)")
+                
                 download = {'file' : filename,
                             'description' : req.args.get('description'),
                             'size' : file_size,
@@ -767,3 +793,23 @@
             raise TracError('Can\'t upload empty file.')
 
         return file.file, unicode_unquote(file.filename), size
+    
+    def _is_unique_filename(self, filename, cursor):
+        sql = "SELECT count(file) FROM download WHERE file = %s"
+        self.log.debug(sql % (filename,))
+        cursor.execute(sql, (filename,))
+        for row in cursor:
+            if row[0] == 0:
+                return True
+            else:
+                return False
+        raise TracError("Could not decide whether the filename %s is unique" % filename)
+        
+    def _get_id_from_filename(self, filename, cursor):
+        sql = "SELECT id FROM download WHERE file = %s"
+        self.log.debug(sql % (filename,))
+        cursor.execute(sql, (filename,))
+        for row in cursor:
+            return row[0]
+        raise TracError("Unknown file %s" % filename)
+    
\ No newline at end of file
Index: /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/core.py
===================================================================
--- /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/core.py	(revision 2822)
+++ /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/core.py	(working copy)
@@ -60,6 +60,11 @@
             req.args['action'] = 'get-file'
             req.args['id'] = match.group(1)
             return True
+        match = re.match(r'''^/downloads/([^/]+)$''', req.path_info)
+        if match:
+            req.args['action'] = 'get-file-by-name'
+            req.args['filename'] = match.group(1)
+            return True
         return False
 
     def process_request(self, req):
Index: /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/templates/downloads-list.cs
===================================================================
--- /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/templates/downloads-list.cs	(revision 2822)
+++ /home/gergely/eclipse33/trac/DownloadsPlugin/tracdownloads/templates/downloads-list.cs	(working copy)
@@ -91,7 +91,7 @@
               <?cs if:downloads.visible_fields.id ?>
                 <td class="id">
                   <div class="id">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.id ?>
                     </a>
                   </div>
@@ -101,7 +101,7 @@
               <?cs if:downloads.visible_fields.file ?>
                 <td class="file">
                   <div class="file">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.file ?>
                     </a>
                   </div>
@@ -119,7 +119,7 @@
               <?cs if:downloads.visible_fields.size ?>
                 <td class="size">
                   <div class="size">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.size ?>
                     </a>
                   </div>
@@ -129,7 +129,7 @@
               <?cs if:downloads.visible_fields.time ?>
                 <td class="time">
                   <div class="time">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.time ?>
                     </a>
                   </div>
@@ -139,7 +139,7 @@
               <?cs if:downloads.visible_fields.count ?>
                 <td class="count">
                   <div class="count">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.count ?>
                     </a>
                   </div>
@@ -149,7 +149,7 @@
               <?cs if:downloads.visible_fields.author ?>
                 <td class="author">
                   <div class="author">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.author ?>
                     </a>
                   </div>
@@ -159,7 +159,7 @@
               <?cs if:downloads.visible_fields.tags && downloads.has_tags ?>
                 <td class="tags">
                   <div class="tags">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.tags ?>
                     </a>
                   </div>
@@ -169,7 +169,7 @@
               <?cs if:downloads.visible_fields.component ?>
                 <td class="component">
                   <div class="component">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.component ?>
                     </a>
                   </div>
@@ -179,7 +179,7 @@
               <?cs if:downloads.visible_fields.version ?>
                 <td class="version">
                   <div class="version">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.version ?>
                     </a>
                   </div>
@@ -189,7 +189,7 @@
               <?cs if:downloads.visible_fields.architecture ?>
                 <td class="architecture">
                   <div class="architecture">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.architecture.name ?>
                     </a>
                   </div>
@@ -199,7 +199,7 @@
               <?cs if:downloads.visible_fields.platform ?>
                 <td class="platform">
                   <div class="platform">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.platform.name ?>
                     </a>
                   </div>
@@ -209,7 +209,7 @@
               <?cs if:downloads.visible_fields.type ?>
                 <td class="type">
                   <div class="type">
-                    <a href="<?cs var:downloads.href ?>/<?cs var:download.id ?>">
+                    <a href="<?cs var:downloads.href ?>/<?cs var:download.dlname ?>">
                       <?cs var:download.type.name ?>
                     </a>
                   </div>
