<commit_before>#! /usr/bin/env python
# -*- coding: utf-8 -*-

# pyAggr3g470r - A Web based news aggregator.
# Copyright (C) 2010-2014  <NAME> - http://cedricbonhomme.org/
#
# For more information : https://bitbucket.org/cedricbonhomme/pyaggr3g470r/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

__author__ = "<NAME>"
__version__ = "$Revision: 4.7 $"
__date__ = "$Date: 2010/01/29 $"
__revision__ = "$Date: 2014/04/12 $"
__copyright__ = "Copyright (c) <NAME>"
__license__ = "AGPLv3"

import os
import datetime
from flask import render_template, jsonify, request, flash, session, url_for, redirect, g, current_app, make_response
from flask.ext.login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUserMixin
from flask.ext.principal import Principal, Identity, AnonymousIdentity, identity_changed, identity_loaded, Permission, RoleNeed, UserNeed
from sqlalchemy import desc
from werkzeug import generate_password_hash

import conf
import utils
import export
import feedgetter
import models
#import search as fastsearch
from forms import SigninForm, AddFeedForm, ProfileForm
from pyaggr3g470r import app, db
from pyaggr3g470r.models import User, Feed, Article, Role

Principal(app)
# Create a permission with a single Need, in this case a RoleNeed.
admin_permission = Permission(RoleNeed('admin'))

login_manager = LoginManager()
login_manager.init_app(app)

#
# Management of the user's session.
#
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user

    # Add the UserNeed to the identity
    if hasattr(current_user, 'id'):
        identity.provides.add(UserNeed(current_user.id))

    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        for role in current_user.roles:
            identity.provides.add(RoleNeed(role.name))

@app.before_request
def before_request():
    g.user = current_user
    if g.user.is_authenticated():
        pass
        #g.user.last_seen = datetime.utcnow()
        #db.session.add(g.user)
        #db.session.commit()

@app.errorhandler(403)
def authentication_failed(e):
    flash('Authentication failed.', 'danger')
    return redirect(url_for('login'))

@app.errorhandler(401)
def authentication_failed(e):
    flash('Authentication required.', 'info')
    return redirect(url_for('login'))

@login_manager.user_loader
def load_user(email):
    # Return an instance of the User model
    return User.query.filter(User.email == email).first()

def redirect_url(default='home'):
    return request.args.get('next') or \
            request.referrer or \
            url_for(default)


from functools import wraps
def feed_access_required(func):
    """
    This decorator enables to check if a user has access to a feed.
    The administrator of the platform is able to access to the feeds of a normal user.
    """
    @wraps(func)
    def decorated(*args, **kwargs):
        if kwargs.get('feed_id', None) != None:
            feed = Feed.query.filter(Feed.id == kwargs.get('feed_id', None)).first()
            if (feed == None or feed.subscriber.id != g.user.id) and not g.user.is_admin():
                flash("This feed do not exist.", "danger")
                return redirect(url_for('home'))
        return func(*args, **kwargs)
    return decorated



#
# Views.
#
@app.route('/login/', methods=['GET', 'POST'])
def login():
    """
    Log in view.
    """
    g.user = AnonymousUserMixin()
    form = SigninForm()

    if form.validate_on_submit():
        user = User.query.filter(User.email == form.email.data).first()
        login_user(user)
        g.user = user
        identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
        flash("Logged in successfully.", 'success')
        return redirect(url_for('home'))
    return render_template('login.html', form=form)

@app.route('/logout/')
@login_required
def logout():
    """
    Log out view. Removes the user information from the session.
    """
    # Update last_seen field
    g.user.last_seen = datetime.datetime.utcnow()
    db.session.add(g.user)
    db.session.commit()

    # Remove the user information from the session
    logout_user()

    # Remove session keys set by Flask-Principal
    for key in ('identity.name', 'identity.auth_type'):
        session.pop(key, None)

    # Tell Flask-Principal the user is anonymous
    identity_changed.send(current_app._get_current_object(), identity=AnonymousIdentity())

    flash("Logged out successfully.", 'success')
    return redirect(url_for('login'))

@app.route('/')
@login_required
def home():
    """
    The home page lists most recent articles of all feeds.
    """
    user = User.query.filter(User.email == g.user.email).first()
    result = []
    for feed in user.feeds:
        new_feed = Feed()
        new_feed.id = feed.id
        new_feed.title = feed.title
        new_feed.enabled = feed.enabled
        new_feed.articles = Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed.id).order_by(desc("Article.date")).limit(9)
        result.append(new_feed)
    unread_articles = len(Article.query.filter(Article.user_id == g.user.id, Article.readed == False).all())
    return render_template('home.html', result=result, head_title=unread_articles)

@app.route('/fetch/', methods=['GET'])
@app.route('/fetch/<feed_id>', methods=['GET'])
@login_required
def fetch(feed_id=None):
    """
    Triggers the download of news.
    """
    feed_getter = feedgetter.FeedGetter(g.user.email)
    feed_getter.retrieve_feed(feed_id)
    flash("New articles retrieved.", 'success')
    return redirect(redirect_url())

@app.route('/about/', methods=['GET'])
@login_required
def about():
    """
    'About' page.
    """
    return render_template('about.html')

@app.route('/feeds/', methods=['GET'])
@login_required
def feeds():
    """
    Lists the subscribed  feeds in a table.
    """
    user = User.query.filter(User.email == g.user.email).first()
    feeds = user.feeds
    return render_template('feeds.html', feeds=feeds)

@app.route('/feed/<int:feed_id>', methods=['GET'])
@login_required
@feed_access_required
def feed(feed_id=None):
    """
    Presents detailed information about a feed.
    """
    feed = Feed.query.filter(Feed.id == feed_id).first()
    word_size = 6
    articles = feed.articles.all()
    nb_articles = len(Article.query.filter(Article.user_id == g.user.id).all())
    top_words = utils.top_words(articles, n=50, size=int(word_size))
    tag_cloud = utils.tag_cloud(top_words)

    today = datetime.datetime.now()
    try:
        last_article = articles[0].date
        first_article = articles[-1].date
        delta = last_article - first_article
        average = round(float(len(articles)) / abs(delta.days), 2)
    except:
        last_article = datetime.datetime.fromtimestamp(0)
        first_article = datetime.datetime.fromtimestamp(0)
        delta = last_article - first_article
        average = 0
    elapsed = today - last_article

    return render_template('feed.html', head_title=utils.clear_string(feed.title), feed=feed, tag_cloud=tag_cloud, \
                        first_post_date=first_article, end_post_date=last_article , nb_articles=nb_articles, \
                        average=average, delta=delta, elapsed=elapsed)

@app.route('/article/<int:article_id>', methods=['GET'])
@login_required
def article(article_id=None):
    """
    Presents the content of an article.
    """
    article = Article.query.filter(Article.user_id == g.user.id, Article.id == article_id).first()
    if article != None:
        if not article.readed:
            article.readed = True
            db.session.commit()
        return render_template('article.html', head_title=utils.clear_string(article.title), article=article)
    flash("This article do not exist.", 'warning')
    return redirect(redirect_url())
    

@app.route('/mark_as_read/', methods=['GET'])
@app.route('/mark_as_read/<int:feed_id>', methods=['GET'])
@login_required
@feed_access_required
def mark_as_read(feed_id=None):
    """
    Mark all unreaded articles as read.
    """
    if feed_id != None:
        Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed_id, Article.readed == False).update({"readed": True})
        flash('Articles marked as read.', 'info')
    else:
        Article.query.filter(Article.user_id == g.user.id, Article.readed == False).update({"readed": True})
        flash("All articles marked as read", 'info')
    db.session.commit()
    return redirect(redirect_url())

@app.route('/like/<int:article_id>', methods=['GET'])
@login_required
def like(article_id=None):
    """
    Mark or unmark an article as favorites.
    """
    Article.query.filter(Article.user_id == g.user.id, Article.id == article_id). \
                update({
                        "like": not Article.query.filter(Article.id == article_id).first().like
                       })
    db.session.commit()
    return redirect(redirect_url())

@app.route('/delete/<int:article_id>/', methods=['GET'])
@login_required
def delete(article_id=None):
    """
    Delete an article from the database.
    """
    article = Article.query.filter(Article.id == article_id).first()
    if article != None and article.source.subscriber.id == g.user.id:
        db.session.delete(article)
        db.session.commit()
        flash('Article "' + article.title + '" deleted.', 'success')
        return redirect(url_for('home'))
    else:
        flash('Impossible to delete the article.', 'danger')
        return redirect(url_for('home'))

@app.route('/articles/<feed_id>/', methods=['GET'])
@app.route('/articles/<feed_id>/<int:nb_articles>', methods=['GET'])
@login_required
@feed_access_required
def articles(feed_id=None, nb_articles=-1):
    """
    List articles of a feed.
    The administrator of the platform is able to access to this view for every users.
    """
    feed = Feed.query.filter(Feed.id == feed_id).first()
    if len(feed.articles.all()) <= nb_articles:
        nb_articles = -1
    if nb_articles != -1:
        feed.articles = feed.articles[0:nb_articles]
    return render_template('articles.html', feed=feed, nb_articles=nb_articles)

@app.route('/favorites/', methods=['GET'])
@login_required
def favorites():
    """
    List favorites articles.
    """
    feeds_with_like = Feed.query.filter(Feed.user_id == g.user.id, Feed.articles.any(like=True))
    result, nb_favorites = [], 0
    for feed in feeds_with_like:
        new_feed = Feed()
        new_feed.id = feed.id
        new_feed.title = feed.title
        new_feed.articles = Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed.id, Article.like == True).all()
        length = len(new_feed.articles.all())
        if length != 0:
            result.append(new_feed)
            nb_favorites += length
    return render_template('favorites.html', feeds=result, nb_favorites=nb_favorites)

@app.route('/unread/', methods=['GET'])
@login_required
def unread():
    """
    List unread articles.
    """
    feeds_with_unread = Feed.query.filter(Feed.user_id == g.user.id, Feed.articles.any(readed=False))
    result, nb_unread = [], 0
    for feed in feeds_with_unread:
        new_feed = Feed()
        new_feed.id = feed.id
        new_feed.title = feed.title
        new_feed.articles = Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed.id, Article.readed == False).all()
        length = len(new_feed.articles.all())
        if length != 0:
            result.append(new_feed)
            nb_unread += length
    return render_template('unread.html', feeds=result, nb_unread=nb_unread)

@app.route('/inactives/', methods=['GET'])
@login_required
def inactives():
    """
    List of inactive feeds.
    """
    nb_days = int(request.args.get('nb_days', 365))
    user = User.query.filter(User.id == g.user.id).first()
    today = datetime.datetime.now()
    inactives = []
    for feed in user.feeds:
        try:
            last_post = feed.articles[0].date
        except IndexError:
            continue
        elapsed = today - last_post
        if elapsed > datetime.timedelta(days=nb_days):
            inactives.append((feed, elapsed))
    return render_template('inactives.html', inactives=inactives, nb_days=nb_days)

@app.route('/index_database/', methods=['GET'])
@login_required
def index_database():
    """
    Index all the database.
    """
    user = models.User.objects(email=g.user.email).first()
    #fastsearch.create_index(user.feeds)
    flash('Database indexed.', 'success')
    return redirect(url_for('home'))

@app.route('/export/', methods=['GET'])
@login_required
def export_articles():
    """
    Export all articles.
    """
    user = User.query.filter(User.id == g.user.id).first()
    archive_file, archive_file_name = export.export_html(user)
    """
    try:
        archive_file, archive_file_name = export.export_html(user.feeds)
    except:
        flash("Error when exporting articles.", 'danger')
        return redirect(url_for('management'))
    """
    response = make_response(archive_file)
    response.headers['Content-Type'] = 'application/x-compressed'
    response.headers['Content-Disposition'] = 'attachment; filename='+archive_file_name
    return response

@app.route('/export_opml/', methods=['GET'])
@login_required
def export_opml():
    """
    Export all feeds to OPML.
    """
    user = User.query.filter(User.id == g.user.id).first()
    response = make_response(render_template('opml.xml', user=user, now=datetime.datetime.now()))
    response.headers['Content-Type'] = 'application/xml'
    response.headers['Content-Disposition'] = 'attachment; filename=feeds.opml'
    return response

@app.route('/search/', methods=['GET'])
@login_required
def search():
    """
    Search articles corresponding to the query.<commit_msg>Trying the export to HTML option on Heroku (2).<commit_after>#! /usr/bin/env python
# -*- coding: utf-8 -*-

# pyAggr3g470r - A Web based news aggregator.
# Copyright (C) 2010-2014  <NAME> - http://cedricbonhomme.org/
#
# For more information : https://bitbucket.org/cedricbonhomme/pyaggr3g470r/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

__author__ = "<NAME>"
__version__ = "$Revision: 4.7 $"
__date__ = "$Date: 2010/01/29 $"
__revision__ = "$Date: 2014/04/12 $"
__copyright__ = "Copyright (c) <NAME>"
__license__ = "AGPLv3"

import os
import datetime
from flask import render_template, jsonify, request, flash, session, url_for, redirect, g, current_app, make_response
from flask.ext.login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUserMixin
from flask.ext.principal import Principal, Identity, AnonymousIdentity, identity_changed, identity_loaded, Permission, RoleNeed, UserNeed
from sqlalchemy import desc
from werkzeug import generate_password_hash

import conf
import utils
import export
import feedgetter
import models
#import search as fastsearch
from forms import SigninForm, AddFeedForm, ProfileForm
from pyaggr3g470r import app, db
from pyaggr3g470r.models import User, Feed, Article, Role

Principal(app)
# Create a permission with a single Need, in this case a RoleNeed.
admin_permission = Permission(RoleNeed('admin'))

login_manager = LoginManager()
login_manager.init_app(app)

#
# Management of the user's session.
#
@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user

    # Add the UserNeed to the identity
    if hasattr(current_user, 'id'):
        identity.provides.add(UserNeed(current_user.id))

    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        for role in current_user.roles:
            identity.provides.add(RoleNeed(role.name))

@app.before_request
def before_request():
    g.user = current_user
    if g.user.is_authenticated():
        pass
        #g.user.last_seen = datetime.utcnow()
        #db.session.add(g.user)
        #db.session.commit()

@app.errorhandler(403)
def authentication_failed(e):
    flash('Authentication failed.', 'danger')
    return redirect(url_for('login'))

@app.errorhandler(401)
def authentication_failed(e):
    flash('Authentication required.', 'info')
    return redirect(url_for('login'))

@login_manager.user_loader
def load_user(email):
    # Return an instance of the User model
    return User.query.filter(User.email == email).first()

def redirect_url(default='home'):
    return request.args.get('next') or \
            request.referrer or \
            url_for(default)


from functools import wraps
def feed_access_required(func):
    """
    This decorator enables to check if a user has access to a feed.
    The administrator of the platform is able to access to the feeds of a normal user.
    """
    @wraps(func)
    def decorated(*args, **kwargs):
        if kwargs.get('feed_id', None) != None:
            feed = Feed.query.filter(Feed.id == kwargs.get('feed_id', None)).first()
            if (feed == None or feed.subscriber.id != g.user.id) and not g.user.is_admin():
                flash("This feed do not exist.", "danger")
                return redirect(url_for('home'))
        return func(*args, **kwargs)
    return decorated



#
# Views.
#
@app.route('/login/', methods=['GET', 'POST'])
def login():
    """
    Log in view.
    """
    g.user = AnonymousUserMixin()
    form = SigninForm()

    if form.validate_on_submit():
        user = User.query.filter(User.email == form.email.data).first()
        login_user(user)
        g.user = user
        identity_changed.send(current_app._get_current_object(), identity=Identity(user.id))
        flash("Logged in successfully.", 'success')
        return redirect(url_for('home'))
    return render_template('login.html', form=form)

@app.route('/logout/')
@login_required
def logout():
    """
    Log out view. Removes the user information from the session.
    """
    # Update last_seen field
    g.user.last_seen = datetime.datetime.utcnow()
    db.session.add(g.user)
    db.session.commit()

    # Remove the user information from the session
    logout_user()

    # Remove session keys set by Flask-Principal
    for key in ('identity.name', 'identity.auth_type'):
        session.pop(key, None)

    # Tell Flask-Principal the user is anonymous
    identity_changed.send(current_app._get_current_object(), identity=AnonymousIdentity())

    flash("Logged out successfully.", 'success')
    return redirect(url_for('login'))

@app.route('/')
@login_required
def home():
    """
    The home page lists most recent articles of all feeds.
    """
    user = User.query.filter(User.email == g.user.email).first()
    result = []
    for feed in user.feeds:
        new_feed = Feed()
        new_feed.id = feed.id
        new_feed.title = feed.title
        new_feed.enabled = feed.enabled
        new_feed.articles = Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed.id).order_by(desc("Article.date")).limit(9)
        result.append(new_feed)
    unread_articles = len(Article.query.filter(Article.user_id == g.user.id, Article.readed == False).all())
    return render_template('home.html', result=result, head_title=unread_articles)

@app.route('/fetch/', methods=['GET'])
@app.route('/fetch/<feed_id>', methods=['GET'])
@login_required
def fetch(feed_id=None):
    """
    Triggers the download of news.
    """
    feed_getter = feedgetter.FeedGetter(g.user.email)
    feed_getter.retrieve_feed(feed_id)
    flash("New articles retrieved.", 'success')
    return redirect(redirect_url())

@app.route('/about/', methods=['GET'])
@login_required
def about():
    """
    'About' page.
    """
    return render_template('about.html')

@app.route('/feeds/', methods=['GET'])
@login_required
def feeds():
    """
    Lists the subscribed  feeds in a table.
    """
    user = User.query.filter(User.email == g.user.email).first()
    feeds = user.feeds
    return render_template('feeds.html', feeds=feeds)

@app.route('/feed/<int:feed_id>', methods=['GET'])
@login_required
@feed_access_required
def feed(feed_id=None):
    """
    Presents detailed information about a feed.
    """
    feed = Feed.query.filter(Feed.id == feed_id).first()
    word_size = 6
    articles = feed.articles.all()
    nb_articles = len(Article.query.filter(Article.user_id == g.user.id).all())
    top_words = utils.top_words(articles, n=50, size=int(word_size))
    tag_cloud = utils.tag_cloud(top_words)

    today = datetime.datetime.now()
    try:
        last_article = articles[0].date
        first_article = articles[-1].date
        delta = last_article - first_article
        average = round(float(len(articles)) / abs(delta.days), 2)
    except:
        last_article = datetime.datetime.fromtimestamp(0)
        first_article = datetime.datetime.fromtimestamp(0)
        delta = last_article - first_article
        average = 0
    elapsed = today - last_article

    return render_template('feed.html', head_title=utils.clear_string(feed.title), feed=feed, tag_cloud=tag_cloud, \
                        first_post_date=first_article, end_post_date=last_article , nb_articles=nb_articles, \
                        average=average, delta=delta, elapsed=elapsed)

@app.route('/article/<int:article_id>', methods=['GET'])
@login_required
def article(article_id=None):
    """
    Presents the content of an article.
    """
    article = Article.query.filter(Article.user_id == g.user.id, Article.id == article_id).first()
    if article != None:
        if not article.readed:
            article.readed = True
            db.session.commit()
        return render_template('article.html', head_title=utils.clear_string(article.title), article=article)
    flash("This article do not exist.", 'warning')
    return redirect(redirect_url())
    

@app.route('/mark_as_read/', methods=['GET'])
@app.route('/mark_as_read/<int:feed_id>', methods=['GET'])
@login_required
@feed_access_required
def mark_as_read(feed_id=None):
    """
    Mark all unreaded articles as read.
    """
    if feed_id != None:
        Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed_id, Article.readed == False).update({"readed": True})
        flash('Articles marked as read.', 'info')
    else:
        Article.query.filter(Article.user_id == g.user.id, Article.readed == False).update({"readed": True})
        flash("All articles marked as read", 'info')
    db.session.commit()
    return redirect(redirect_url())

@app.route('/like/<int:article_id>', methods=['GET'])
@login_required
def like(article_id=None):
    """
    Mark or unmark an article as favorites.
    """
    Article.query.filter(Article.user_id == g.user.id, Article.id == article_id). \
                update({
                        "like": not Article.query.filter(Article.id == article_id).first().like
                       })
    db.session.commit()
    return redirect(redirect_url())

@app.route('/delete/<int:article_id>/', methods=['GET'])
@login_required
def delete(article_id=None):
    """
    Delete an article from the database.
    """
    article = Article.query.filter(Article.id == article_id).first()
    if article != None and article.source.subscriber.id == g.user.id:
        db.session.delete(article)
        db.session.commit()
        flash('Article "' + article.title + '" deleted.', 'success')
        return redirect(url_for('home'))
    else:
        flash('Impossible to delete the article.', 'danger')
        return redirect(url_for('home'))

@app.route('/articles/<feed_id>/', methods=['GET'])
@app.route('/articles/<feed_id>/<int:nb_articles>', methods=['GET'])
@login_required
@feed_access_required
def articles(feed_id=None, nb_articles=-1):
    """
    List articles of a feed.
    The administrator of the platform is able to access to this view for every users.
    """
    feed = Feed.query.filter(Feed.id == feed_id).first()
    if len(feed.articles.all()) <= nb_articles:
        nb_articles = -1
    if nb_articles != -1:
        feed.articles = feed.articles[0:nb_articles]
    return render_template('articles.html', feed=feed, nb_articles=nb_articles)

@app.route('/favorites/', methods=['GET'])
@login_required
def favorites():
    """
    List favorites articles.
    """
    feeds_with_like = Feed.query.filter(Feed.user_id == g.user.id, Feed.articles.any(like=True))
    result, nb_favorites = [], 0
    for feed in feeds_with_like:
        new_feed = Feed()
        new_feed.id = feed.id
        new_feed.title = feed.title
        new_feed.articles = Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed.id, Article.like == True).all()
        length = len(new_feed.articles.all())
        if length != 0:
            result.append(new_feed)
            nb_favorites += length
    return render_template('favorites.html', feeds=result, nb_favorites=nb_favorites)

@app.route('/unread/', methods=['GET'])
@login_required
def unread():
    """
    List unread articles.
    """
    feeds_with_unread = Feed.query.filter(Feed.user_id == g.user.id, Feed.articles.any(readed=False))
    result, nb_unread = [], 0
    for feed in feeds_with_unread:
        new_feed = Feed()
        new_feed.id = feed.id
        new_feed.title = feed.title
        new_feed.articles = Article.query.filter(Article.user_id == g.user.id, Article.feed_id == feed.id, Article.readed == False).all()
        length = len(new_feed.articles.all())
        if length != 0:
            result.append(new_feed)
            nb_unread += length
    return render_template('unread.html', feeds=result, nb_unread=nb_unread)

@app.route('/inactives/', methods=['GET'])
@login_required
def inactives():
    """
    List of inactive feeds.
    """
    nb_days = int(request.args.get('nb_days', 365))
    user = User.query.filter(User.id == g.user.id).first()
    today = datetime.datetime.now()
    inactives = []
    for feed in user.feeds:
        try:
            last_post = feed.articles[0].date
        except IndexError:
            continue
        elapsed = today - last_post
        if elapsed > datetime.timedelta(days=nb_days):
            inactives.append((feed, elapsed))
    return render_template('inactives.html', inactives=inactives, nb_days=nb_days)

@app.route('/index_database/', methods=['GET'])
@login_required
def index_database():
    """
    Index all the database.
    """
    user = models.User.objects(email=g.user.email).first()
    #fastsearch.create_index(user.feeds)
    flash('Database indexed.', 'success')
    return redirect(url_for('home'))

@app.route('/export/', methods=['GET'])
@login_required
def export_articles():
    """
    Export all articles.
    """
    user = User.query.filter(User.id == g.user.id).first()
    try:
        archive_file, archive_file_name = export.export_html(user.feeds)
    except:
        flash("Error when exporting articles.", 'danger')
        return redirect(url_for('management'))
    response = make_response(archive_file)
    response.headers['Content-Type'] = 'application/x-compressed'
    response.headers['Content-Disposition'] = 'attachment; filename='+archive_file_name
    return response

@app.route('/export_opml/', methods=['GET'])
@login_required
def export_opml():
    """
    Export all feeds to OPML.
    """
    user = User.query.filter(User.id == g.user.id).first()
    response = make_response(render_template('opml.xml', user=user, now=datetime.datetime.now()))
    response.headers['Content-Type'] = 'application/xml'
    response.headers['Content-Disposition'] = 'attachment; filename=feeds.opml'
    return response

@app.route('/search/', methods=['GET'])
@login_required
def search():
    """
    Search articles corresponding to the query.
{'fraction_non_alphanumeric': 0.13494371642659184, 'fraction_numerical': 0.005029595921579362, 'mean_word_length': 3.418442932728647, 'pattern_counts': {'":': 6, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7182100', 'n_tokens_mistral': 9272, 'n_tokens_neox': 8959, 'n_words': 2118}