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")
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)
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})
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
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})