<file_sep>import unittest
from cloudr.utils import change_path_prefix


class TestUtils(unittest.TestCase):

    def test_change_path_prefix(self):
        self.assertEqual(change_path_prefix('/123', '/', '/'), '/123')
        self.assertEqual(change_path_prefix('/123/234', '/123', '/123'), '/123/234')
        self.assertEqual(change_path_prefix('/123', '/', '/234'), '/234/123')
        self.assertEqual(change_path_prefix('/123', '/', '/123'), '/123/123')
        self.assertEqual(change_path_prefix('/123/345', '/123', '/'), '/345')
        self.assertEqual(change_path_prefix('/123/234/345', '/123/234', '/456'), '/456/345')
        self.assertEqual(change_path_prefix('/123/345', '/123', '/234/456'), '/234/456/345')


if __name__ == "__main__":
    unittest.main()
<file_sep>import os
import json


_config = json.load('config.json')
_CURRENT_DIR = os.path.split(__file__)[0]


ARIA2_HOST = _config['aria2']['host']
ARIA2_PORT = _config['aria2']['port']
ARIA2_TOKEN = _config['aria2']['token']

REDIS_HOST = _config['redis']['host']
REDIS_PORT = _config['redis']['port']
REDIS_URL = 'redis://{}:{}'.format(REDIS_HOST, REDIS_PORT)  # 'redis://localhost:6379'

SQLITE_URL = 'sqlite:///' + _CURRENT_DIR + os.sep + 'db.sqlite3'

SPLIT_SIZE = _config['split_size']

FILE_PATH = os.path.join(os.path.abspath(_CURRENT_DIR, os.path.pardir), '.files')
<file_sep>import unittest
from cloudr import create_app

class TestDatabase(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.app_context = self.app.app_context()
        self.app_context.push()
    
    def test_app_exsits(self):
        self.assertFalse(self.app is None)


if __name__ == "__main__":
    unittest.main()<file_sep>from flask import Blueprint, render_template, request, redirect, jsonify
from cloudr.model import Users
from flask_login import login_user


view_bp = Blueprint("views", __name__)


@view_bp.route("/login", methods=["POST", "GET"])
def login():
    if request.method == "POST":
        user_name = request.form['username']
        password = request.form['password']
        user = Users.query.filter(Users.username == user_name).first()
        if user and user.password == password:
            login_user(user)
            return redirect('/app')
        else:
            return jsonify({"result": "invalid name or password."})

    return render_template('login.html')

# debug with webpack dev server $.post('/login', 'username=lee&password=<PASSWORD>')


@view_bp.route('/video')
def video():
    return render_template('video.html')
<file_sep>"""
this file is used for debug flask in vscode.
"""

from cloudr import create_app


app = create_app()
app.run(debug=True, use_reloader=False)<file_sep>from .aria2 import Aria2
<file_sep>import unittest
from cloudr.utils.filetype import check_file_type


class TestFileType(unittest.TestCase):
    OTHER_TYPE = "other"
    IMAGE_TYPE = "image"
    VIDEO_TYPE = "video"
    AUDIO_TYPE = "audio"
    DOCUMENT_TYPE = "document"

    def test_other_type(self):
        self.assertEqual(check_file_type("myfile"), self.OTHER_TYPE)
        self.assertEqual(check_file_type("myfile.123"), self.OTHER_TYPE)

    def test_image_type(self):
        self.assertEqual(check_file_type("myfile.jpg"), self.IMAGE_TYPE)
        self.assertEqual(check_file_type("/favoico.ICO"), self.IMAGE_TYPE)

    def test_video_type(self):
        self.assertEqual(check_file_type("/foo/bar/myfile.mP4"), self.VIDEO_TYPE)

    def test_audio_type(self):
        self.assertEqual(check_file_type("/etc/myfile.mp4.fLaC"), self.AUDIO_TYPE)

    def test_document_type(self):
        self.assertEqual(check_file_type("C:\\myfile.pdf"), self.DOCUMENT_TYPE)


if __name__ == "__main__":
    unittest.main()
<file_sep>// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import $ from 'jquery'
import App from './App'
import 'bootstrap'
import { store } from './store'
import '@mdi/font/css/materialdesignicons.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'animate.css/animate.css'

Vue.config.productionTip = false

function resizeListContainer(event) {
    var listContainer = document.querySelector(".list-container")
    var rec = listContainer.getBoundingClientRect()
    var pageHeight = window.innerHeight
    listContainer.style.height = (pageHeight - rec.top) + "px"
};

window.onresize = resizeListContainer
window.$ = $

$(document).ready(function() {
    resizeListContainer()
})

/* eslint-disable no-new */
new Vue({
    store,
    el: '#app',
    components: { App },
    render: h => h(App)
})
<file_sep>from .app import app_bp
from .views import view_bp
<file_sep>from cloudr.config import REDIS_URL

broker_url = REDIS_URL
result_backend = REDIS_URL

task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']

beat_schedule = {
    'refresh': {
        'task': 'task.download.refresh',
        'schedule': 10.0
    }
}
<file_sep>import Vue from 'vue'
import Vuex from 'vuex'
import { apiDeleteResource, apiGetPathContent, apiNewDirectory, apiRenameResource, apiPasteFiles, apiSearch } from "../lib/api"

Vue.use(Vuex)

export const store = new Vuex.Store({
    state: {
        path: '/',
        files: [],
        select: [],
        notification: [], // notification message like {level: 'info', content: 'this is a info'}
        operateFiles: [], // this array includes the selected operate files
        imgModalAttr: { fileName: '', show: false }
    },
    getters: {
        pathNameArray(state) {
            if (state.path === '/') {
                return ['Home']
            }

            let paths = state.path.split('/')
            paths[0] = 'Home'
            return paths
        },
        isFileItemSelected({ select }) {
            return select.every((v) => !v)
        },
        formatPath(state, { pathNameArray }) {
            return pathNameArray.join('/')
        },
        hasMessage({ notification }) {
            return (notification.length > 0)
        },
        firstMessage({ notification }) {
            return notification[0]
        }
    },
    mutations: {
        enterDir(state, { dirName }) {
            if (state.path.endsWith('/')) {
                state.path += dirName
            }
            else {
                state.path += ('/' + dirName)
            }
        },
        enterParentDir(state, { parentDirName, index }) {
            if (state.path !== '/') {
                let path = state.path.toString()
                let nameArr = path.split('/')
                nameArr[0] = 'Home'
                parentDirName = parentDirName.toString().replace(/\//g, '') // remove / in upperDirName
                if (nameArr[index] === parentDirName) {
                    path = nameArr.slice(0, index + 1).join('/')
                    path = path.startsWith('Home') ? path.replace('Home', '') : path
                    path = (path === '') ? '/' : path
                    state.path = path
                }
            }
        },
        changeFiles({ files, select }, { newFiles }) {
            let length = files.length > newFiles.length ? files.length : newFiles.length
            newFiles.sort(compareFileName)
            files.splice(0, length, ...newFiles)
            select.splice(0, length, ...getFalseIter(newFiles.length))
        },
        deleteFiles({ files }, { indexList }) {
            indexList.sort().reverse()
            for (let index of indexList) {
                files.splice(index, 1)
            }
        },
        addDir({ files }, { file }) {
            files.push(file)
        },
        rename({ files }, { newName, index }) {
            let file = files[index]
            file.fileName = newName
            files.splice(index, 1, file)
        },
        selectOne({ select }, { index, value }) {
            if (typeof (value) !== 'boolean') {
                throw TypeError('Value must be boolean.')
            }
            select.splice(index, 1, value)
        },
        selectAll({ select }, { value }) {
            if (typeof (value) !== 'boolean') {
                throw TypeError('Value must be boolean.')
            }
            if (value === true) {
                select.splice(0, select.length, ...getTrueIter(select.length))
            }
            else {
                select.splice(0, select.length, ...getFalseIter(select.length))
            }
        },
        pushMessage({ notification }, payload) {
            notification.push(payload)
        },
        deleteLatestMessage({ notification }) {
            if (notification.length > 0) {
                notification.shift()
            }
        },
        modifyOperateFiles({ operateFiles }, { files }) {
            let length = operateFiles.length > files.length ? operateFiles.length : files.length
            if (files.length > 0) {
                operateFiles.splice(0, length, ...files)
            }
            else {
                operateFiles.splice(0, length)
            }
        },
        imgModal({ imgModalAttr }, { show, fileName }) {
            imgModalAttr.show = show
            if (fileName !== undefined) {
                imgModalAttr.fileName = fileName
            }
        }
    },
    actions: {
        enterDir(context, payload) {
            context.commit('enterDir', payload)
            context.dispatch('getCurrentPathContent')
        },
        enterParentDir(context, payload) {
            context.commit('enterParentDir', payload)
            context.dispatch('getCurrentPathContent')
        },
        async getCurrentPathContent(context) {
            let path = context.state.path
            try {
                let response = await apiGetPathContent(path)
                let newFiles = response.files
                context.commit('changeFiles', { newFiles })
            }
            catch (e) {
                let content = ''
                if (e.status === 504) {
                    content = '服务器无响应，请稍后刷新再试。'
                }
                else {
                    content = '网络出现问题，请稍后刷新再试。'
                }
                let payload = {
                    level: 'warning',
                    content: content
                }
                context.dispatch('publishMessage', payload)
            }
        },
        async deleteFiles(context, {path, fileList, indexList}) {
            let response = await apiDeleteResource(path, fileList)
            if (response.result === 'success') {
                context.commit('deleteFiles', { indexList })
            }
        },
        async newDirectory(context, {path, dirName}) {
            let response = await apiNewDirectory(path, dirName)
            if (response.result === 'success') {
                context.commit('addDir', { file: response.file })
            }
            return response
        },
        async rename(context, {path, oldName, newName, index}) {
            let response = await apiRenameResource(path, oldName, newName)
            if (response.result === 'success') {
                context.commit('rename', { newName, index })
            }
        },
        selectOne(context, payload) {
            context.commit('selectOne', payload)
        },
        selectAll(context, payload) {
            context.commit('selectAll', payload)
        },
        publishMessage({ commit, dispatch }, payload) {
            commit('pushMessage', payload)
            setTimeout(() => dispatch('deleteLatestMessage'), 5000)
        },
        deleteLatestMessage({ commit }) {
            commit('deleteLatestMessage')
        },
        modifyOperateFiles({ commit, dispatch }, payload) {
            commit('modifyOperateFiles', payload)
        },
        async pasteFiles({ dispatch }, { fileNames, path, newPath }) {
            let response = await apiPasteFiles(fileNames, path, newPath)
            return response
        },
        async search({ commit, dispatch }, { query, path }) {
            commit('enterDir', { dirName: 'search' })
            let response = await apiSearch(query, path)
            if (response.result === 'success') {
                commit('changeFiles', { newFiles: response.files })
            }
            return response
        }
    }
})

const fileType = ['directory', 'image', 'video', 'audio', 'document', 'zip', 'other']

function compareString(str1, str2) {
    if (str1 > str2) {
        return 1
    }
    else if (str1 < str2) {
        return -1
    }
    else {
        return 0
    }
}

function compareFileName(f1, f2) {
    if (f1.fileType === 'directory' && f2.fileType !== 'directory') {
        return 1
    }
    else if (f1.fileType !== 'directory' && f2.fileType === 'directory') {
        return -1
    }
    else {
        return compareString(f1.fileName, f2.fileName)
    }
}

function compareFileNameWithTime(f1, f2) {
    if (f1.fileType === 'directory' && f2.fileType === 'directory') {
        return compareString(f1.modifiedTime, f2.modifiedTime)
    }
    else if (f1.fileType === 'directory' && f2.fileType !== 'directory') {
        return 1
    }
    else if (f1.fileType !== 'directory' && f2.fileType === 'directory') {
        return -1
    }
    else {
        let rv = compareString(f1.modifiedTime, f2.modifiedTime)
        if (rv === 0) {
            return compareString(f1.fileName, f2.fileName)
        }

        return rv
    }
}

function compareFileNameWithSize(f1, f2) {
    if (f1.fileType === 'directory' && f2.fileType !== 'directory') {
        return 1
    }
    else if (f1.fileType !== 'directory' && f2.fileType === 'directory') {
        return -1
    }
    else {
        let rv = compareString(f1.fileSize, f2.fileSize)
        if (rv === 0) {
            return compareString(f1.fileName, f2.fileName)
        }

        return rv
    }
}

function* getFalseIter(length) {
    for (let i = 0; i < length; i++) {
        yield false
    }
}

function* getTrueIter(length) {
    for (let i = 0; i < length; i++) {
        yield true
    }
}
<file_sep>from .filetype import check_file_type
<file_sep>from flask import Flask
from ..config import SQLITE_URL
from .model import db, Users, File, FileType, OfflineDownload
from .redis_model import redis_db, UploadCache


def create_app():
    app = Flask(__name__)

    app.config['SQLALCHEMY_DATABASE_URI'] = SQLITE_URL
    app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

    db.init_app(app)

    return app


db_app = create_app()
<file_sep>import json
import os

file_types = ['directory', 'image', 'video', 'audio', 'document', 'zip', 'other']

# IMAGE_TYPE = {"jpg", "png", "gif", "webp", "tif", "bmp", "ico", "psd"}
# VIDEO_TYPE = {"mp4", "m4v", "mkv", "mov", "avi", "mpg", "mpg", "rmvb"}
# AUDIO_TYPE = {"mid", "mp3", "m4a", "ogg", "flac", "wav", "amr"}
# ZIP_TYPE = {"zip", "tar", "rar", "gz", "bz2", "7z", "xz"}
# DOCUMENT_TYPE = {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "epub"}

current_pwd = os.path.split(__file__)[0]
with open(current_pwd + os.sep + "filetype.json", 'r') as f:
    TYPE_LIST = json.loads(f.read())


def check_file_type(file_name):
    arr = file_name.split('.')
    if(len(arr) < 2):
        return "other"

    ext_name = arr[-1].lower()
    for typename in TYPE_LIST:
        if(ext_name in TYPE_LIST[typename]):
            return typename

    return "other"
<file_sep>import json
import os
from walrus import Model, Walrus, IntegerField, ListField, TextField, DateTimeField, BooleanField


_config = json.load(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json'))
redis_db = Walrus(host=_config['redis']['host'], port=_config['redis']['port'], db=0)


class UploadCache(Model):
    __database__ = redis_db

    _id = 0
    id = IntegerField(primary_key=True)
    cache = ListField()
    sequence_number = IntegerField(default=0)
    max_sequence_number = IntegerField(default=0)
    # store upload file information
    size = IntegerField(default=0)
    path = TextField(default='/')
    md5 = TextField()
    upload_date = DateTimeField()
    file_name = TextField()
    filetype = IntegerField()
    user_id = IntegerField()
    # store saving task information
    saving_task_start = BooleanField(default=False)

    @classmethod
    def generate_id(cls):
        rv = cls._id
        cls._id += 1
        return rv
<file_sep>from flask import Blueprint, render_template
from flask_login import login_required


app_bp = Blueprint('app', __name__, static_folder='../app/dist/static', template_folder='../app/dist', url_prefix='/app')


@app_bp.route('/', methods=['GET'])
@login_required
def app():
    return render_template('index.html')
<file_sep>import os
import math
from datetime import datetime
from werkzeug.utils import secure_filename 
from flask import send_from_directory, current_app, request, jsonify, session, abort
from flask_login import current_user
from cloudr import model
from cloudr.model import db, File, Users, UploadCache
from cloudr.utils.filetype import check_file_type
from cloudr.utils import api_result, SUCCESS_RESULT, FAILURE_RESULT
from cloudr.config import SPLIT_SIZE
from . import bp


# @bp.route("/downloads/<filemd5>", methods=["POST", "GET"])
# def file_download(filemd5):
#     return send_from_directory(current_app.config['FILE_PATH'], filemd5)


@bp.route("/download", methods=["GET"])
def file_download():
    user_name = 'lee'
    path = request.args.get('path')
    filename = request.args.get('filename')
    file = File.query.join(Users).filter(
        Users.username == user_name,
        File.filename == filename,
        File.path == path
    ).first()
    if not file:
        abort(404)
    return send_from_directory(current_app.config['FILE_PATH'], file.md5, as_attachment=True, attachment_filename=filename, conditional=True)


@bp.route("/uploads", methods=["POST"])
def file_upload():
    blob = request.files['blob']
    try:
        uc = UploadCache.load(request.form['task_id'])
    except Exception:
        return jsonify(api_result(FAILURE_RESULT, 'upload task may be expired. restart a new upload later.'))

    uc.cache.append(blob)
    if not uc.saving_task_start:
        # todo: start saving task
        pass
    return jsonify(api_result(SUCCESS_RESULT))


@bp.route('/upload-request', methods=["POST"])
def upload_request():
    try:
        user_id = current_user.id
    except AttributeError:
        abort(404)
    file = request.files['file']
    size = int(request.form['filesize'])
    path = request.form['path']
    md5 = request.form['md5']
    upload_date = datetime.now()
    file_name = file.filename
    file_type_name = check_file_type(file_name)
    filetype = model.FileType.query.filter(model.FileType.filetype == file_type_name).first()
    if not filetype:
        return jsonify(api_result("failure", "can't identify the file type."))
    filetype = filetype.id

    max_sequence_number = math.ceil(size / SPLIT_SIZE)
    uc = UploadCache.create(
        id=UploadCache.generate_id(),
        max_sequence_number=max_sequence_number,
        size=size,
        path=path,
        md5=md5,
        upload_date=upload_date,
        file_name=file_name,
        filetype=filetype,
        user_id=user_id
    )

    return jsonify(api_result(SUCCESS_RESULT, task_id=uc.id, split_size=SPLIT_SIZE))
<file_sep>import os
import click
from flask.cli import with_appcontext
from flask import Flask, render_template
from flask_login import LoginManager
from .model import db
from .config import FILE_PATH, SQLITE_URL


login_manager = LoginManager()
login_manager.login_view = '/login'


@login_manager.user_loader
def load_user(user_id):
    from .model import Users
    return Users.query.filter(Users.id == int(user_id)).first()


@click.command("init-db")
@with_appcontext
def init_db_command():
    from . import model
    db.drop_all()
    db.create_all()
    db.session.commit()
    click.echo("Initialized the database.")


def init_file_type_table():
    from . import model
    for file_type in model.FileType.file_types:
        ft = model.FileType(filetype=file_type)
        db.session.add(ft)
    db.session.commit()
    click.echo("Initialized file_type table.")


def add_user(name, password):
    from .model import Users
    u = Users(username=name, password=<PASSWORD>)
    db.session.add(u)
    db.session.commit()
    click.echo("add " + str(u) + " success.")


@click.command('init-table')
@with_appcontext
def init_table():
    init_file_type_table()
    add_user('lee', '123')


def create_app():
    app = Flask(__name__, static_folder='static')

    app.config['SECRET_KEY'] = os.urandom(24)
    app.config['LOGIN_DISABLED'] = False
    app.config['SQLALCHEMY_DATABASE_URI'] = SQLITE_URL
    app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    app.config['FILE_PATH'] = FILE_PATH

    db.init_app(app)
    login_manager.init_app(app)

    from . import api
    from . import file
    from . import views
    app.register_blueprint(api.bp)
    app.register_blueprint(file.bp)
    app.register_blueprint(views.app_bp)
    app.register_blueprint(views.view_bp)

    app.cli.add_command(init_db_command)
    app.cli.add_command(init_table)

    return app
<file_sep>from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin


db = SQLAlchemy()


class Users(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    password = db.Column(db.String(40), nullable=False)

    def __repr__(self):
        return "<({}) {}>".format(self.id, self.username)


class File(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    userid = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    filetype = db.Column(db.Integer, db.ForeignKey("file_type.id"), nullable=False)
    filename = db.Column(db.String(40), nullable=False)
    filesize = db.Column(db.Integer)
    uploaddate = db.Column(db.DateTime, nullable=False)
    path = db.Column(db.Text, nullable=False)
    md5 = db.Column(db.String(40), nullable=False)

    def __repr__(self):
        return "<File ({name}) {path}>".format(name=self.filename, path=self.path)


class FileType(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    filetype = db.Column(db.String(10), nullable=False)

    file_types = ['directory', 'image', 'video', 'audio', 'document', 'zip', 'other']

    def __repr__(self):
        return '<({}) {}>'.format(self.id, self.filetype)


class OfflineDownload(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    userid = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    filename = db.Column(db.String(40))
    path = db.Column(db.Text, nullable=False)
    url = db.Column(db.Text, nullable=False)
    gid = db.Column(db.String(16))
    completed = db.Column(db.Integer, nullable=False)  # from 0 to 100 percent
    done = db.Column(db.Boolean, nullable=False)
    time = db.Column(db.DateTime, nullable=False)
    error = db.Column(db.Integer, nullable=False)
    message = db.Column(db.Text)

    def __repr__(self):
        return '<{} {} {}%>'.format(self.id, self.filename, self.completed)
<file_sep>import os
import hashlib
from datetime import datetime
from flask import jsonify, request, current_app
from cloudr.model import File, Users, FileType, OfflineDownload, db
from . import bp
from task.download import addUri
from cloudr.utils import change_path_prefix
from flask_login import login_required, current_user


@bp.route("/hello", methods=["GET", "POST"])
def hello():
    return jsonify(greet="hello")


@bp.route("/get-path-content", methods=["POST"])
@login_required
def get_path_content():
    user_name = current_user.username
    path = request.json['path']
    files = File.query.join(Users).join(FileType).\
                       filter(Users.username == user_name, File.path == path).\
                       add_columns(FileType.filetype).all()

    rv = {"result": "success", "files": []}
    for index, f in enumerate(files):
        file_item = {}
        file_item['id'] = index
        file_item['fileName'] = f.File.filename
        file_item['fileType'] = f.filetype
        file_item['fileSize'] = f.File.filesize
        file_item['modifiedTime'] = f.File.uploaddate.strftime("%Y-%m-%d %H:%M:%S")
        file_item['path'] = f.File.path
        rv['files'].append(file_item)

    return jsonify(rv)


@bp.route("/delete-resource", methods=["POST"])
@login_required
def delete_resource():
    dir_file_type = FileType.query.filter(FileType.filetype == 'directory').first().id
    user_name = current_user.username
    path = request.json['path']  # todo: path check
    file_list = request.json["fileList"]
    for file_name in file_list:
        file = File.query.join(Users).join(FileType).\
                        filter(Users.username == user_name).\
                        filter(File.path == path).\
                        filter(File.filename == file_name).first()
        if file:
            if file.filetype != dir_file_type:
                real_file_name = current_app.config["FILE_PATH"] + os.sep + file.md5
                File.query.filter(File.id == file.id).delete()
                if os.path.isfile(real_file_name):
                    os.remove(real_file_name)
            else:
                # todo: delete the whole directory
                File.query.filter(File.id == file.id).delete()
        else:
            pass
    db.session.commit()
    return jsonify({"result": "success"})


@bp.route("/new-directory", methods=["POST"])
@login_required
def new_directory():
    user_name = current_user.username
    size = 0
    path = request.json['path']  # todo: path check
    md5 = "-"
    upload_date = datetime.now()
    # file_name = secure_filename(request.json['dirName'])
    file_name = request.json['dirName']  # todo: check file name to void repeat
    file_type = FileType.query.filter(FileType.filetype == 'directory').first().id
    user_id = Users.query.filter(Users.username == user_name).first().id  # todo: catch user_name is not found

    file = File(userid=user_id, filetype=file_type, filename=file_name, filesize=size, uploaddate=upload_date, path=path, md5=md5)
    db.session.add(file)
    db.session.commit()

    file_info = {}
    file_info["id"] = file.id
    file_info["fileName"] = file.filename
    file_info["fileType"] = "directory"
    file_info["fileSize"] = file.filesize
    file_info["modifiedTime"] = file.uploaddate.strftime("%Y-%m-%d %H:%M:%S")

    return jsonify({"result": "success", "file": file_info})


@bp.route("/rename-resource", methods=["POST"])
@login_required
def rename_resource():
    new_name = request.json['newname']
    old_name = request.json['oldname']
    path = request.json['path']
    user_name = current_user.username

    file = File.query.join(Users).filter(Users.username == user_name,
                                       File.path == path,
                                       File.filename == old_name).first()
    File.query.filter(File.id == file.id).update({'filename': new_name})
    db.session.commit()

    return jsonify({"result": "success"})


@bp.route('/offline-download', methods=['POST'])
@login_required
def offline_download():
    params = request.json
    user_name = current_user.username
    user_id = Users.query.filter(Users.username == user_name).first().id
    path = params['path']
    url = params['url']
    time = datetime.now()
    od = OfflineDownload(path=path, url=url, completed=0, time=time, error=0, userid=user_id, done=False)
    db.session.add(od)
    db.session.commit()
    addUri.delay(od.id, [url])

    return jsonify({"result": "success", 'id': od.id})


@bp.route('/move-resource', methods=['POST'])
@login_required
def move_resource():
    params = request.json
    user_name = current_user.username
    user_id = Users.query.filter(Users.username == user_name).first().id
    file_names = params['filenames']
    path = params['path']
    new_path = params['newpath']
    directory_type_id = FileType.query.filter(FileType.filetype == 'directory').first().id
    for file_name in file_names:
        file = File.query.filter(
            File.userid == user_id, File.path == path, File.filename == file_name).first()
        if file.filetype != directory_type_id:
            file.path = new_path
        else:
            path_like = (file.path if file.path != '/' else '') + '/' + file.filename + '%'
            path_prefix = file.path  # this path is the path the need to change
            files = File.query.filter(
                File.userid == user_id,
                File.path.like(path_like)
            ).all()
            for f in files:
                print(change_path_prefix(f.path, path_prefix, new_path), f, sep=',')
                f.path = change_path_prefix(f.path, path_prefix, new_path)
            file.path = new_path
    db.session.commit()

    return jsonify({"result": "success"})


@bp.route('/search', methods=['POST'])
@login_required
def search():
    params = request.json
    path = params['path']
    query_str = params['query']
    user_name = current_user.username
    user_id = Users.query.filter(Users.username == user_name).first().id
    files = File.query.join(FileType).filter(
        File.userid == user_id, File.path.like(path + '%'), File.filename.like('%' + query_str + '%')
    ).add_columns(FileType.filetype).all()

    rv = {"result": "success", "files": []}
    for index, f in enumerate(files):
        file_item = {}
        file_item['id'] = index
        file_item['fileName'] = f.File.filename
        file_item['fileType'] = f.filetype
        file_item['fileSize'] = f.File.filesize
        file_item['modifiedTime'] = f.File.uploaddate.strftime("%Y-%m-%d %H:%M:%S")
        file_item['path'] = f.File.path
        rv['files'].append(file_item)

    return jsonify(rv)
<file_sep>

SUCCESS_RESULT = "success"
FAILURE_RESULT = "failure"


def api_result(result, reason=None, **kwargs):
    rv = {}
    if result == SUCCESS_RESULT:
        rv["result"] = SUCCESS_RESULT
    else:
        rv["result"] = FAILURE_RESULT

    if reason:
        rv["reason"] = reason

    rv.update(kwargs)

    return rv


def change_path_prefix(path, path_prefix, new_path_prefix):
    '''
    this function changes the path's prefix. like
    ('/123/345', '/123', '/') => '/345'
    ('/123/345', '/123', '/222') => '/222/345'
    '''
    if path_prefix not in path or path.index(path_prefix) != 0:
        raise ValueError('path_prefix must in path, or path_prefix must at start of the path.')
    if new_path_prefix == path_prefix:
        return path

    if new_path_prefix == '/':
        return path[len(path_prefix):]
    elif path_prefix == '/':
        return new_path_prefix + path
    else:
        return new_path_prefix + path[len(path_prefix):]
<file_sep># cloud-file-manager
当前还处于初级阶段，只完成了云盘的基本功能，主要包括：
- 文件或文件夹基本操作（新建、上传下载、移动、删除）
- 搜索
- 多用户
- 离线下载（暂时还不支持BT下载）

#### 目前使用到的技术栈：
- 前端：Vue、bootstrap、webpack
- 后端： Python（Flask、Celery）、aria2、redis

![Screen Shot](https://raw.githubusercontent.com/lee88688/cloud-file-manager/master/screen%20shot.PNG)
<file_sep>import os
import shutil
from hashlib import md5
from cloudr.model import db_app, db, OfflineDownload, File, FileType
from .celery import app
from cloudr.utils.aria2 import Aria2
from cloudr.utils.filetype import check_file_type
from cloudr.config import ARIA2_HOST, ARIA2_PORT, ARIA2_TOKEN, MAX_READ_SIZE, FILE_PATH


aria2 = Aria2(ARIA2_HOST, ARIA2_PORT, ARIA2_TOKEN)


def _addUri(id, uris):
    if len(uris) > 1:
        raise TypeError("uris only support one uri.")
    r = aria2.addUri(uris)
    print(r)

    if 'error' in r:
        error_code = r['error']['code']
        error_message = r['error']['message']
        done = True
        OfflineDownload.query.filter(OfflineDownload.id == id).update({
            'error': error_code,
            'message': error_message,
            'done': done
        })
    else:
        gid = r['result']
        OfflineDownload.query.filter(
            OfflineDownload.id == id).update({'gid': gid})
    db.session.commit()

    return r


def add_new_file(id, file_path):
    download_file = OfflineDownload.query.filter(
        OfflineDownload.id == id).first()
    userid = download_file.userid
    filename = download_file.filename
    uploaddate = download_file.time
    # path = download_file.path
    filesize = os.path.getsize(file_path)
    # find the file and calculate the md5.
    with open(file_path, 'rb') as f:
        m = md5()
        r = f.read(MAX_READ_SIZE)
        m.update(r)
        while len(r) > 0:
            r = f.read(MAX_READ_SIZE)
            m.update(r)
        md5_str = m.hexdigest()
    filetype = FileType.query.filter(FileType.filetype == check_file_type(filename)).first()
    if not filetype:
        raise TypeError('can not specify filetype for {}'.format(filename))
    filetype = filetype.id
    file = File(
        userid=userid,
        filetype=filetype,
        filename=filename,
        filesize=filesize,
        uploaddate=uploaddate,
        path=download_file.path,
        md5=md5_str
    )
    db.session.add(file)
    # move the file to destination dir
    new_path = FILE_PATH + os.sep + md5_str
    shutil.move(file_path, new_path)


def _refresh():
    downloads = OfflineDownload.query.filter(
        OfflineDownload.done == False).all()
    for d in downloads:
        r = aria2.getFiles(d.gid)
        if 'error' in r:
            error_code = r['error']['code']
            error_message = r['error']['message']
            d.code = error_code
            d.message = error_message
            d.done = True
            db.session.commit()
            continue
        result = r['result'][0]
        total_length = result['length']
        completed = int(
            float(result['completedLength']) / float(total_length) * 100)
        done = (completed == 100)
        path = result['path']
        if d.filename is None:
            filename = os.path.split(path)[-1]
        else:
            filename = None
        OfflineDownload.query.filter(OfflineDownload.id == d.id).update({
            'filename': filename,
            'path': d.path,  # this path is virtual path for cloudr
            'completed': completed,
            'done': done
        })
        # if done move the file into file dir, and insert a row into File
        if done:
            add_new_file(d.id, path)
        db.session.commit()


@app.task
def addUri(id, uris):
    with db_app.app_context():
        r = _addUri(id, uris)
    return r


@app.task
def refresh():
    # todo: when some files download error, they need to be removed from aria2 and delete files.
    with db_app.app_context():
        _refresh()
<file_sep>from flask import Blueprint


bp = Blueprint("file", __name__, url_prefix="/file")

from . import file_operate