This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 096aaa843a2886b6af201be51b4848fa1a6ef81c
Author: Sanskriti Mohapatra <[email protected]>
AuthorDate: Thu Nov 19 19:55:54 2020 +0530

    [#8368] Snippets for the new Files App contribution
---
 Allura/allura/nf/allura/css/site_style.css         |   9 +
 Allura/allura/templates/jinja_master/master.html   |  19 +
 Allura/allura/templates/jinja_master/nav_menu.html |   3 +
 ForgeFiles/forgefiles/__init__.py                  |  16 +
 ForgeFiles/forgefiles/files_main.py                | 660 +++++++++++++++++++++
 ForgeFiles/forgefiles/model/__init__.py            |  18 +
 ForgeFiles/forgefiles/model/files.py               | 247 ++++++++
 ForgeFiles/forgefiles/nf/files/css/files.css       |  86 +++
 ForgeFiles/forgefiles/nf/files/js/create_folder.js |  77 +++
 ForgeFiles/forgefiles/nf/files/js/delete.js        |  33 ++
 ForgeFiles/forgefiles/nf/files/js/edit.js          |  82 +++
 ForgeFiles/forgefiles/nf/files/js/files.js         |  94 +++
 .../forgefiles/nf/files/js/publish_folder.js       |  46 ++
 ForgeFiles/forgefiles/nf/files/js/upload_files.js  |  36 ++
 ForgeFiles/forgefiles/templates/create_folder.html |  58 ++
 ForgeFiles/forgefiles/templates/delete.html        |  62 ++
 ForgeFiles/forgefiles/templates/edit.html          |  78 +++
 ForgeFiles/forgefiles/templates/files.html         | 193 ++++++
 ForgeFiles/forgefiles/templates/mail.html          |  34 ++
 .../forgefiles/templates/publish_folder.html       |  69 +++
 ForgeFiles/forgefiles/templates/upload_file.html   |  55 ++
 ForgeFiles/forgefiles/tests/__init__.py            |  17 +
 ForgeFiles/forgefiles/tests/functional/__init__.py |  17 +
 .../forgefiles/tests/functional/test_root.py       | 144 +++++
 ForgeFiles/forgefiles/tests/model/__init__.py      |  51 ++
 ForgeFiles/forgefiles/tests/model/test_files.py    |  54 ++
 ForgeFiles/forgefiles/tests/test_files_roles.py    |  53 ++
 ForgeFiles/setup.py                                |  32 +
 28 files changed, 2343 insertions(+)

diff --git a/Allura/allura/nf/allura/css/site_style.css 
b/Allura/allura/nf/allura/css/site_style.css
index 77e4c7c..1f8b4d0 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -4082,3 +4082,12 @@ Nav bar styles moved out of navbar.css since they are 
more stylistic than layout
 .user-card .card-right .subitem a{
   color: #00b3ff;
 }
+
+/* Download button style for Files plugin*/
+#download_button{
+  float:right !important;
+  cursor: pointer !important;
+  height: 3em !important;
+  color:#0077aa !important;
+  display: none;
+}
diff --git a/Allura/allura/templates/jinja_master/master.html 
b/Allura/allura/templates/jinja_master/master.html
index 3892f64..101ee23 100644
--- a/Allura/allura/templates/jinja_master/master.html
+++ b/Allura/allura/templates/jinja_master/master.html
@@ -45,6 +45,7 @@
 {% do g.resource_manager.register_widgets(c) %}
 
 
+
 {# paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ #}
 <!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
 <!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
@@ -199,6 +200,24 @@
         });
 
     });
+
+$(document).ready(function(){
+    $.ajax({
+    url:'/p/{{c.project.shortname}}/files/project_file/',
+    type:'GET',
+    success: function(res){
+        var count = parseInt(res);
+        if (count != 0){
+            $('#download_button').show();
+        }
+        else{
+            $('#download_button').hide();
+        }
+    }
+});
+
+});
+
 </script>
 </body>
 </html>
diff --git a/Allura/allura/templates/jinja_master/nav_menu.html 
b/Allura/allura/templates/jinja_master/nav_menu.html
index 3904896..4b8ac26 100644
--- a/Allura/allura/templates/jinja_master/nav_menu.html
+++ b/Allura/allura/templates/jinja_master/nav_menu.html
@@ -40,6 +40,9 @@
                 {%- endif -%}
             </a>
         </h1>
+        <a 
href="/p/{{c.project.shortname}}/files/download_file?app_url={{c.app.url}}" >
+          <button class="btn-home btn-danger" id="download_button" 
>Download</button>
+        </a> 
     {% set status = 
c.project.troves_by_type('developmentstatus')|sort(attribute='fullname') %}
     {% set status = status[-1] %}
     {% if status and status.shortname not in ['production', 'mature'] %}
diff --git a/ForgeFiles/forgefiles/__init__.py 
b/ForgeFiles/forgefiles/__init__.py
new file mode 100755
index 0000000..144e298
--- /dev/null
+++ b/ForgeFiles/forgefiles/__init__.py
@@ -0,0 +1,16 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
diff --git a/ForgeFiles/forgefiles/files_main.py 
b/ForgeFiles/forgefiles/files_main.py
new file mode 100755
index 0000000..508e6f6
--- /dev/null
+++ b/ForgeFiles/forgefiles/files_main.py
@@ -0,0 +1,660 @@
+'''This is the main controller module for the Files Plugin.'''
+
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+
+#       !/bin/python
+
+import logging
+from urllib import unquote
+
+from tg import config, redirect, expose, flash
+from tg.decorators import with_trailing_slash, without_trailing_slash
+from tg import tmpl_context as c, app_globals as g
+from tg import request, response
+from jinja2.exceptions import TemplateNotFound
+
+from allura.app import Application
+from allura.controllers import BaseController
+from allura.lib.decorators import require_post
+from allura.lib.widgets.subscriptions import SubscribeForm
+from allura import model as M
+from allura.controllers import attachments as att
+from allura import version
+from allura.model.timeline import TransientActor
+
+
+from bson import ObjectId
+from webob import exc
+
+# local imports ##
+from forgefiles.model.files import UploadFolder, UploadFiles, Upload
+
+log = logging.getLogger(__name__)
+
+
+class FilesApp(Application):
+    """Files plugin for the Allura platform"""
+
+    __version__ = version.__version__
+    tool_label = 'Files'
+    tool_description = """Upload executables for your project.
+        You may maintain version specific executables as well."""
+    default_mount_label = 'Files'
+    default_mount_point = 'files'
+    uninstallable = True
+    ordinal = 9
+    max_instances = 1
+
+    def __init__(self, project, config):
+        Application.__init__(self, project, config)
+        self.root = FilesController()
+
+    def install(self, project):
+        'Set up any default permissions and roles here'
+        self.config.options['project_name'] = project.name
+        super(FilesApp, self).install(project)
+        role_anon = M.ProjectRole.by_name('*anonymous')._id
+        self.config.acl = [
+            M.ACE.allow(role_anon, 'read'),
+        ]
+
+    def uninstall(self, project):
+        "Remove all the tool's artifacts from the database"
+        app_config_id = {'app_config_id': c.app.config._id}
+        Upload.query.remove(app_config_id)
+        UploadFolder.query.remove(app_config_id)
+        file_objects = UploadFiles.query.find(app_config_id).all()
+        for file_object in file_objects:
+            file_object.delete()
+        super(FilesApp, self).uninstall(project)
+
+
+def get_parent_folders(linked_file_object=None):
+
+    '''Returns the list of the parent folders for the current file or folder'''
+
+    parent_folder = linked_file_object.parent_folder if linked_file_object 
else None
+    parent_folders_list = []
+    while parent_folder:
+        parent_folders_list.append(str(parent_folder._id))
+        parent_folder = parent_folder.parent_folder
+    parent_folders_list = list(set(parent_folders_list))
+    return parent_folders_list
+
+
+class FilesController(BaseController):
+    """Root controller for the Files Application"""
+
+    @expose('jinja:forgefiles:templates/files.html')
+    def index(self):
+
+        '''Index method for the Root controller'''
+
+        folder_object = None
+        file_object = None
+
+        upload_object = Upload.query.get(project_id=c.project._id)
+        self.attachment = AttachmentsController(upload_object)
+        file_objects = UploadFiles.query.find({'project_id': c.project._id, 
'parent_folder_id': None})
+        file_objects = file_objects.sort([('created_date', -1)]).all()
+        folder_objects = UploadFolder.query.find({'project_id': c.project._id, 
'parent_folder_id': None})
+        folder_objects = folder_objects.sort([('created_date', -1)]).all()
+        if c.user in c.project.admins():
+            M.Mailbox.subscribe(type='direct')
+        c.subscribe_form = SubscribeForm(thing='files')
+        tool_subscribed = M.Mailbox.subscribed()
+        if tool_subscribed:
+            subscribed = M.Mailbox.subscribed()
+        else:
+            subscribed = False
+        file_object = UploadFiles.query.get(project_id=c.project._id, 
linked_to_download=True)
+        parents = get_parent_folders(linked_file_object=file_object)
+
+        return dict(file_objects=file_objects,
+                    folder_objects=folder_objects, 
folder_object=folder_object, file_object=file_object,
+                    subscribed=subscribed, parents=parents)
+
+    def get_parent_folder_url(self, parent_folder_id):
+
+        ''' Returns the url,parent_folder and id of parent_folder object if 
object is there'''
+
+        if (parent_folder_id == 'None') or (not parent_folder_id):
+            parent_folder_id = None
+            parent_folder = None
+            url = c.app.url
+        else:
+            parent_folder = 
UploadFolder.query.get(_id=ObjectId(parent_folder_id))
+            parent_folder_id = ObjectId(parent_folder._id)
+            url = parent_folder.url()
+        return parent_folder_id, parent_folder, url
+
+    @require_post()
+    @expose()
+    def create_folder(self, parent_folder_id=None, folder_name=None):
+
+        '''Controller method for creating a folder. The folder is stored in 
UploadFolder collection'''
+
+        parent_folder_id, parent_folder, url = 
self.get_parent_folder_url(parent_folder_id)
+        if folder_name:
+            folder_object = UploadFolder.query.find({
+                'project_id': c.project._id, 'folder_name': folder_name,
+                'parent_folder_id': parent_folder_id}).first()
+            if folder_object:
+                flash('Folder with the same name already exists!')
+            else:
+                folder_object = UploadFolder(folder_name=folder_name)
+                folder_object.parent_folder_id = parent_folder_id
+                parent = parent_folder
+                while parent:
+                    parent.folder_ids.append(str(folder_object._id))
+                    parent = parent.parent_folder
+                flash('Folder is created successfully')
+                g.director.create_activity(c.user, 'created', folder_object, 
related_nodes=[c.project])
+        else:
+            flash('Folder is not created successfully')
+        return redirect(url)
+
+    @require_post()
+    @expose()
+    def upload_file(self, parent_folder_id=None, file_upload=None, 
filename=None):
+
+        '''Controller method for creating a folder. The folder is stored in 
UploadFolder collection'''
+
+        parent_folder_id, parent_folder, url = 
self.get_parent_folder_url(parent_folder_id)
+        if file_upload is not None:
+            file_object = UploadFiles.query.find({
+                'project_id': c.project._id, 'filename': filename,
+                'parent_folder_id': parent_folder_id}).first()
+            if file_object:
+                flash('File with the same name already exists!')
+            else:
+                upload_object = Upload(
+                    project_id=c.project._id, filename=filename, 
filetype=file_upload.type)
+                attach_object = upload_object.attach(
+                    filename, file_upload.file, 
parent_folder_id=parent_folder_id)
+                if attach_object.parent_folder:
+                    upload_object.file_url = attach_object.parent_folder.url()
+                else:
+                    upload_object.file_url = c.app.url
+                parent = parent_folder
+                while parent:
+                    parent.file_ids.append(str(attach_object._id))
+                    parent = parent.parent_folder
+                flash('File is uploaded successfully')
+                g.director.create_activity(c.user, 'uploaded', upload_object, 
related_nodes=[c.project])
+        else:
+            flash('File is not uploaded successfully')
+        return redirect(url)
+
+    @require_post()
+    @expose()
+    def delete_file(self, file_id=None):
+
+        '''Controller method to delete a file'''
+
+        file_object = UploadFiles.query.get(_id=ObjectId(file_id))
+        file_name = file_object.filename
+        transient_actor = TransientActor(activity_name=file_name)
+        url = c.app.url
+        if file_id is not None:
+            self.delete_file_from_db(file_id=file_id)
+            parent_folder = file_object.parent_folder
+            if parent_folder:
+                url = parent_folder.url()
+            flash('File is successfully deleted')
+        else:
+            flash('File is not deleted')
+        g.director.create_activity(
+            c.user, 'deleted the file', transient_actor, 
related_nodes=[c.project])
+        M.main_orm_session.flush()
+        return redirect(url)
+
+    def delete_file_from_db(self, file_id=None):
+
+        '''Method to delete a file from db'''
+
+        file_object = UploadFiles.query.get(_id=ObjectId(file_id))
+        Upload.query.remove({'_id': file_object.artifact_id})
+        file_object.delete()
+
+    def delete_folder_recursively(self, folder_id):
+
+        '''This method is called recursively to delete folder in a hierarchy'''
+
+        sub_file_objects = UploadFiles.query.find(dict({
+            'project_id': c.project._id, 'parent_folder_id': 
ObjectId(folder_id)})).all()
+        for file_object in sub_file_objects:
+            self.delete_file_from_db(file_id=file_object._id)
+        sub_folder_objects = UploadFolder.query.find({
+            'project_id': c.project._id, 'parent_folder_id': 
ObjectId(folder_id)}).all()
+        for folder_object in sub_folder_objects:
+            self.delete_folder_recursively(folder_object._id)
+        UploadFolder.query.remove(dict({'_id': ObjectId(folder_id)}))
+
+    @without_trailing_slash
+    @require_post()
+    @expose('jinja:forgefiles:templates/files.html')
+    def delete_folder(self, folder_id=None):
+
+        '''Controller method to delete a folder'''
+
+        folder_object = UploadFolder.query.get(_id=ObjectId(folder_id))
+        folder_name = folder_object.folder_name
+        transient_actor = TransientActor(activity_name=folder_name)
+        url = c.app.url
+        if folder_id is not None:
+            self.delete_folder_recursively(folder_id)
+            if folder_object.parent_folder:
+                url = folder_object.parent_folder.url()
+            flash('Folder is deleted Successfully')
+        else:
+            flash('Folder is not deleted')
+        g.director.create_activity(
+            c.user, 'deleted the folder', transient_actor, 
related_nodes=[c.project])
+        M.main_orm_session.flush()
+        return redirect(url)
+
+    @expose()
+    def link_file(self, file_id=None, status=None):
+
+        '''Controller method to link a file to the download button'''
+        if status == 'False':
+            linkable_file_object = UploadFiles.query.get(_id=ObjectId(file_id))
+            linkable_file_object.linked_to_download = False
+            M.main_orm_session.flush()
+        else:
+            file_objects = UploadFiles.query.find({'project_id': 
c.project._id}).all()
+            for file_object in file_objects:
+                if file_object.linked_to_download:
+                    file_object.linked_to_download = False
+            linkable_file_object = UploadFiles.query.get(_id=ObjectId(file_id))
+            linkable_file_object.linked_to_download = True
+            M.main_orm_session.flush()
+
+    @expose()
+    def download_file(self, filename=None, app_url=None):
+
+        '''Controller method to download a file'''
+
+        if filename:
+            request_path = request.path.split(c.app.url)[-1].rstrip('/')
+            request_path = unquote(request_path)
+            linked_file_object = UploadFiles.query.find({
+                'project_id': c.project._id, 'filename': filename, 'path': 
request_path}).first()
+            upload_object = Upload.query.find({'_id': 
linked_file_object.artifact_id}).first()
+        else:
+            linked_file_object = UploadFiles.query.find({
+                'project_id': c.project._id, 'linked_to_download': 
True}).first()
+            if linked_file_object:
+                upload_object = Upload.query.find({'_id': 
linked_file_object.artifact_id}).first()
+            else:
+                upload_object = None
+        if linked_file_object:
+            try:
+                wrapper = upload_object.attachments[::-1]
+                data = wrapper
+                response.headers['Content-Type'] = 'application/octet'
+                response.headers['Content-Length'] = len(data)
+                response.headers['Pragma'] = 'public'
+                response.headers['Cache-Control'] = 'max-age=0'
+                requested_filename = linked_file_object.filename
+                response.headers['Content-Disposition'] = 'attachment; 
filename="%s"' % (requested_filename)
+                M.Mailbox.subscribe(type='direct')
+                return linked_file_object.serve(embed=True)
+            except Exception as e:
+                log.exception('%s error to download the file', e)
+        else:
+            data = 'No artifact available'
+            flash('No artifact available')
+        return redirect(app_url)
+
+    @require_post()
+    @expose()
+    def edit_folder(self, folder_id=None, folder_name=None):
+
+        '''Controller method to edit the folder name'''
+
+        url = c.app.url
+        folder_object = UploadFolder.query.get(_id=ObjectId(folder_id))
+        if folder_object:
+            folder_object.folder_name = folder_name
+            flash("Folder name edited successfully")
+            if folder_object.parent_folder:
+                url = folder_object.parent_folder.url()
+        else:
+            flash("Folder name not edited")
+        M.main_orm_session.flush()
+        redirect(url)
+
+    @require_post()
+    @expose()
+    def edit_file(self, file_id=None, file_name=None):
+
+        '''Controller method to edit the file name'''
+
+        url = c.app.url
+        file_object = UploadFiles.query.get(_id=ObjectId(file_id))
+        upload_object = Upload.query.get(_id=file_object.artifact_id)
+        if file_object:
+            upload_object.filename = file_name
+            file_object.filename = file_name
+            flash("File name edited successfully")
+            if file_object.parent_folder:
+                url = file_object.parent_folder.url()
+        else:
+            flash("File not edited")
+        M.main_orm_session.flush()
+        return redirect(url)
+
+    @require_post()
+    @expose()
+    def publish_folder(self, folder_id=None, remarks=None):
+
+        '''Controller which publishes the folder. It send update about the 
publishing of the folder.'''
+
+        folder_object = UploadFolder.query.get(_id=ObjectId(folder_id))
+        url = c.app.url
+        if folder_object:
+            folder_object.published = True
+            folder_object.remarks = remarks
+            mailbox_object = M.Mailbox.query.find({
+                'project_id': c.project._id, 'app_config_id': 
c.app.config._id}).all()
+            user_ids = [i.user_id for i in mailbox_object]
+            admins = [i._id for i in c.project.admins()]
+            user_ids += admins
+            user_ids = list(set(user_ids))
+            from allura.tasks import mail_tasks
+            from allura.lib import helpers as h
+            template_name = ''
+            try:
+                for i in user_ids:
+                    user_object = M.User.query.get(_id=i)
+                    template_name = 'forgefiles:/templates/mail.html'
+                    text = 
g.jinja2_env.get_template(template_name).render(dict(
+                        base_url=config.get('base_url'), 
user_object=user_object, project=c.project,
+                        remarks=remarks, folder_object=folder_object, 
project_owner=c.user,
+                        domain=config.get('domain')
+                    ))
+                    email_object = M.EmailAddress.get(claimed_by_user_id=i)
+                    if email_object:
+                        mail_tasks.sendsimplemail.post(
+                            fromaddr=g.noreply,
+                            reply_to=g.noreply,
+                            toaddr=email_object.email,
+                            subject='%s - %s Release Update' % 
(config.get('site_name'), c.project.name),
+                            message_id=h.gen_message_id(),
+                            text=text)
+                if folder_object.parent_folder:
+                    url = folder_object.parent_folder.url()
+                flash('Successfully Published')
+            except TemplateNotFound:
+                log.exception('%s Template not found' % (template_name))
+                log.info('Folder %s is not published successfully' % 
(folder_object.folder_name))
+                flash('Folder is not published successfully')
+        return redirect(url)
+
+    @require_post()
+    @expose()
+    def disable_folder(self, folder_id=None, status=None):
+
+        '''Controller method to disable the folder.'''
+
+        folder_object = UploadFolder.query.get(_id=ObjectId(folder_id))
+
+        if status == 'True':
+            disable_status = True
+            text = 'disabled'
+        else:
+            disable_status = False
+            text = 'enabled'
+        if folder_object:
+            folder_object.disabled = disable_status
+            '''Disabling Child folders & files of the current folder '''
+
+            for child_folder_id in folder_object.folder_ids:
+                child_folder_object = 
UploadFolder.query.get(_id=ObjectId(child_folder_id))
+                if child_folder_object:
+                    child_folder_object.disabled = disable_status
+            for child_file_id in folder_object.file_ids:
+                child_file_object = 
UploadFiles.query.get(_id=ObjectId(child_file_id))
+                if child_file_object:
+                    child_file_object.disabled = disable_status
+            M.main_orm_session.flush()
+            flash('Folder %s successfully' % (text))
+        else:
+            flash('No folder exists')
+
+    @require_post()
+    @expose()
+    def disable_file(self, file_id=None, status=None):
+
+        '''Controller method to disable the file.'''
+
+        file_object = UploadFiles.query.get(_id=ObjectId(file_id))
+        if status == 'True':
+            disable_status = True
+            text = 'disabled'
+        else:
+            disable_status = False
+            text = 'enabled'
+        if file_object:
+            file_object.disabled = disable_status
+            flash('File %s successfully' % (text))
+        else:
+            flash('No file exists')
+
+    @expose()
+    def project_file(self):
+        files_count = UploadFiles.query.find({
+            'project_id': c.project._id, 'linked_to_download': True, 
'disabled': False}).count()
+        return str(files_count)
+
+    @expose('json:')
+    @require_post()
+    def subscribe(self, subscribe=None, unsubscribe=None):
+
+        '''Controller method that subscribes an user to the files plugin.'''
+
+        if subscribe:
+            M.Mailbox.subscribe(type='direct')
+        elif unsubscribe:
+            M.Mailbox.unsubscribe()
+        return {
+            'status': 'ok',
+            'subscribed': M.Mailbox.subscribed(),
+        }
+
+    def get_folder_object(self, folder_id=None):
+        '''Returns the folder object for input folder id'''
+        folder_object = UploadFolder.query.get(_id=ObjectId(folder_id))
+        return folder_object
+
+    @expose('jinja:forgefiles:templates/create_folder.html')
+    def get_parent_for_create_folder(self, folder_id=None):
+        '''Returns the parent object of the input folder id'''
+        folder_object = self.get_folder_object(folder_id)
+        return dict(folder_object=folder_object)
+
+    @expose('jinja:forgefiles:templates/upload_file.html')
+    def get_parent_for_upload_file(self, folder_id=None):
+        '''Returns the parent object of the input folder id'''
+        folder_object = self.get_folder_object(folder_id)
+        return dict(folder_object=folder_object)
+
+    def get_folder_file_object(self, object_id=None):
+        '''Returns corresponding file or folder object for the input id '''
+        folder_object = UploadFolder.query.get(_id=ObjectId(object_id))
+        file_object = UploadFiles.query.get(_id=ObjectId(object_id))
+        return dict(folder_object=folder_object, file_object=file_object)
+
+    @expose('jinja:forgefiles:templates/edit.html')
+    def get_editable_object(self, object_id=None):
+        '''Returns object id of the folder or file to be edited'''
+        object_dict = self.get_folder_file_object(object_id)
+        return object_dict
+
+    @expose('jinja:forgefiles:templates/delete.html')
+    def get_deletable_object(self, object_id=None):
+        '''Returns object id of the folder or file to be deleted'''
+        object_dict = self.get_folder_file_object(object_id)
+        return object_dict
+
+    @expose('jinja:forgefiles:templates/publish_folder.html')
+    def get_publishable_folder(self, folder_id=None):
+        '''Returns the status and folder object if the folder can be published 
or not'''
+        linked_file_object = UploadFiles.query.get(project_id=c.project._id, 
linked_to_download=True, disabled=False)
+        parent_folders = 
get_parent_folders(linked_file_object=linked_file_object)
+        if folder_id:
+            folder_object = UploadFolder.query.get(_id=ObjectId(folder_id))
+            status = str(folder_object._id) in parent_folders
+        else:
+            folder_object = None
+            status = False
+        return dict(folder_object=folder_object, status=status)
+
+    @expose()
+    def _lookup(self, name, *remainder):
+        ''' Class method which is used to call individual files controller 
class'''
+        if not remainder:
+            argument = name
+        else:
+            argument = remainder[-1]
+        if argument == 'createFolder':
+            argument = None
+        return IndividualFilesController(argument), remainder
+
+
+def folder_breadcrumbs(folder_object=None):
+    ''' Function to create a breadcrumbs for folders '''
+    list_object = folder_object.path.split('/')
+    second_list = []
+    length = 0
+    urls = {}
+    for i in list_object:
+        length += len(i)
+        folder_object = UploadFolder.query.get(folder_name=i)
+        urls[str(i)] = str(folder_object.url())
+        if length in range(1, (61-len(list_object[-1])+1)):
+            second_list.append(i)
+    second_list.append('...')
+    second_list.append(list_object[-1])
+    string = '/'.join(second_list)
+    if length > 61:
+        return string, urls
+    else:
+        return folder_object.path, urls
+
+
+# handle requests for individual folder,file objects
+class IndividualFilesController(BaseController):
+    """Handle requests for a specific folder/file objects"""
+
+    def __init__(self, arg):
+        path = request.path.split(c.app.url)[-1].rstrip('/')
+        if path == arg:
+            path = arg
+        path = unquote(path)
+        arg = unquote(arg)
+        self.folder_object = UploadFolder.query.find({
+            'project_id': ObjectId(c.project._id), 'folder_name': arg, 'path': 
path}).first()
+        self.file_object = UploadFiles.query.find({
+            'project_id': ObjectId(c.project._id), 'filename': arg, 'path': 
path}).first()
+        methods = ('create_folder', 'upload_file', 'delete_file', 
'delete_folder', 'subscribe')
+        if (not self.folder_object) and (not self.file_object) and (arg not in 
methods):
+            log.exception('No Folder/File object found')
+            raise exc.HTTPNotFound()
+        else:
+            pass
+
+    @expose('jinja:forgefiles:templates/files.html')
+    @with_trailing_slash
+    def index(self):
+        ''' Index method of individual folder/file objects'''
+        folder_objects = None
+        file_objects = None
+        folder_path, urls = '', ''
+        if self.folder_object:
+            folder_objects = UploadFolder.query.find({
+                'project_id': c.project._id, 'parent_folder_id': 
self.folder_object._id})
+            folder_objects = folder_objects.sort([('created_date', -1)]).all()
+            file_objects = UploadFiles.query.find({
+                'project_id': c.project._id, 'parent_folder_id': 
self.folder_object._id})
+            file_objects = file_objects.sort([('created_date', -1)]).all()
+            folder_path, urls = 
folder_breadcrumbs(folder_object=self.folder_object)
+        elif self.file_object:
+            return 
FilesController().download_file(filename=self.file_object.filename)
+        if c.user in c.project.admins():
+            M.Mailbox.subscribe(type='direct')
+        c.subscribe_form = SubscribeForm(thing='files')
+        tool_subscribed = M.Mailbox.subscribed()
+        if tool_subscribed:
+            subscribed = M.Mailbox.subscribed()
+        else:
+            subscribed = False
+        file_object = UploadFiles.query.get(project_id=c.project._id, 
linked_to_download=True)
+        parents = get_parent_folders(linked_file_object=file_object)
+
+        return dict(folder_objects=folder_objects,
+                    file_objects=file_objects, 
folder_object=self.folder_object, file_object=self.file_object,
+                    subscribed=subscribed, parents=parents, 
folder_path=folder_path, urls=urls)
+
+    @require_post()
+    @expose()
+    def create_folder(self, parent_folder_id=None, folder_name=None):
+        return 
FilesController().create_folder(parent_folder_id=parent_folder_id, 
folder_name=folder_name)
+
+    @require_post()
+    @expose()
+    def upload_file(self, parent_folder_id=None, filename=None, 
file_upload=None):
+        return FilesController().upload_file(
+            parent_folder_id=parent_folder_id, filename=filename, 
file_upload=file_upload)
+
+    @require_post()
+    @expose()
+    def delete_file(self, file_id=None):
+        return FilesController().delete_file(file_id=file_id)
+
+    @expose('json:')
+    @require_post()
+    def subscribe(self, subscribe=None, unsubscribe=None):
+        if subscribe:
+            M.Mailbox.subscribe(type='direct')
+        elif unsubscribe:
+            M.Mailbox.unsubscribe()
+        return {
+            'status': 'ok',
+            'subscribed': M.Mailbox.subscribed(),
+        }
+
+    @expose()
+    def _lookup(self, name, *remainder):
+        if not remainder:
+            argument = name
+        else:
+            argument = remainder[-1]
+        return IndividualFilesController(argument), remainder
+
+
+class AttachmentController(att.AttachmentController):
+    AttachmentClass = UploadFiles
+    edit_perm = 'update'
+
+
+class AttachmentsController(att.AttachmentsController):
+    AttachmentControllerClass = AttachmentController
diff --git a/ForgeFiles/forgefiles/model/__init__.py 
b/ForgeFiles/forgefiles/model/__init__.py
new file mode 100755
index 0000000..8b738df
--- /dev/null
+++ b/ForgeFiles/forgefiles/model/__init__.py
@@ -0,0 +1,18 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from files import UploadFolder, UploadFiles
diff --git a/ForgeFiles/forgefiles/model/files.py 
b/ForgeFiles/forgefiles/model/files.py
new file mode 100755
index 0000000..33062d0
--- /dev/null
+++ b/ForgeFiles/forgefiles/model/files.py
@@ -0,0 +1,247 @@
+''' This is the Collection module for the Files plugin. Upload, UploadFolder & 
UploadFile are the collections'''
+#        Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+
+from datetime import datetime
+
+from urllib import quote
+import re
+
+from ming import schema as S
+from ming.orm import Mapper
+from ming.orm import FieldProperty, ForeignIdProperty, RelationProperty
+
+from tg import tmpl_context as c
+
+from allura.model.artifact import VersionedArtifact
+from allura.model.auth import AlluraUserProperty, User
+from allura.model.session import project_orm_session, artifact_orm_session
+from allura.model.filesystem import File
+from allura.model.timeline import ActivityObject
+from allura.lib import helpers as h
+
+README_RE = re.compile(r'^README(\.[^.]*)?$', re.IGNORECASE)
+
+
+class Upload(VersionedArtifact, ActivityObject):
+
+    ''' The Upload collection for files. '''
+
+    class __mongometa__:
+        name = 'upload'
+        session = project_orm_session
+    type_s = 'Upload'
+    _id = FieldProperty(S.ObjectId)
+    filename = FieldProperty(str)
+    filetype = FieldProperty(str)
+    project_id = ForeignIdProperty('Project', if_missing=lambda: c.project._id)
+    created_date = FieldProperty(datetime, if_missing=datetime.utcnow)
+    project = RelationProperty('Project', via='project_id')
+    file_url = None
+
+    @classmethod
+    def attachment_class(cls):
+        return UploadFiles
+
+    @property
+    def activity_name(self):
+        return self.filename
+
+    @property
+    def activity_url(self):
+        return self.file_url
+
+
+class UploadFolder(VersionedArtifact, ActivityObject):
+
+    ''' The UploadFolder collection is for the Folders. Every folder is an 
object of this class.'''
+
+    class __mongometa__:
+        name = 'upload_folder'
+        session = project_orm_session
+
+    type_s = 'UploadFolder'
+    _id = FieldProperty(S.ObjectId)
+    folder_name = FieldProperty(str)
+    project_id = ForeignIdProperty('Project', if_missing=lambda: c.project._id)
+    parent_folder_id = ForeignIdProperty('UploadFolder')
+    created_date = FieldProperty(datetime, if_missing=datetime.utcnow)
+    author = ForeignIdProperty('User', if_missing=lambda: c.user._id)
+    parent_folder = RelationProperty('UploadFolder', via='parent_folder_id')
+    project = RelationProperty('Project', via='project_id')
+    path = FieldProperty(str)
+    published = FieldProperty(bool, if_missing=False)
+    remarks = FieldProperty(str)
+    disabled = FieldProperty(bool, if_missing=False)
+    folder_ids = FieldProperty([str])
+    file_ids = FieldProperty([str])
+
+    def created_by(self):
+
+        '''Returns the user object of the admin who creaated the folder '''
+
+        user_obj = User.query.find({'_id': self.author}).first()
+        return user_obj
+
+    def url(self):
+        parent_folder = self.parent_folder
+        if parent_folder:
+            string = ''
+            while parent_folder:
+                string += parent_folder.folder_name + '/'
+                parent_folder = parent_folder.parent_folder
+            list_obj = string.rsplit('/')[::-1]
+            string = '/'.join(list_obj)
+            string = string.lstrip('/')
+            url_str = c.app.url + string + '/' + self.folder_name
+            self.path = string + '/' + self.folder_name
+        else:
+            url_str = c.app.url + self.folder_name
+            self.path = self.folder_name
+        return quote(url_str)
+
+    def folder_id(self):
+        return str(self._id)
+
+    @property
+    def activity_name(self):
+        return self.folder_name
+
+    @property
+    def activity_url(self):
+        parent_folder = self.parent_folder
+        if parent_folder:
+            return parent_folder.url()
+        else:
+            return c.app.url
+
+
+class UploadFiles(File):
+
+    '''The UploadFiles collection is for the Files. Any file which is uploaded 
is treated as an Upload object'''
+
+    thumbnail_size = (255, 255)
+    ArtifactType = Upload
+
+    class __mongometa__:
+        name = 'upload_files'
+        session = project_orm_session
+        indexes = ['artifact_id', 'app_config_id']
+
+        def before_save(data):
+            _session = artifact_orm_session._get()
+            skip_last_updated = getattr(_session, 'skip_last_updated', False)
+            data['mod_date'] = datetime.utcnow()
+            if c.project and not skip_last_updated:
+                c.project.last_updated = datetime.utcnow()
+
+    artifact_id = FieldProperty(S.ObjectId)
+    app_config_id = FieldProperty(S.ObjectId)
+    type = FieldProperty(str)
+    project_id = FieldProperty(S.ObjectId)
+    parent_folder_id = ForeignIdProperty('UploadFolder')
+    created_date = FieldProperty(datetime, if_missing=datetime.utcnow)
+    mod_date = FieldProperty(datetime, if_missing=datetime.utcnow)
+    author = AlluraUserProperty(if_missing=lambda: c.user._id)
+    parent_folder = RelationProperty('UploadFolder', via='parent_folder_id')
+    linked_to_download = FieldProperty(bool, if_missing=False)
+    path = FieldProperty(str)
+    disabled = FieldProperty(bool, if_missing=False)
+
+    @property
+    def file_size(self):
+
+        '''Returns the size of the file'''
+
+        size = self.length
+        one_gb = 1000000000
+        one_mb = 1000000
+        one_kb = 1000
+        if size > one_gb:
+            return "{0:.2f} GB".format(float(size)/one_gb)
+        elif size > one_mb:
+            return "{0:.2f} MB".format(float(size)/one_mb)
+        elif size > one_kb:
+            return "{0:.2f} KB".format(float(size)/one_kb)
+        else:
+            return "{} B".format(size)
+
+    @property
+    def artifact(self):
+
+        '''Returns the Artifact object'''
+
+        return self.ArtifactType.query.get(_id=self.artifact_id)
+
+    def uploaded_by(self):
+
+        '''Returns the user object of the admin who uploads the file'''
+
+        user_obj = User.query.find({'_id': self.author}).first()
+        return user_obj
+
+    def url(self):
+
+        '''Returns the URL of the uploaded file'''
+
+        parent_folder = self.parent_folder
+        if parent_folder:
+            string = ''
+            while parent_folder:
+                string += parent_folder.folder_name + '/'
+                parent_folder = parent_folder.parent_folder
+            list_obj = string.rsplit('/')[::-1]
+            string = '/'.join(list_obj)
+            string = string.lstrip('/')
+            url_str = c.app.url + string + '/' + self.filename
+            self.path = string + '/' + self.filename
+        else:
+            url_str = c.app.url + self.filename
+            self.path = self.filename
+        return quote(url_str)
+
+    def readme(self):
+        'returns (filename, unicode text) if a readme file is found'
+        if README_RE.match(self.filename):
+            name = self.filename
+            obj_content = self.rfile().read(self.rfile().length)
+            return (self.filename, h.really_unicode(obj_content))
+        return None, None
+
+    def is_embedded(self):
+        from tg import request
+        return self.filename in 
request.environ.get('allura.macro.att_embedded', [])
+
+    @classmethod
+    def metadata_for(cls, artifact):
+        return dict(
+            artifact_id=artifact._id,
+            app_config_id=artifact.app_config_id)
+
+    @classmethod
+    def save_attachment(cls, filename, fp, content_type=None, **kwargs):
+        filename = h.really_unicode(filename)
+        original_meta = dict(type="project_file", 
app_config_id=c.app.config._id, project_id=c.project._id)
+        original_meta.update(kwargs)
+        fp.seek(0)
+        return cls.from_stream(
+            filename, fp, content_type=content_type,
+            **original_meta)
+
+
+Mapper.compile_all()
diff --git a/ForgeFiles/forgefiles/nf/files/css/files.css 
b/ForgeFiles/forgefiles/nf/files/css/files.css
new file mode 100644
index 0000000..ec32edd
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/css/files.css
@@ -0,0 +1,86 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+.upload_buttons{
+    float: right;
+}
+.main_div .btn{
+    width: auto;
+    height: 3em;
+}
+.main_div table{
+    position:relative;
+    top:20px;
+    
+}
+.name_col{
+    width:35%;
+}
+.date_col{
+    width:15%;
+}
+
+.size_col{
+    width:12%;
+}
+
+.author_col{
+    width:25%;
+}
+.actions_col{
+    width:13%;
+}
+.folder_actions{
+    display: flex;
+    justify-content: space-between;
+}
+.disable_object, #disable_link{
+    color: grey !important;
+}
+.main_div .fa, .no_objects i{
+    font-size: 15px;
+}
+.file_actions{
+    display: flex;
+    justify-content: space-between;
+}
+.no_objects{
+    position:relative;
+    top:40px;
+    text-align: center;
+}
+.folder_dis .icon {
+    color: grey !important;
+}
+.file_dis .icon {
+    color: grey !important;
+}
+.folder_name_label{
+    font-size: 15px !important;
+}
+
+
+/* CSS For Popups */
+.modal-header .close{
+    position: absolute;top: 10px;right: 10px;cursor: pointer;
+}
+.modal-form-error{
+    margin-left: unset !important;
+}
+
diff --git a/ForgeFiles/forgefiles/nf/files/js/create_folder.js 
b/ForgeFiles/forgefiles/nf/files/js/create_folder.js
new file mode 100644
index 0000000..f293612
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/js/create_folder.js
@@ -0,0 +1,77 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+$('#admin_modal_title').hide();
+$('#folder_id').focus();
+
+function validateFolderForm(){
+    var folder_id = document.getElementById('folder_id');
+    var folder_name = $(folder_id).val().trim();
+    var error = $('#error_message');
+
+    var flag;
+    flag = validateName(folder_name);
+
+    if ( folder_name.length === 0){
+        $(error).text('Please enter folder name.');
+        return false;
+    }
+    else{
+        return true;
+    }
+     
+ }
+
+function validateName(folder_name){
+
+    var error = $('#error_message');
+    var regular_exp = new RegExp("^[a-zA-Z0-9_ +.,=#~@!()\\[\\]-]+$");
+    var validation_msg;
+    if(folder_name.length===0){validation_msg="Please enter folder name."}
+    else{
+        if(folder_name.slice(0,1)==="."){
+            validation_msg='Folder name cannot start with ".".'}
+        else{
+            if(folder_name.slice(0,1)===" "){
+                validation_msg="Folder name cannot start with a space."}
+            else{
+                if(folder_name.slice(-1)===" "){
+                    validation_msg="Folder name cannot end with a space."}
+                else{if(!regular_exp.test(folder_name)){validation_msg='Folder 
name cannot contain characters like ($/\"%^&*`|?<>:;).'}
+                else{validation_msg=true}}}}}
+    
+    return validation_msg;
+    }
+
+$('#folder_id').keyup(function(){
+    var folder_name = $('#folder_id').val();
+    var error = $('#error_message');
+    var flag;
+    flag = validateName(folder_name);
+    if (flag != true){
+        $(error).text(flag);
+        $('#submit_btn').attr('disabled',true);
+    }
+    else{
+        $(error).text('');
+        $('#submit_btn').attr('disabled',false);
+    }
+});
+
+
diff --git a/ForgeFiles/forgefiles/nf/files/js/delete.js 
b/ForgeFiles/forgefiles/nf/files/js/delete.js
new file mode 100644
index 0000000..f53b679
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/js/delete.js
@@ -0,0 +1,33 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+$('#admin_modal_title').hide();
+
+ function ConfirmDeleteFolder(){
+      var obj_id = document.getElementById('delete_id');
+      var confirm_delete = $(obj_id).val();
+      var error = $('#error_message');
+      if((confirm_delete === "DELETE")){
+        return true;
+        }
+      else{
+        $(error).text('You must confirm with the word DELETE');
+        return false;
+      }
+     }
diff --git a/ForgeFiles/forgefiles/nf/files/js/edit.js 
b/ForgeFiles/forgefiles/nf/files/js/edit.js
new file mode 100644
index 0000000..52d64cd
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/js/edit.js
@@ -0,0 +1,82 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+$('#admin_modal_title').hide();
+$('#obj_id').select();
+var obj_type = $('#obj_type').val();
+
+function validateEditFolderForm(){
+    var folder_obj = document.getElementById('obj_id');
+    var folder_name = $(folder_obj).val().trim();
+    var error = $('#error_message');
+    var flag;
+    flag = validateName(folder_name);
+    if (folder_name.length === 0){
+        if(obj_type == 'folder')
+            $(error).text('Please enter folder name');
+        else if(obj_type == 'file')
+            $(error).text('Please enter file name');
+        return false;
+    }
+    else{
+        return true;
+    }
+     
+ }
+
+//#################//
+
+function validateName(object_name){
+
+    var error = $('#error_message');
+    var regular_exp=new RegExp("^[a-zA-Z0-9_ +.,=#~@!()\\[\\]-]+$");
+    var validation_msg;
+    if(object_name.length===0){ if(obj_type == 'folder') 
{validation_msg="Please enter folder name."} else {validation_msg="Please enter 
file name."} }
+    else{
+        if(object_name.slice(0,1)==="."){
+            if(obj_type == 'folder') {validation_msg='Folder name cannot start 
with ".".'} else {validation_msg='File name cannot start with ".".'} }
+        else{
+            if(object_name.slice(0,1)===" "){
+            if(obj_type == 'folder') {validation_msg="Folder name cannot start 
with a space."} else {validation_msg="File name cannot start with a space."} }
+            else{
+                if(object_name.slice(-1)===" "){
+                 if(obj_type == 'folder') {validation_msg="Folder name cannot 
end with a space."} else {validation_msg="File name cannot end with a space."} }
+                else{if(!regular_exp.test(object_name)){
+                 if(obj_type == 'folder') {validation_msg='Folder name cannot 
contain characters like ($/\"%^&*`|?<>:;).'}
+                 else {validation_msg='File name cannot contain characters 
like ($/\"%^&*`|?<>:;).'} }
+                else{validation_msg=true}}}}}
+    
+    return validation_msg;
+    }
+
+$('#obj_id').keyup(function(){
+    var obj_name = $('#obj_id').val();
+    var error = $('#error_message');
+    var flag;
+    flag = validateName(obj_name);
+    if (flag != true){
+        $(error).text(flag);
+        $('#submit_btn').attr('disabled',true);
+    }
+    else{
+        $(error).text('');
+        $('#submit_btn').attr('disabled',false);
+    }
+});
+
diff --git a/ForgeFiles/forgefiles/nf/files/js/files.js 
b/ForgeFiles/forgefiles/nf/files/js/files.js
new file mode 100644
index 0000000..53a1684
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/js/files.js
@@ -0,0 +1,94 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {    options.async 
= true; });
+
+function removeModalContent(){
+    $('#delete_folder').val(' ');
+    $('#error_message_delete_folder').remove();
+}
+
+
+ function ConfirmDisableFolder(folderID,status,parent_status,url)
+     {
+         if (status == 'True' && parent_status == 'True'){
+             alert('Please enable parent folder of this folder');
+             return false;
+         }
+         else{
+             if (status == 'False'){
+                 var confirm_resp = confirm("Are you sure you want to 
disable?");
+                 var disable_status = 'True';
+             }
+             else if(status == 'True'){
+                 var confirm_resp = confirm("Are you sure you want to 
enable?");
+                 var disable_status = 'False';
+             }
+             if (confirm_resp){
+                $.post(url, {'folder_id':folderID, 'status':disable_status}, 
function() {
+                    location.reload();
+                });
+             }
+             else
+                 return false;
+            true
+         }
+     }
+ function ConfirmDisableFile(fileID,status,parent_status,url)
+     {
+         if (status == 'True' && parent_status == 'True'){
+             alert('Please enable parent folder of this file');
+             return false;
+         }
+         else{
+             if (status == 'False'){
+                 var confirm_resp = confirm("Are you sure you want to 
disable?");
+                 var disable_status = 'True';
+             }
+             else if(status == 'True'){
+                 var confirm_resp = confirm("Are you sure you want to 
enable?");
+                 var disable_status = 'False';
+             }
+             if (confirm_resp){
+                 $.post(url, {'file_id':fileID, 'status':disable_status}, 
function() {
+                    location.reload();
+                });
+             }
+             else
+                 return false;
+         }
+     }
+
+ function ConfirmLinkFile(fileID,linked_to_download,url)
+ {
+     
+    if(linked_to_download === 'True'){
+        var confirm_resp = confirm("This file is already linked to Downloads. 
Do you want to unlink it?");
+        var link_status = 'False';
+    }
+    else{
+        var confirm_resp = confirm("Are you sure you want to link to the 
Downloads?");
+        var link_status = 'True';
+    }
+    $.post(url, {'file_id':fileID, 'status':link_status}, function() {
+        location.reload();
+    })
+ }
+
+
diff --git a/ForgeFiles/forgefiles/nf/files/js/publish_folder.js 
b/ForgeFiles/forgefiles/nf/files/js/publish_folder.js
new file mode 100644
index 0000000..a69ed78
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/js/publish_folder.js
@@ -0,0 +1,46 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+$('#admin_modal_title').hide();
+$('#remarks_id').focus();
+
+
+ function ConfirmPublishFolder(){
+     var remarks = document.getElementById('remarks_id');
+     var release_notes = $(remarks).val().trim();
+     var error = $('#error_message');
+     var parent = document.getElementById('parent_publish_status');
+     var parent_publish_status = $(parent).val();
+     var current_folder = document.getElementById('publish_status');
+     var publish_status = $(current_folder).val();
+     var submit_btn = $('#submit_btn');
+     if(release_notes.length === 0){
+        $(error).text('Please enter release notes');
+        return false;
+     }
+     else if(parent_publish_status === 'False'){
+        $(submit_btn).attr('disabled', true);
+        $(error).text('To publish this folder, please link a file under it to 
the Download button');
+        return false;
+     }
+     else{
+        return true;
+    }
+
+ }
diff --git a/ForgeFiles/forgefiles/nf/files/js/upload_files.js 
b/ForgeFiles/forgefiles/nf/files/js/upload_files.js
new file mode 100644
index 0000000..c00c582
--- /dev/null
+++ b/ForgeFiles/forgefiles/nf/files/js/upload_files.js
@@ -0,0 +1,36 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+$('#admin_modal_title').hide();
+
+ function validateFileForm(){
+     var file_input = document.getElementById('file_input');
+     var file_path = file_input.value.split('\\').pop();
+     var filename = $('#filename');
+     var file_val = $(file_input).val();
+     var error = $('#error_message');
+     if (file_val.length === 0){
+         $(error).text('Please upload a file');
+         return false;
+         }
+     else{
+         $(filename).val(file_path);
+         return true;
+         }
+ }
diff --git a/ForgeFiles/forgefiles/templates/create_folder.html 
b/ForgeFiles/forgefiles/templates/create_folder.html
new file mode 100644
index 0000000..fef917e
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/create_folder.html
@@ -0,0 +1,58 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+<script type="text/javascript" src="{{g.app_static('js/create_folder.js')}}" 
async="async"></script>
+
+    <h1 class="title" >
+        <span> Create Folder </span>
+    </h1>
+
+    <div id="folder_popup" class="" role="dialog" >
+     <form {% if folder_object %} 
action="{{folder_object.url()}}/create_folder" {% else %} 
action="{{c.app.url}}create_folder" {% endif %} method="post"  
enctype="multipart/form-data" onsubmit="return validateFolderForm();">
+     <div class="modal-dialog">
+         <!-- Modal content-->
+         <div class="modal-content">
+           <div class="modal-header">
+             <a class="icon close" href="#" title="Close" 
data-dismiss="modal"><i class="fa fa-close"></i></a>
+          </div>
+           <div class="modal-body grid-13">
+             <p>
+            
+                 <label for="folder_name">Folder Name:</label>
+                 <input type="text" id="folder_id" name="folder_name" 
maxlength="260" value="" >
+
+                 <span id="error_message" class="modal-form-error"></span>
+             </p>
+           </div>
+         <input type="hidden" name="parent_folder_id" {% if folder_object %} 
value="{{folder_object._id}}" {% else %} value=None {% endif %} />
+           <div class="modal-footer grid-13">
+             <p>
+                 <input type="submit" id="submit_btn" value="Save"/>
+                 <a href="#" class="close">Cancel</a>
+             </p>
+           </div>
+         </div>
+     </div>
+     {{lib.csrf_token()}}
+     </form>
+    </div>
+
+
diff --git a/ForgeFiles/forgefiles/templates/delete.html 
b/ForgeFiles/forgefiles/templates/delete.html
new file mode 100644
index 0000000..229450d
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/delete.html
@@ -0,0 +1,62 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+<script type="text/javascript" src="{{g.app_static('js/delete.js')}}"></script>
+
+    <h1 class="title" >
+        <span> Are you sure you want to delete this {% if folder_object %} 
folder? {% elif file_object %} file? {% endif %} </span>
+    </h1>
+
+<div id="delete_folder_popup"  >
+<form id ='delete_folder_form' method="post" {% if folder_object %} 
action="{{c.app.url}}delete_folder" {% elif file_object %} 
action="{{c.app.url}}delete_file" {% endif %} onsubmit="return 
ConfirmDeleteFolder();" >
+<div class="modal-dialog">
+    <!-- Modal content-->
+    <div class="modal-content">
+    <div class="modal-header">
+        <a class="icon close" href="#" title="Close" data-dismiss="modal"><i 
class="fa fa-close"></i></a>
+    </div>
+    <div class="modal-body grid-13">
+        <p>
+    
+            <label for="delete">This is an irreversible action. If you are 
sure then type 'DELETE'.</label>
+            <input type="text" id="delete_id" autofocus />
+
+            <span id="error_message" class="modal-form-error"></span>
+        </p>
+    </div>
+    {% if folder_object %}
+        <input type="hidden" name="folder_id" id='delete_folder_id' 
value="{{folder_object._id}}" />
+    {% elif file_object %}
+        <input type="hidden" name="file_id" id='delete_file_id' 
value="{{file_object._id}}" />
+    {% endif %}
+    <div class="modal-footer grid-13">
+        <p>
+                 <input type="submit" id="submit_btn" value="Ok"/>
+                 <a href="#" class="close">Cancel</a>
+        </p>
+    </div>
+    </div>
+</div>
+{{lib.csrf_token()}}
+</form>
+</div>
+
+
diff --git a/ForgeFiles/forgefiles/templates/edit.html 
b/ForgeFiles/forgefiles/templates/edit.html
new file mode 100644
index 0000000..7379872
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/edit.html
@@ -0,0 +1,78 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+<script type="text/javascript" src="{{g.app_static('js/edit.js')}}"></script>
+
+    <h1 class="title" >
+        <span> Edit {% if folder_object %} Folder {% elif file_object %} File 
{% endif %} Name </span>
+    </h1>
+
+<!-- popup for edit folder-->
+<div id="edit_folder_popup" >
+ <form id ='edit_folder_form' method="post"
+    {% if folder_object %}
+        action="{{c.app.url}}edit_folder"
+    {% elif file_object %}
+        action="{{c.app.url}}edit_file"
+    {% endif %}
+    onsubmit="return validateEditFolderForm();" >
+ <div class="modal-dialog">
+     <!-- Modal content-->
+     <div class="modal-content">
+     <div class="modal-header">
+         <a class="icon close" href="#" title="Close" data-dismiss="modal"><i 
class="fa fa-close"></i></a>
+     </div>
+
+    {% if folder_object %}
+     <div class="modal-body grid-13">
+         <p>
+             <label for="folder_name">Folder Name:</label>
+             <input type="text" id="obj_id" name="folder_name" maxlength="260" 
value="{{folder_object.folder_name}}" />
+             <input type="hidden" id="obj_type" value="folder" />
+             <span id="error_message" class="modal-form-error"></span>
+         </p>
+     </div>
+     <input type="hidden" name="folder_id" id='edit_folder_id' 
value="{{folder_object._id}}" />
+    {% elif file_object %}
+     <div class="modal-body grid-13">
+         <p>
+             <label for="file_name">File Name:</label>
+             <input type="text" id="obj_id" name="file_name" maxlength="260" 
value="{{file_object.filename}}" />
+             <input type="hidden" id="obj_type" value="file" />
+             <span id="error_message" class="modal-form-error"></span>
+         </p>
+     </div>
+     <input type="hidden" name="file_id" id='edit_file_id' 
value="{{file_object._id}}" />
+    {% endif %}
+
+     <div class="modal-footer grid-13">
+         <p>
+                 <input type="submit" id="submit_btn" value="Save"/>
+                 <a href="#" class="close">Cancel</a>
+         </p>
+     </div>
+     </div>
+ </div>
+ {{lib.csrf_token()}}
+ </form>
+</div>
+
+
diff --git a/ForgeFiles/forgefiles/templates/files.html 
b/ForgeFiles/forgefiles/templates/files.html
new file mode 100755
index 0000000..91a0d41
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/files.html
@@ -0,0 +1,193 @@
+{#-
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-#}
+
+{% extends g.theme.master %}
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+{% import 'forgeactivity:templates/macros.html' as am with context %}
+
+{% do g.register_app_css('css/files.css', compress=False) %}
+
+{% block title %}
+{{c.project.name}} / {{c.app.config.options.mount_label}} / File Upload
+{% endblock %}
+
+{% macro path_links(parts, urls) %}
+  {% if parts != [''] %}
+      <a href="{{c.app.url}}"><b class="fa fa-folder-open-o" title="Root 
directory"></b></a> /
+  {% endif %}
+  {% for part in parts %}
+    {% if part != '...' and not loop.last %}
+        <a href="{{ urls[part] }}"> 
+                {{part}} 
+        </a> /
+    {% else %}
+        {{ part }} {% if not loop.last %} / {% endif %}
+    {% endif %}
+  {% endfor %}
+{% endmacro %}
+
+{% block header %}
+Files {% if folder_object %} - {{path_links(folder_path.split('/'), urls)}} {% 
endif %}
+{% endblock %}
+
+{% block actions %}
+{% if c.user and c.user != c.user.anonymous() %}
+{{c.subscribe_form.display(value=subscribed, action='subscribe', 
style='icon')}} 
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+
+{% if not c.user.is_anonymous() %}
+
+<div class="main_div">
+
+     {% if h.has_access(c.project, 'admin')() %}
+         <div class="upload_buttons" >
+            <a {% if folder_object %} 
href="{{c.app.url}}get_parent_for_create_folder?folder_id={{folder_object._id}}"
 {% else %} href="{{c.app.url}}get_parent_for_create_folder" {% endif %} 
class="admin_modal">
+             <button id="create_folder" type= "button" class="btn btn-info 
btn-lg" {% if folder_object.disabled %} disabled {% endif %} >Create 
Folder</button>
+            </a>
+            <a {% if folder_object %} 
href="{{c.app.url}}get_parent_for_upload_file?folder_id={{folder_object._id}}" 
{% else %} href="{{c.app.url}}get_parent_for_upload_file" {% endif %} 
class="admin_modal">
+             <button id="add_files" type= "button" class="btn btn-info btn-lg" 
{% if folder_object.disabled %} disabled {% endif %} >Add File</button>
+            </a>
+         </div>
+     {% endif %}
+
+
+             {% if file_objects or folder_objects %}
+             <table>
+             <colgroup>
+                <col class="name_col">
+                <col class="date_col">
+                <col class="size_col">
+                <col class="author_col">
+                {% if h.has_access(c.project, 'admin')() %}
+                <col class="actions_col">
+                {% endif %}
+             </colgroup>
+                 <thead>
+                     <tr>
+                         <th>File</th>
+                         <th>Date</th>
+                         <th>Size</th>
+                         <th>Author</th>
+                         {% if h.has_access(c.project, 'admin')() %}
+                             <th>Actions</th>
+                         {% endif %}
+
+                     </tr>
+                 </thead>
+                 <tbody>
+                 {% for folder in folder_objects %}
+                 <tr {% if not folder.disabled %} 
title="{{folder.folder_name}}" {% else %} class="{{folder.folder_name}}"
+                 title="{{folder.folder_name}} is disabled" {% endif %} >
+                 <td {% if folder.disabled %} class="folder_dis" {% endif %} 
title="Click to enter {{folder.folder_name}}">
+                     {% set icon_name = 'folder' %}
+
+                    <a class="icon" href="{{folder.url()}}" title="Click to 
enter {{folder.folder_name}}">
+                        <i class="fa 
fa-folder"></i>&nbsp;{{folder.folder_name|truncate(30)}}
+                    </a>
+
+                 </td>
+                 <td>{{lib.abbr_date(folder.created_date)}}</td>
+                 <td></td>
+                 <td 
title="{{folder.created_by().display_name}}">{{folder.created_by().display_name|truncate(30)}}</td>
+                 {% if h.has_access(c.project, 'admin')() %}
+                 <td>
+                     <div class="folder_actions">
+                        <a data-toggle="tooltip" {% if not folder.disabled %} 
title="Publish" class="admin_modal" href= 
"{{c.app.url}}get_publishable_folder?folder_id={{folder._id}}" {% else %} 
class="disable_object" {% endif %} ><i class= "fa fa-share" {% if 
folder.disabled %} onClick="return false"{% endif %} ></i></a>
+
+                        <a data-toggle="tooltip " {% if not folder.disabled %} 
class="edit_icon admin_modal" 
href="{{c.app.url}}get_editable_object?object_id={{folder._id}}" title="Edit" 
{% else %} class="disable_object" {% endif %}><i class="fa fa-edit" 
data-toggle='modal'></i></a>
+                         <a data-toggle="tooltip " {% if not folder.disabled 
%} class="delete_icon admin_modal" 
href="{{c.app.url}}get_deletable_object?object_id={{folder._id}}" 
title="Delete" {% else %} class="disable_object" {% endif %}><i class="fa 
fa-trash-o" data-toggle='modal'></i></a>
+
+
+                         <a href="#" data-toggle="tooltip" {% if 
folder.disabled %} title="Enable" {% else %} title="Disable" {% endif %} 
onclick="ConfirmDisableFolder('{{ folder._id }}', '{{folder.disabled}}', 
'{{folder.parent_folder.disabled}}','{{c.app.url}}disable_folder')" ><i {% if 
folder.disabled %} class="fa fa-undo" {% else %} class="fa fa-ban" {% endif 
%}></i></a>
+
+
+                     </div>
+                 </td>
+                 {% endif%}
+
+                 </tr>
+                 {% endfor %}
+
+                 {% for file in file_objects %}
+                 <tr {% if not file.disabled %} title="{{file.filename}}" {% 
set url = file.url() %} {% else %} 
+                 title="{{file.filename}} is disabled" {% set url = '#' %} {% 
endif %} >
+                 <td {% if file.disabled %} class="file_dis" {% endif %} >
+                     {% set icon_name = 'file' %}
+                     <a class="icon" {% if not file.disabled %} 
href="{{file.url()}}" title="Click to download {{file.filename}}" {% else %} 
href="#" {% endif %} >
+                        <i class="fa 
fa-file-o"></i>&nbsp;{{file.filename|truncate(30)}}
+                    </a>
+
+
+                 </td>
+                 <td>{{lib.abbr_date(file.created_date)}}</td>
+                 <td>{{file.file_size}}</td>
+                 <td 
title="{{file.uploaded_by().display_name}}">{{file.uploaded_by().display_name|truncate(30)}}</td>
+                {% if h.has_access(c.project, 'admin')() %}
+                 <td>
+                     <div class="file_actions">
+                         <a data-toggle="tooltip" {% if not file.disabled  %}  
href= "#" {% if not file.linked_to_download%} title="Link" {% else %} 
title="Unlink" {% endif %} class="link_icon" onclick="ConfirmLinkFile('{{ 
file._id }}', '{{ file.linked_to_download }}', '{{c.app.url}}link_file')" {% 
else %} class="disable_object" {% endif %} ><i class= "fa fa-link" {% if 
file.linked_to_download %} id="disable_link" {% endif %}></i></a>
+                         <a data-toggle="tooltip " {% if not file.disabled %} 
class="edit_icon admin_modal" 
href="{{c.app.url}}get_editable_object?object_id={{file._id}}" title="Edit" {% 
else %} class="disable_object" {% endif %}><i class="fa fa-edit" 
data-toggle='modal' ></i></a>
+
+                         <a data-toggle="tooltip " {% if not file.disabled %} 
class="delete_icon admin_modal" 
href="{{c.app.url}}get_deletable_object?object_id={{file._id}}" title="Delete" 
{% else %} class="disable_object" {% endif %}><i class="fa fa-trash-o" 
data-toggle='modal' {% if not file.disabled %} {% endif %} ></i></a>
+
+                         <a href="#" data-toggle="tooltip" {% if file.disabled 
%} title="Enable" {% else %} title="Disable" {% endif %} 
onclick="ConfirmDisableFile('{{ file._id }}', '{{file.disabled}}', 
'{{file.parent_folder.disabled}}','{{c.app.url}}disable_file')" ><i {% if 
file.disabled %} class="fa fa-undo" {% else %} class="fa fa-ban" {% endif 
%}></i></a>
+
+                     </div>
+                 </td>
+                 {% endif %}
+                 </tr>
+                 {% endfor %}
+                 </tbody>
+             </table>
+             {% else %}
+             <div class="no_objects" >
+                 <i>No file or folder has been uploaded yet.</i>
+             </div>
+             {% endif %}
+
+             {% for file in file_objects %}
+                {% if not file.disabled  %}
+                    {% set name, text = file.readme() %}
+                    {% if name %}
+                        <h1 id="readme"> Read Me </h1>
+                        {{h.render_any_markup(name, text)}}
+                    {% endif %}
+                {% endif %}
+             {% endfor %}
+
+     
+</div>
+
+{% else %}
+ <p> Please login to upload a project file </p>
+{% endif %}
+
+{% endblock %}
+
+
+{% block extra_js %}
+
+<script type="text/javascript" src="{{g.app_static('js/files.js')}}"></script>
+
+{% endblock %}
+
diff --git a/ForgeFiles/forgefiles/templates/mail.html 
b/ForgeFiles/forgefiles/templates/mail.html
new file mode 100644
index 0000000..9466b5a
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/mail.html
@@ -0,0 +1,34 @@
+{#
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+#}
+
+Dear {{user_object.display_name}},
+
+
+A new release of {{project.name}} is available now!
+
+Release Notes from project owner {{project_owner.display_name}} : {{remarks}}
+
+Please check out the release @ <{{base_url}}{{folder_object.url()}}>
+
+
+---
+
+Sent from {{domain}} because you indicated interest in <{{ base_url }} >
+
+To unsubscribe from further messages, please visit <{{ base_url 
}}/auth/subscriptions/>
diff --git a/ForgeFiles/forgefiles/templates/publish_folder.html 
b/ForgeFiles/forgefiles/templates/publish_folder.html
new file mode 100644
index 0000000..c4bd240
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/publish_folder.html
@@ -0,0 +1,69 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% do g.register_css('css/files.css', compress=False) %}
+
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+
+    <h1 class="title" >
+        <span> Publish Folder </span>
+    </h1>
+
+  <!-- popup for publish folder-->
+<div id="publish_folder_popup"  >
+<form id ='publish_folder_form' method="post" 
action="{{c.app.url}}publish_folder" onsubmit="return ConfirmPublishFolder();" >
+<div class="modal-dialog">
+    <!-- Modal content-->
+    <div class="modal-content">
+    <div class="modal-header">
+        <a class="icon close" href="#" title="Close" data-dismiss="modal"><i 
class="fa fa-close"></i></a>
+    </div>
+    <div class="modal-body grid-13">
+        <p>
+    
+            <label for="folder_name" class="folder_name_label" >Release 
Notes:</label>
+            <textarea type="text-area" id="remarks_id" name="remarks" rows="4" 
cols="50" maxlength="100" {% if not status %} disabled {% endif %} 
></textarea><br />
+
+            <span id="error_message" class="modal-form-error">
+                {% if not status %} To publish this folder, please link a file 
under it to the Download button {% endif %} 
+                {% if status and folder_object.published %} This folder has 
been already published. Please publish again only if there are any significant 
changes. {% endif %}
+            </span>
+        </p>
+    </div>
+    <input type="hidden" name="folder_id" id='publish_folder_id' 
value="{{folder_object._id}}" />
+    <input type="hidden" id='parent_publish_status' value="{{status}}" />
+    <input type="hidden" id='publish_status' 
value="{{folder_object.published}}" />
+
+    <div class="modal-footer grid-13">
+        <p>
+                 <input type="submit" id="submit_btn" value="Ok" {% if not 
status %} disabled {% endif %} />
+                 <a href="#" class="close">Cancel</a>
+        </p>
+    </div>
+    </div>
+</div>
+{{lib.csrf_token()}}
+</form>
+</div>
+
+
+<script type="text/javascript" 
src="{{g.app_static('js/publish_folder.js')}}"></script>
+
+
+
diff --git a/ForgeFiles/forgefiles/templates/upload_file.html 
b/ForgeFiles/forgefiles/templates/upload_file.html
new file mode 100644
index 0000000..1dd109d
--- /dev/null
+++ b/ForgeFiles/forgefiles/templates/upload_file.html
@@ -0,0 +1,55 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+
+{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
+<script type="text/javascript" 
src="{{g.app_static('js/upload_files.js')}}"></script>
+
+
+    <h1 class="title" >
+        <span> Add File </span>
+    </h1>
+
+ <!-- popup for add files-->
+ <div id="file_popup" >
+     <form {% if folder_object %} action="{{folder_object.url()}}/upload_file" 
{% else %} action="{{c.app.url}}upload_file" {% endif %} method="post"  
enctype="multipart/form-data" onsubmit="return validateFileForm()" >
+     <div class="modal-dialog">
+         <!-- Modal content-->
+         <div class="modal-content">
+           <div class="modal-header">
+             <a class="icon close" href="#" title="Close" 
data-dismiss="modal"><i class="fa fa-close"></i></a>
+          </div>
+           <div class="modal-body grid-13">
+             <p>
+             <input type="file" id="file_input" name="file_upload" />
+             <span id="error_message" class="modal-form-error"></span>
+             </p>
+           </div>
+         <input type="hidden" name="parent_folder_id" {% if folder_object %} 
value="{{folder_object._id}}" {% else %} value="None" {% endif %} />
+         <input type="hidden" name="filename" id="filename" value="" />
+           <div class="modal-footer grid-13">
+             <p>
+                 <input type="submit" id="submit_btn" value="Save"/>
+                 <a href="#" class="close">Cancel</a>
+             </p>
+           </div>
+         </div>         
+     </div>
+     {{lib.csrf_token()}}
+     </form>
+ </div>
diff --git a/ForgeFiles/forgefiles/tests/__init__.py 
b/ForgeFiles/forgefiles/tests/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeFiles/forgefiles/tests/__init__.py
@@ -0,0 +1,17 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
diff --git a/ForgeFiles/forgefiles/tests/functional/__init__.py 
b/ForgeFiles/forgefiles/tests/functional/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeFiles/forgefiles/tests/functional/__init__.py
@@ -0,0 +1,17 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
diff --git a/ForgeFiles/forgefiles/tests/functional/test_root.py 
b/ForgeFiles/forgefiles/tests/functional/test_root.py
new file mode 100644
index 0000000..569d8a7
--- /dev/null
+++ b/ForgeFiles/forgefiles/tests/functional/test_root.py
@@ -0,0 +1,144 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+from tg import tmpl_context as c
+from nose.tools import assert_true,assert_not_equal,assert_equals
+
+from allura import model as M
+from alluratest.controller import TestController
+
+
+from forgefiles.model.files import UploadFolder
+from forgefiles.model.files import UploadFiles
+
+from testfixtures import TempDirectory
+
+
+class TestFiles(TestController):
+    def setUp(self):
+        TestController.setUp(self)
+
+    def test_files(self):
+        c.user = M.User.by_username('test-admin')
+        r = self.app.get('/files/')
+        assert 'p/test' in r
+
+    def test_create_folder(self):
+        c.user = M.User.by_username('test-admin')
+        self.app.get('/files/')
+        data1 = {'folder_name': 'NewTestFolder'}
+        folder_object = self.app.post('/p/test/files/create_folder/', data1)
+        assert folder_object is not None
+
+    def test_upload_file(self):
+        c.user = M.User.by_username('test-admin')
+        self.app.get('/files/')
+        dir = TempDirectory()
+        path = dir.write('myfile.txt', b'Testing Upload')
+        with open(path, 'rb') as f:
+            file_upload = [('file_upload', 'myfile.txt', f.read())]
+            filename_dict = {'filename':'myfile.txt'}
+            file_object = self.app.post('/p/test/files/upload_file', 
filename_dict, upload_files=file_upload)
+        dir.cleanup()
+        assert file_object is not None
+
+    def test_edit_folder(self):
+        folder = create_folder(self)
+        folder_object = UploadFolder.query.get(folder_name=folder.folder_name)
+        data1 = {'folder_id': str(folder_object._id), 'folder_name': 
'NewFolderName'}
+        self.app.post('/p/test/files/edit_folder', data1)
+        resp = self.app.get('/files/')
+        assert 'NewFolderName' in resp
+
+    def test_edit_file(self):
+        file_object = upload_file(self)
+        db_file_object = UploadFiles.query.get(filename=file_object.filename)
+        data1 = {'file_id': str(db_file_object._id), 'file_name': 
'NewFileName'}
+        self.app.post('/p/test/files/edit_file', data1)
+        resp = self.app.get('/files/')
+        assert 'NewFileName' in resp
+
+    def test_publish_folder(self):
+        create_folder(self)
+        folder_object = UploadFolder.query.get(folder_name='TestFolder')
+        data1 = {'folder_id': str(folder_object._id), 'remarks': 'Publishing 
new Version'}
+        self.app.post('/p/test/files/publish_folder', data1)
+        resp = self.app.get('/files/')
+        print(folder_object.published)
+        assert_equals(folder_object.published, True)
+
+    def test_link_file(self):
+        file_object = upload_file(self)
+        db_file_object = UploadFiles.query.get(filename=file_object.filename)
+        data1 = {'file_id': str(db_file_object._id)}
+        self.app.post('/p/test/files/link_file', data1)
+        resp = self.app.get('/files/')
+        assert_true(str(db_file_object.linked_to_download) in resp)
+
+    def test_disable_folder(self):
+        create_folder(self)
+        folder_object = UploadFolder.query.get(folder_name='TestFolder')
+        data1 = {'folder_id': str(folder_object._id), 'status': 'True'}
+        self.app.post('/p/test/files/disable_folder', data1)
+        resp = self.app.get('/files/')
+        assert_true(str(folder_object.disabled) in resp)
+
+    def test_disable_file(self):
+        file_object = upload_file(self)
+        db_file_object = UploadFiles.query.get(filename=file_object.filename)
+        data1 = {'file_id': str(db_file_object._id), 'status': 'True'}
+        self.app.post('/p/test/files/disable_file', data1)
+        resp = self.app.get('/files/')
+        assert_true(str(db_file_object.disabled) in resp)
+
+    def test_delete_folder(self):
+        create_folder(self)
+        folder_object = UploadFolder.query.get(folder_name='TestFolder')
+        data1 = {'folder_id': str(folder_object._id)}
+        self.app.post('/p/test/files/delete_folder', data1)
+        new_folder_object = UploadFolder.query.get(_id=folder_object._id)
+        assert new_folder_object is None
+
+    def test_delete_file(self):
+        file_object = upload_file(self)
+        db_file_object = UploadFiles.query.get(filename=file_object.filename)
+        data1 = {'file_id': str(db_file_object._id)}
+        self.app.post('/p/test/files/delete_file', data1)
+        new_file_object = UploadFiles.query.get(_id=db_file_object._id)
+        assert new_file_object is None
+
+
+def create_folder(self):
+    c.user = M.User.by_username('test-admin')
+    self.app.get('/files/')
+    data = {'folder_name': 'TestFolder'}
+    self.app.post('/p/test/files/create_folder/', data)
+    folder_object = UploadFolder.query.get(folder_name='TestFolder')
+    return folder_object
+
+
+def upload_file(self):
+    c.user = M.User.by_username('test-admin')
+    self.app.get('/files/')
+    dir = TempDirectory()
+    path = dir.write('myfile.txt', b'Testing Upload')
+    with open(path, 'rb') as f:
+        file_upload = [('file_upload', 'myfile.txt', f.read())]
+        filename_dict = {'filename':'myfile.txt'}
+        self.app.post('/p/test/files/upload_file', filename_dict, 
upload_files=file_upload)
+    file_object = UploadFiles.query.get(filename='myfile.txt')
+    dir.cleanup()
+    return file_object
diff --git a/ForgeFiles/forgefiles/tests/model/__init__.py 
b/ForgeFiles/forgefiles/tests/model/__init__.py
new file mode 100644
index 0000000..b91b746
--- /dev/null
+++ b/ForgeFiles/forgefiles/tests/model/__init__.py
@@ -0,0 +1,51 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from tg import tmpl_context as c
+from ming.orm.ormsession import ThreadLocalORMSession
+
+from allura.websetup import bootstrap
+from allura.lib import helpers as h
+from allura.lib import plugin
+from allura import model as M
+from alluratest.controller import setup_basic_test
+
+
+def setUp():
+    setup_basic_test()
+
+
+class FilesTestWithModel(object):
+
+    def setUp(self):
+        bootstrap.wipe_database()
+        project_reg = plugin.ProjectRegistrationProvider.get()
+        c.user = bootstrap.create_user('Test User')
+        neighborhood = M.Neighborhood(name='Projects', url_prefix='/p/',
+                                      features=dict(private_projects=False,
+                                                    max_projects=None,
+                                                    css='none',
+                                                    google_analytics=False))
+        project_reg.register_neighborhood_project(neighborhood, [c.user])
+        c.project = neighborhood.register_project('test', c.user)
+        c.project.install_app('Files', 'files')
+        ThreadLocalORMSession.flush_all()
+        h.set_context('test', 'files', neighborhood='Projects')
+
+    def tearDown(self):
+        ThreadLocalORMSession.close_all()
+
diff --git a/ForgeFiles/forgefiles/tests/model/test_files.py 
b/ForgeFiles/forgefiles/tests/model/test_files.py
new file mode 100644
index 0000000..6af03e2
--- /dev/null
+++ b/ForgeFiles/forgefiles/tests/model/test_files.py
@@ -0,0 +1,54 @@
+'''This module is added for testing the files model '''
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from nose.tools import assert_equal, assert_true, assert_false
+
+from forgefiles.tests.model import FilesTestWithModel
+from forgefiles.model.files import UploadFolder
+from forgefiles.model.files import UploadFiles
+
+
+
+class TestUpload(FilesTestWithModel):
+    ''' Test class for UploadFolder & uploadFiles model'''
+
+    def test_upload_folder(self):
+        '''Creates an object of the UploadFolder Collction and tests its 
fields'''
+        upload_folder = UploadFolder
+        upload_folder.folder_name = 'testFolder'
+        upload_folder.published = True
+        upload_folder.remark = 'Publishing new Version'
+        upload_folder.disabled = False
+        assert_equal(upload_folder.folder_name, 'testFolder')
+        assert_true(upload_folder.published)
+        assert_equal(upload_folder.remark, 'Publishing new Version')
+        assert_false(upload_folder.disabled)
+
+    def test_upload_file(self):
+        '''Creates an object of the UploadFiles Collction and tests its 
fields'''
+
+        upload_file = UploadFiles
+        upload_file.filename = 'testFile'
+        upload_file.filetype = 'project_file'
+        upload_file.file_url = 'TestFolder/testFile'
+        upload_file.linked_to_download = True
+        upload_file.published = False
+        assert_equal(upload_file.filename, 'testFile')
+        assert_equal(upload_file.filetype, 'project_file')
+        assert_true(upload_file.linked_to_download)
+        assert_false(upload_file.published)
diff --git a/ForgeFiles/forgefiles/tests/test_files_roles.py 
b/ForgeFiles/forgefiles/tests/test_files_roles.py
new file mode 100644
index 0000000..203f331
--- /dev/null
+++ b/ForgeFiles/forgefiles/tests/test_files_roles.py
@@ -0,0 +1,53 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from nose.tools import assert_equal
+from tg import tmpl_context as c, app_globals as g
+
+from alluratest.controller import setup_basic_test, setup_global_objects
+from allura import model as M
+from allura.lib import security
+from allura.tests import decorators as td
+from allura.lib import helpers as h
+
+
+def setUp():
+    setup_basic_test()
+    setup_with_tools()
+
+
[email protected]_tool('u/test-user-1', 'files')
[email protected]_user_project('test-user-1')
+def setup_with_tools():
+    setup_global_objects()
+    h.set_context('test', neighborhood='Projects')
+    c.project.install_app('files', 'files')
+    g.set_app('files')
+
+
+def test_role_assignments():
+    admin = M.User.by_username('test-admin')
+    user = M.User.by_username('test-user')
+    anon = M.User.anonymous()
+
+    def check_access(perm):
+        pred = security.has_access(c.app, perm)
+        return pred(user=admin), pred(user=user), pred(user=anon)
+    assert_equal(check_access('read'), (True, True, True))
+    assert_equal(check_access('create'), (True, False, False))
+    assert_equal(check_access('update'), (True, False, False))
+    assert_equal(check_access('delete'), (True, False, False))
diff --git a/ForgeFiles/setup.py b/ForgeFiles/setup.py
new file mode 100755
index 0000000..a3dbaa9
--- /dev/null
+++ b/ForgeFiles/setup.py
@@ -0,0 +1,32 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+#       !/bin/python
+from setuptools import setup
+
+setup(name='ForgeFiles',
+      version='0.1.0',
+      description="",
+      packages=['forgefiles'],
+      include_package_data=True,
+      zip_safe=True,
+      entry_points="""
+      # -*- Entry points: -*-
+      [allura]
+      Files=forgefiles.files_main:FilesApp
+      """,
+      )

Reply via email to