public async Task <OnlyOffice> WaitForceSaveContentAsync()
        {
            // if no editing session in progress
            if (await GetEditingKeyAsync() == null)
            {
                return(null);
            }
            OnlyOffice newItem = null;

            Docs.OnlyOfficeCallbackData data = new Docs.OnlyOfficeCallbackData(5000);
            lock (context.docs.onlyOfficeCallbacksLock)
            {
                List <Docs.OnlyOfficeCallbackData> list = null;
                if (!context.docs.onlyOfficeCallbacks.ContainsKey(node.id))
                {
                    list = new List <Docs.OnlyOfficeCallbackData>();
                    context.docs.onlyOfficeCallbacks[node.id] = list;
                }
                list.Add(data);
            }
            try
            {
                var json = await ForceSaveAsync();

                // if forcesave in progress, wait for the document update
                if (json != null && json.ContainsKey("error") && json["error"] == 0)
                {
                    newItem = await data.Task;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"GOT EXCEPTION: {e}");
            }
            finally
            {
                lock (context.docs.onlyOfficeCallbacksLock)
                {
                    if (context.docs.onlyOfficeCallbacks.ContainsKey(node.id))
                    {
                        var list = context.docs.onlyOfficeCallbacks[node.id];
                        list.Remove(data);
                        if (list.Count == 0)
                        {
                            context.docs.onlyOfficeCallbacks.Remove(node.id);
                        }
                    }
                }
            }
            return(newItem);
        }
        void ThreadStart()
        {
            logger.Log(LogLevel.Info, "OnlyOffice sessions Thread START");
            bool stop = false;

            lock (sessionStatusLock)
            {
                while (!stop)
                {
                    try
                    {
                        ModelList <OnlyOfficeSession> sessions;
                        using (var db = DB.Create(dbUrl, true))
                        {
                            var task = db.SelectAsync <OnlyOfficeSession>("SELECT * FROM `onlyoffice_session` WHERE TIMESTAMPDIFF(SECOND, `ctime`, NOW()) > 30");
                            task.Wait();
                            sessions = task.Result;
                        }

                        logger.Log(LogLevel.Info, $"OnlyOffice sessions count: {sessions.Count()}");

                        if (sessions.Any())
                        {
                            foreach (var session in sessions)
                            {
                                var task = OnlyOffice.GetInfoAsync(publicUrl, session.key);
                                task.Wait();
                                var json = task.Result;

                                if (json != null && json.ContainsKey("error") && json["error"] is JsonPrimitive && ((JsonPrimitive)json["error"]).JsonType == JsonType.Number)
                                {
                                    int error = json["error"];
                                    if (error == 1)
                                    {
                                        logger.Log(LogLevel.Error, $"OnlyOffice session {session.id} not found for key {session.key}. Delete the session");
                                        using (var db = DB.Create(dbUrl))
                                            session.Delete(db);
                                    }
                                    else if (error == 2)
                                    {
                                        logger.Log(LogLevel.Error, $"OnlyOffice session {session.id} with key {session.key}. Callback url not valid");
                                    }
                                    else if (error == 3)
                                    {
                                        logger.Log(LogLevel.Error, $"OnlyOffice session {session.id} with key {session.key}. Internal server error.");
                                    }
                                    else if (error == 5)
                                    {
                                        logger.Log(LogLevel.Error, $"OnlyOffice session {session.id} with key {session.key}. Command not correct.");
                                    }
                                    else if (error == 6)
                                    {
                                        logger.Log(LogLevel.Error, $"OnlyOffice session {session.id} with key {session.key}. Invalid token.");
                                    }
                                    else if (error != 0 && error != 4)
                                    {
                                        logger.Log(LogLevel.Error, $"OnlyOffice session {session.id} with key {session.key}. Unknown error code {error}.");
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        logger.Log(LogLevel.Error, $"OnlyOffice Thread Exception: {e.ToString()}");
                    }
                    Monitor.Wait(sessionStatusLock, TimeSpan.FromHours(1));
                    stop = sessionStatusStop;
                }
            }
            logger.Log(LogLevel.Info, "OnlyOffice session Thread STOP");
        }
        public OnlyOfficeSessions(Logger logger, string dbUrl, string publicUrl) : base(dbUrl)
        {
            this.dbUrl     = dbUrl;
            this.logger    = logger;
            this.publicUrl = publicUrl;

            sessionStatusThread      = new Thread(ThreadStart);
            sessionStatusThread.Name = "OnlyOfficeSessionStatusThread";
            sessionStatusThread.Start();

            BeforeAsync = async(p, c) => await c.EnsureIsSuperAdminAsync();

            Get["/checkall"] = (p, c) =>
            {
                lock (sessionStatusLock)
                    Monitor.Pulse(sessionStatusLock);
                c.Response.StatusCode = 200;
            };

            GetAsync["/{id}/info"] = async(p, c) =>
            {
                var id = (string)p["id"];
                using (DB db = await DB.CreateAsync(dbUrl, true))
                {
                    var session = new OnlyOfficeSession {
                        id = id
                    };
                    if (await session.LoadAsync(db))
                    {
                        c.Response.StatusCode = 200;
                        c.Response.Content    = await OnlyOffice.GetInfoAsync(publicUrl, session.key);
                    }
                    await db.CommitAsync();
                }
            };

            DeleteAsync["/{id}/users/{userId}"] = async(p, c) =>
            {
                var id     = (string)p["id"];
                var userId = (string)p["userId"];
                using (DB db = await DB.CreateAsync(dbUrl, true))
                {
                    var session = new OnlyOfficeSession {
                        id = id
                    };
                    if (await session.LoadAsync(db))
                    {
                        await OnlyOffice.DisconnectUserAsync(publicUrl, session.key, userId);

                        c.Response.StatusCode = 200;
                    }
                    await db.CommitAsync();
                }
            };

            GetAsync["/{id}/forcesave"] = async(p, c) =>
            {
                var id = (string)p["id"];
                using (DB db = await DB.CreateAsync(dbUrl, true))
                {
                    var session = new OnlyOfficeSession {
                        id = id
                    };
                    if (await session.LoadAsync(db))
                    {
                        c.Response.StatusCode = 200;
                        c.Response.Content    = await OnlyOffice.ForceSaveAsync(publicUrl, session.key);
                    }
                    await db.CommitAsync();
                }
            };
        }