Beispiel #1
0
    public DbWriter GetWriter()
    {
        var writer = (DbWriter)dbFactory.CreateWriter();

        spawnedWriters.Add(writer);
        return(writer);
    }
Beispiel #2
0
    /// <summary>
    /// Update our modules with the given module view (parses code, sets up data, etc.)
    /// </summary>
    /// <param name="module"></param>
    /// <returns></returns>
    public LoadedModule?UpdateModule(ContentView module, bool force = true)
    {
        if (!force && loadedModules.ContainsKey(module.name) && loadedModules[module.name].lastRevisionId == module.lastRevisionId)
        {
            return(null);
        }
        //if(!force && loadedModules.ContainsKey(module.name))
        //    return null;

        var getValue = new Func <string, string?>((s) =>
        {
            if (module.values.ContainsKey(s))
            {
                return(module.values[s].ToString());
            }
            else
            {
                return(null);
            }
        });

        var getValueNum = new Func <string, long>((s) =>
        {
            long result = 0;
            long.TryParse(getValue(s), out result);
            return(result);
        });

        //no matter if it's an update or whatever, have to just rebuild the module
        var mod = new LoadedModule();

        mod.script = new Script();
        mod.script.DoString(module.text);     //This could take a LONG time.
        mod.lastRevisionId = module.lastRevisionId;
        mod.contentId      = module.id;

        var getData = new Func <string, Dictionary <string, string?> >((k) =>
        {
            var command         = mod.dataConnection?.CreateCommand() ?? throw new InvalidOperationException("No connection to module storage!");
            command.CommandText = $"SELECT key, value FROM {module.name} WHERE key LIKE $key";
            command.Parameters.AddWithValue("$key", k);
            var result = new Dictionary <string, string?>();
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    result[reader.GetString(0)] = reader.IsDBNull(1) ? null : reader.GetString(1);
                }
            }
            return(result);
        });

        var sendRawMessage = new Action <long, long, string>((uid, room, message) =>
        {
            using var writer = dbFactory.CreateWriter();
            var realMessage  = new MessageView()
            {
                createUserId  = mod.currentUserId,
                contentId     = room,
                receiveUserId = uid,
                text          = message,
                module        = module.name,
                createDate    = DateTime.Now
            }; //, mod.currentUserId);
            try {
                writer.WriteAsync(realMessage, mod.currentUserId).Wait();
            }
            catch (AggregateException ex) {
                ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();
            }
            //addMessage(
        });

        mod.script.Globals["getdata"] = new Func <string, string?>((k) =>
        {
            var result = getData(k);
            if (result.ContainsKey(k))
            {
                return(result[k]);
            }
            else
            {
                return(null);
            }
        });
        mod.script.Globals["getalldata"] = getData;
        mod.script.Globals["setdata"]    = new Action <string, string>((k, v) =>
        {
            var command = mod.dataConnection?.CreateCommand() ?? throw new InvalidOperationException("No connection to module storage!");
            var vtext   = "$value";

            command.Parameters.AddWithValue("$key", k);

            if (v != null)
            {
                command.Parameters.AddWithValue("$value", v);
            }
            else
            {
                vtext = "NULL";
            }

            command.CommandText = $"INSERT OR REPLACE INTO {module.name} (key, value) values($key, {vtext})";
            command.ExecuteNonQuery();
        });
        mod.script.Globals["getvalue"]    = getValue;
        mod.script.Globals["getvaluenum"] = getValueNum;
        mod.script.Globals["prntdbg"]     = new Action <string>((m) =>
        {
            //mod.debug.Enqueue($"[{mod.currentRequester.userId}:{mod.currentFunction}|{string.Join(",", mod.currentArgs)}] {m} ({DateTime.Now})");
            mod.debug.Enqueue($"[{mod.currentUserId}:{mod.currentFunction}|{string.Join(",", mod.currentArgs)}] {m} ({DateTime.Now})");

            while (mod.debug.Count > config.MaxDebugSize)
            {
                mod.debug.Dequeue();
            }
        });
        //mod.script.Globals["sendrawmessage"] = sendRawMessage;
        mod.script.Globals["usermessage"]      = new Action <long, string>((uid, message) => sendRawMessage(uid, mod.currentParentId, message)); //sendRawMessage(uid, 0, message));
        mod.script.Globals["broadcastmessage"] = new Action <string>((message) => sendRawMessage(0, mod.currentParentId, message));

        var pluralize = new Func <int, string, string>((i, s) => s + (i == 1 ? "" : "s"));
        var agoize    = new Func <double, string, string>((d, s) => (int)d + " " + pluralize((int)d, s) + " ago");

        mod.script.Globals["pluralize"]          = pluralize;
        mod.script.Globals["gettimestamp"]       = new Func <string>(() => DateTime.Now.ToString());
        mod.script.Globals["timesincetimestamp"] = new Func <string, string>((t) =>
        {
            TimeSpan diff = DateTime.Now - Convert.ToDateTime(t);

            if (diff.TotalSeconds < 10)
            {
                return("Moments ago");
            }
            else if (diff.TotalSeconds < 60)
            {
                return(agoize(diff.TotalSeconds, "second"));
            }
            else if (diff.TotalMinutes < 60)
            {
                return(agoize(diff.TotalMinutes, "minute"));
            }
            else if (diff.TotalHours < 24)
            {
                return(agoize(diff.TotalHours, "hour"));
            }
            else if (diff.TotalDays < 30)
            {
                return(agoize(diff.TotalDays, "day"));
            }
            else if (diff.TotalDays < 365)
            {
                return(agoize(diff.TotalDays / 30, "month"));
            }
            else
            {
                return(agoize(diff.TotalDays / 365, "year"));
            }
        });
        mod.script.Globals["b64encode"] = new Func <string, string?>(s =>
        {
            if (s == null)
            {
                return(null);
            }
            var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(s);
            return(System.Convert.ToBase64String(plainTextBytes).Replace('/', '!'));
        });
        mod.script.Globals["b64decode"] = new Func <string, string?>(s =>
        {
            if (s == null)
            {
                return(null);
            }
            var base64EncodedBytes = System.Convert.FromBase64String(s.Replace('!', '/'));
            return(System.Text.Encoding.UTF8.GetString(base64EncodedBytes));
        });

        mod.script.Globals["getbroadcastid"] = new Func <long>(() => mod.currentParentId);

        SetupDatabaseForModule(module.name);
        mod.subcommands = ParseAllSubcommands(mod) ?? new Dictionary <string, ModuleSubcommandInfo?>();

        UpdateLoadedModule(module.name, mod);

        return(mod);
    }
    public async Task <ContentView> UploadFile(ContentView newView, UploadFileConfig fileConfig, Stream fileData, long requester)
    {
        fileData.Seek(0, SeekOrigin.Begin);

        if (fileData.Length == 0)
        {
            throw new RequestException("No data uploaded!");
        }

        if (fileConfig.quantize >= 0 && (fileConfig.quantize < config.MinQuantize || fileConfig.quantize > config.MaxQuantize))
        {
            throw new RequestException($"Quantize must be between {config.MinQuantize} and {config.MaxQuantize}");
        }

        int trueQuantize = 0;
        var tempLocation = GetAndMakeTempPath(); //Work is done locally for quantize/etc

        var manipResult = await imageManip.FitToSizeAndSave(fileData, tempLocation, config.MaxSize);

        newView.literalType = manipResult.MimeType;
        AddImageRender(manipResult.RenderCount + manipResult.LoadCount);

        try
        {
            var meta = new Dictionary <string, object>()
            {
                { "size", manipResult.SizeInBytes },
                { "width", manipResult.Width },
                { "height", manipResult.Height }
            };

            //OK the quantization step. This SHOULD modify the view for us!
            if (fileConfig.quantize > 0)
            {
                trueQuantize = await TryQuantize(fileConfig.quantize, newView, tempLocation);
            }

            if (trueQuantize > 0)
            {
                meta["quantize"] = trueQuantize;
            }

            //We now have the metadata
            newView.meta = JsonConvert.SerializeObject(meta);

            using var tempWriter = dbFactory.CreateWriter();
            //This is QUITE dangerous: a file could be created in the api first and THEN the file write fails!
            newView = await tempWriter.WriteAsync(newView, requester);

            try
            {
                await SaveMainDataAsync(tempLocation, newView.hash, newView.literalType ?? "");
            }
            catch (Exception ex)
            {
                logger.LogError($"FILE WRITE '{newView.hash}'({newView.id}) FAILED: {ex}");
                await tempWriter.DeleteAsync <ContentView>(newView.id, requester);
            }
        }
        finally
        {
            System.IO.File.Delete(tempLocation);
        }

        //The view is already done.
        return(newView);
    }
    /// <summary>
    /// Move all the given messages (by id) to the given parent. Fails early if any message is not found, or a problem is found
    /// with permissions before the move. May still fail in the middle.
    /// </summary>
    /// <param name="messageIds"></param>
    /// <param name="newParent"></param>
    /// <param name="requester"></param>
    /// <param name="message"></param>
    /// <returns></returns>
    public async Task <List <MessageView> > RethreadMessagesAsync(List <long> messageIds, long newParent, long requester, string message = "")
    {
        using var search = dbFactory.CreateSearch();
        using var writer = dbFactory.CreateWriter();
        var user = await search.GetById <UserView>(RequestType.user, requester, true);

        if (string.IsNullOrWhiteSpace(message))
        {
            message = $"Rethreading {messageIds.Count} comments to content {newParent}";
        }

        //Go lookup the messages
        var messages = await search.SearchSingleType <MessageView>(requester, new SearchRequest()
        {
            type   = RequestType.message.ToString(),
            fields = "*",
            query  = "id in @ids",
            order  = "id"
        }, new Dictionary <string, object>()
        {
            { "ids", messageIds }
        });

        var leftovers = messageIds.Except(messages.Select(x => x.id)).ToList();

        if (leftovers.Count > 0)
        {
            throw new RequestException($"(Precheck): Some messages were not found: {string.Join(",", leftovers)}");
        }

        //Get a copy of them but with the parent reset
        var newMessages = messages.Select(x =>
        {
            var m       = mapper.Map <MessageView>(x);
            m.contentId = newParent;
            return(m);
        }).OrderBy(x => x.id).ToList();

        //Now, verify them all. If ANY of them throw an error, it will happen BEFORE work was performed
        for (var i = 0; i < newMessages.Count; i++)
        {
            await writer.ValidateWorkGeneral(newMessages[i], messages[i], user, UserAction.update);
        }

        //OK, now we can write them
        var result = new List <MessageView>();

        foreach (var m in newMessages)
        {
            result.Add(await writer.WriteAsync(m, requester, message));
        }

        await writer.WriteAdminLog(new Db.AdminLog()
        {
            type      = AdminLogType.rethread,
            initiator = requester,
            target    = newParent,
            text      = $"User {user.username} ({user.id}) rethreaded {result.Count} messages into content {newParent}"
        });

        return(result);
    }