Example #1
0
    def inbox_create(self, as_actor: ap.Person, create: ap.Create) -> None:
        self._handle_replies(as_actor, create)
        obj = create.get_object()
        current_app.logger.debug(f"inbox_create {obj.ACTIVITY_TYPE} {obj!r} as {as_actor!r}")

        if obj.ACTIVITY_TYPE == ap.ActivityType.AUDIO:
            # create a remote Audio and process it
            from tasks import create_sound_for_remote_track, upload_workflow

            act = Activity.query.filter(Activity.url == create.id).first()
            if not act:
                current_app.logger.error(f"cannot find activity with url == {create.id!r}")
                return

            sound_id = create_sound_for_remote_track(act)
            # TODO(dashie): fetch_remote_track should be done inside the upload_workflow to not have to do celery tasks dependencies
            # Plus it's better to do it like that, one function to do everything, locally or remotely.
            upload_workflow.delay(sound_id)
        else:
            current_app.logger.error(f"got an unhandled Activity Type {obj.ACTIVITY_TYPE!r} in the inbox")
Example #2
0
def upload():
    pcfg = {"title": gettext("New upload")}
    user = User.query.filter(User.id == current_user.id).one()

    form = SoundUploadForm()

    if request.method == "POST" and "sound" in request.files:
        if form.validate_on_submit():
            filename_orig = request.files["sound"].filename
            filename_hashed = get_hashed_filename(filename_orig)

            sounds.save(request.files["sound"],
                        folder=user.slug,
                        name=filename_hashed)

            rec = Sound()
            rec.filename = filename_hashed
            rec.filename_orig = filename_orig
            rec.licence = form.licence.data
            if form.album.data:
                rec.album_id = form.album.data.id
                if not form.album.data.sounds:
                    rec.album_order = 0
                else:
                    rec.album_order = form.album.data.sounds.count() + 1

            rec.user_id = current_user.id
            if not form.title.data:
                rec.title = filename_orig
            else:
                rec.title = form.title.data
            rec.description = form.description.data
            rec.private = form.private.data

            if "flac" in request.files[
                    "sound"].mimetype or "ogg" in request.files[
                        "sound"].mimetype:
                rec.transcode_state = Sound.TRANSCODE_WAITING
                rec.transcode_needed = True

            db.session.add(rec)
            db.session.commit()

            # push the job in queue
            from tasks import upload_workflow

            upload_workflow.delay(rec.id)

            # log
            add_user_log(rec.id, user.id, "sounds", "info",
                         "Uploaded {0} -- {1}".format(rec.id, rec.title))

            flash(gettext("Uploaded ! Processing will now follow."), "success")
        else:
            return render_template("sound/upload.jinja2",
                                   pcfg=pcfg,
                                   form=form,
                                   flash="Error with the file")
        return redirect(
            url_for("bp_sound.show",
                    username=current_user.name,
                    soundslug=rec.slug))

    # GET
    return render_template("sound/upload.jinja2", pcfg=pcfg, form=form)
Example #3
0
def retry_processing(username_or_id, soundslug):
    """
    Reset track processing state.
    ---
    tags:
        - Tracks
    parameters:
        - name: user_id
          in: path
          type: integer
          required: true
          description: User ID
        - name: soundslug
          in: path
          type: string
          required: true
          description: Track slug
    responses:
        200:
            description: Returns track id.
    """
    # Get logged in user from bearer token, or None if not logged in
    if current_token:
        current_user = current_token.user
    else:
        current_user = None

    if not current_user:
        return jsonify({"error": "unauthorized"}), 401

    # Get the associated User from url fetch
    track_user = User.query.filter(User.name == username_or_id, User.local.is_(True)).first()
    if not track_user:
        try:
            track_user = User.query.filter(User.flake_id == username_or_id).first()
        except sqlalchemy.exc.DataError:
            return jsonify({"error": "User not found"}), 404
    if not track_user:
        return jsonify({"error": "User not found"}), 404

    if current_user.id != track_user.id:
        return jsonify({"error": "forbidden"}), 403

    sound = Sound.query.filter(Sound.slug == soundslug, Sound.user_id == track_user.id).first()
    if not sound:
        return jsonify({"error": "not found"}), 404

    if sound.transcode_state != Sound.TRANSCODE_ERROR:
        return jsonify({"error": "cannot reset transcode state if no error"}), 503

    # Delete sound info if any
    if sound.sound_infos.count() > 0:
        db.session.delete(sound.sound_infos.one())

    # Reset transcode state if transcode is needed
    if sound.transcode_needed:
        sound.transcode_state = Sound.TRANSCODE_WAITING

    db.session.commit()

    # re-schedule a job push
    from tasks import upload_workflow

    upload_workflow.delay(sound.id)

    # log
    add_user_log(
        sound.id,
        current_user.id,
        "sounds",
        "info",
        "Re-scheduled a processing {0} -- {1}".format(sound.id, sound.title),
    )

    return jsonify({"trackId": sound.id})
Example #4
0
def upload():
    """
    Create a new track.
    ---
    tags:
        - Tracks
    security:
        - OAuth2:
            - write
    responses:
        200:
            description: Returns the track id and slug.
    """
    errors = {}

    current_user = current_token.user
    if not current_user:
        return jsonify({"error": "Unauthorized"}), 403

    if "file" not in request.files:
        errors["file"] = "No file present"

    if len(errors) > 0:
        return jsonify({"error": errors}), 400

    # Check for user quota already reached
    if current_user.quota_count >= current_user.quota:
        return jsonify({"error": "quota limit reached"}), 507  # Insufficient storage
        # or 509 Bandwitdh Limit Exceeded...

    # Get file, and file size
    file_uploaded = request.files["file"]
    file_uploaded.seek(0, os.SEEK_END)  # ff to the end
    file_size = file_uploaded.tell()
    file_uploaded.seek(0)  # rewind

    if (current_user.quota_count + file_size) > current_user.quota:
        return jsonify({"error": "quota limit reached"}), 507  # Insufficient storage

    # Do the same with the artwork
    if "artwork" in request.files:
        artwork_uploaded = request.files["artwork"]
        artwork_uploaded.seek(0, os.SEEK_END)
        artwork_size = artwork_uploaded.tell()
        artwork_uploaded.seek(0)
        if artwork_size > Reel2bitsDefaults.artwork_size_limit:  # Max size of 2MB
            return jsonify({"error": "artwork too big, 2MB maximum"}), 413  # Request Entity Too Large
    else:
        artwork_uploaded = None

    form = SoundUploadForm()

    if form.validate_on_submit():
        filename_orig = file_uploaded.filename
        filename_hashed = get_hashed_filename(filename_orig)

        # Save the track file
        sounds.save(file_uploaded, folder=current_user.slug, name=filename_hashed)

        rec = Sound()
        rec.filename = filename_hashed
        rec.filename_orig = filename_orig

        # Save the artwork
        if artwork_uploaded:
            artwork_filename = get_hashed_filename(artwork_uploaded.filename)
            artworksounds.save(artwork_uploaded, folder=current_user.slug, name=artwork_filename)
            rec.artwork_filename = artwork_filename

        rec.licence = form.licence.data
        if form.album.data:
            rec.album_id = form.album.data.id
            if not form.album.data.sounds:
                rec.album_order = 0
            else:
                rec.album_order = form.album.data.sounds.count() + 1

        rec.user_id = current_user.id
        if not form.title.data:
            rec.title, _ = splitext(filename_orig)
        else:
            rec.title = form.title.data
        rec.description = form.description.data
        rec.private = form.private.data
        rec.file_size = file_size
        rec.transcode_file_size = 0  # will be filled, if needed in transcoding workflow
        rec.genre = form.genre.data

        # Handle tags
        tags = form.tags.data.split(",")
        # Clean
        tags = [t.strip() for t in tags if t]
        # For each tag get it or create it
        for tag in tags:
            dbt = SoundTag.query.filter(SoundTag.name == tag).first()
            if not dbt:
                dbt = SoundTag(name=tag)
                db.session.add(dbt)
            rec.tags.append(dbt)

        if "flac" in file_uploaded.mimetype or "ogg" in file_uploaded.mimetype or "wav" in file_uploaded.mimetype:
            rec.transcode_state = Sound.TRANSCODE_WAITING
            rec.transcode_needed = True

        db.session.add(rec)

        # recompute user quota
        current_user.quota_count = current_user.quota_count + rec.file_size

        db.session.commit()

        # push the job in queue
        from tasks import upload_workflow

        upload_workflow.delay(rec.id)

        # log
        add_user_log(rec.id, current_user.id, "sounds", "info", "Uploaded {0} -- {1}".format(rec.id, rec.title))

        return jsonify({"id": rec.flake_id, "slug": rec.slug})

    return jsonify({"error": json.dumps(form.errors)}), 400
Example #5
0
def search():
    """
    Search.
    ---
    tags:
        - Global
    parameters:
        - name: q
          in: query
          type: string
          required: true
          description: search string
    responses:
        200:
            description: fixme.
    """
    # Get logged in user from bearer token, or None if not logged in
    if current_token:
        current_user = current_token.user
    else:
        current_user = None

    s = request.args.get("q", None)
    if not s:
        return jsonify({"error": "No search string provided"}), 400

    # This is the old search endpoint and needs to be improved
    # Especially tracks and accounts needs to be returned in the right format, with the data helpers
    # Users should be searched from known Actors or fetched
    # URI should be searched from known activities or fetched
    # FTS, well, FTS needs to be implemented

    results = {"accounts": [], "sounds": [], "mode": None, "from": None}

    if current_user:
        results["from"] = current_user.name

    # Search for sounds
    # TODO: Implement FTS to get sounds search
    sounds = []

    # Search for accounts
    accounts = []
    is_user_at_account = RE_ACCOUNT.match(s)

    if s.startswith("https://"):
        # Try to match the URI from Activities in database
        results["mode"] = "uri"
        users = Actor.query.filter(Actor.meta_deleted.is_(False),
                                   Actor.url == s).all()
    elif is_user_at_account:
        # It matches [email protected], try to match it locally
        results["mode"] = "acct"
        user = is_user_at_account.group("user")
        instance = is_user_at_account.group("instance")
        users = Actor.query.filter(Actor.meta_deleted.is_(False),
                                   Actor.preferred_username == user,
                                   Actor.domain == instance).all()
    else:
        # It's a FTS search
        results["mode"] = "username"
        # Match actor username in database
        if current_user:
            users = (db.session.query(Actor, Follower).outerjoin(
                Follower,
                and_(Actor.id == Follower.target_id,
                     Follower.actor_id == current_user.actor[0].id)).filter(
                         or_(Actor.preferred_username.contains(s),
                             Actor.name.contains(s))).filter(
                                 not_(Actor.id ==
                                      current_user.actor[0].id)).all())
        else:
            users = (db.session.query(Actor).filter(
                or_(Actor.preferred_username.contains(s),
                    Actor.name.contains(s))).all())

    # Handle the found users
    if len(users) > 0:
        for actor in users:
            relationship = False
            if current_user:
                relationship = to_json_relationship(current_user, actor.user)
            accounts.append(to_json_account(actor.user, relationship))

    if len(accounts) <= 0:
        # Do a webfinger
        # TODO FIXME: We should do this only if https:// or user@account submitted
        # And rework it slightly differently since we needs to backend.fetch_iri() for https:// who
        # can match a Sound and not only an Actor
        current_app.logger.debug(f"webfinger for {s}")
        try:
            remote_actor_url = get_actor_url(s, debug=current_app.debug)
            # We need to get the remote Actor
            backend = ap.get_backend()
            iri = backend.fetch_iri(remote_actor_url)
            if iri:
                # We have fetched an unknown Actor
                # Save it in database and return it properly
                current_app.logger.debug(
                    f"got remote actor URL {remote_actor_url}")

                act = ap.parse_activity(iri)

                fetched_actor, fetched_user = create_remote_actor(act)
                db.session.add(fetched_user)
                db.session.add(fetched_actor)
                db.session.commit()

                relationship = False
                if current_user:
                    relationship = to_json_relationship(
                        current_user, fetched_user)
                accounts.append(to_json_account(fetched_user, relationship))
                results["mode"] = "webfinger"

        except (InvalidURLError, ValueError):
            current_app.logger.exception(f"Invalid AP URL: {s}")
            # Then test fetching as a "normal" Activity ?
    # Finally fill the results dict
    results["accounts"] = accounts

    # FIXME: handle exceptions
    if results["mode"] == "uri" and len(sounds) <= 0:
        backend = ap.get_backend()
        iri = backend.fetch_iri(s)
        if iri:
            # FIXME: Is INBOX the right choice here ?
            backend.save(Box.INBOX, iri)
        # Fetch again, but get it from database
        activity = Activity.query.filter(Activity.url == iri).first()
        if not activity:
            current_app.logger.exception("WTF Activity is not saved")
        else:
            from tasks import create_sound_for_remote_track, upload_workflow

            sound_id = create_sound_for_remote_track(activity)
            sound = Sound.query.filter(Sound.id == sound_id).one()
            upload_workflow.delay(sound.id)

            relationship = False
            if current_user:
                relationship = to_json_relationship(current_user, sound.user)
            acct = to_json_account(sound.user, relationship)
            sounds.append(to_json_track(sound, acct))

    return jsonify({"who": s, "results": results})