/// <summary>
        /// What's this?<br />
        /// This will load a CgiScriptServiceInfo object and attempt to serialise it into a JSON string.
        /// </summary>
        /// <param name="serviceInformation"></param>
        /// <returns>Serialised CgiScriptServiceInfo JSON string</returns>
        public static string SaveScript(CgiScriptServiceInfo serviceInformation)
        {
            try
            {
                //validation
                if (serviceInformation != null)
                {
                    //attempt json serialisation
                    var jsonString =
                        JsonConvert.SerializeObject(serviceInformation, Formatting.Indented, GenericJsonSettings.Settings);

                    //validation
                    if (!string.IsNullOrWhiteSpace(jsonString))
                    {
                        return(jsonString);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }

            //default
            return(@"");
        }
        public ArrayList GetTables(SQLiteConnection c)
        {
            var list = new ArrayList();

            // executes query that select names of all tables in master table of the database
            const string query = "SELECT name FROM sqlite_master " +
                                 "WHERE type = 'table'";

            try
            {
                var table = GetDataTable(query, c);

                // Return all table names in the ArrayList

                foreach (DataRow row in table.Rows)
                {
                    var name = row.ItemArray[0].ToString();
                    list.Add(name);
                }
            }
            catch (Exception e)
            {
                UiMessages.Error($"Table load error\n\n{e}");
            }
            return(list);
        }
        private DataSet LoadEntireDatabase(bool waitWindow = true)
        {
            if (waitWindow)
            {
                return((DataSet)ArcWaitWindow.ArcWaitWindow.Show(LoadEntireDatabase, @"Loading SQLite database..."));
            }

            try
            {
                var conn = new SQLiteConnection($@"Data Source={DbFile}");
                conn.Open();

                GlobalConnection = conn;

                var d = new DataSet();
                foreach (var tableName in GetTables(GlobalConnection))
                {
                    d.Tables.Add(GetDataTable($"SELECT * FROM `{tableName}`", GlobalConnection));
                }

                GlobalConnection.Close();

                return(d);
            }
            catch (Exception ex)
            {
                UiMessages.Error($"Database load error\n\n{ex}");
            }

            return(null);
        }
Beispiel #4
0
        private static DataTable FileToDataTable(string filePath)
        {
            try
            {
                var table = new DataTable(@"LH1000_Config");
                table.Columns.Add(@"Setting", typeof(string));
                table.Columns.Add(@"Value", typeof(string));

                var decompressedString = File.ReadAllText(filePath);
                var lineSplit          = decompressedString.Split(
                    new[] { "\r\n", "\r", "\n" },
                    StringSplitOptions.None
                    ).ToList();

                //trim first line due to gibberish
                lineSplit.RemoveAt(0);

                const char delimiter = '=';
                foreach (object[] s in lineSplit.Select(l => l.Split(delimiter)).Where(s => s.Length == 2))
                {
                    table.Rows.Add(s);
                }

                return(table);
            }
            catch (Exception ex)
            {
                UiMessages.Error($"Data load error:\n\n{ex}");
            }

            //default
            return(null);
        }
Beispiel #5
0
        private void DoubleClickProcessor(object sender, DataGridViewCellEventArgs e)
        {
            try
            {
                var senderType = sender.GetType();
                var gridType   = typeof(DataGridView);
                if (senderType == gridType)
                {
                    var gridView = (DataGridView)sender;

                    if (gridView.Rows.Count <= 0)
                    {
                        return;
                    }

                    var value = gridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();

                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        Clipboard.SetText(value);
                        //UiMessages.Info(value, @"Cell Content");
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }
        }
Beispiel #6
0
        /// <summary>
        /// Polls the modem to find out whether authentication is needed (new login request)
        /// </summary>
        /// <param name="waitWindow"></param>
        /// <param name="warningMode"></param>
        /// <returns></returns>
        public static bool TestLogin(bool waitWindow = true, bool warningMode = false)
        {
            if (waitWindow)
            {
                return((bool)ArcWaitWindow.ArcWaitWindow.Show(TestLogin, @"Testing authentication...", warningMode));
            }

            try
            {
                //for some reason, the modem will retain login information
                //even when you quit the app; making it authorise logins
                //if you don't provide credentials.
                var dummyAuth = new ArcCredential(@"", @"");

                //attempt dummy login if the session token is empty
                return(Global.InitToken != null || DoLogin(dummyAuth, false, warningMode, true));
            }
            catch (Exception ex)
            {
                if (!warningMode)
                {
                    UiMessages.Error($"Login test error\n\n{ex}");
                }
                else
                {
                    UiMessages.Warning("We couldn't verify your authentication status; this will affect your " +
                                       "ability to connect to the modem's CGI pages. Please verify if the modem is reachable.");
                }
            }

            //default
            return(false);
        }
Beispiel #7
0
        private void LoadLog(LogType type)
        {
            try
            {
                //download data from modem
                var handler = new CgiSystemLog(type);
                var table   = handler.GrabTable();

                //validate
                if (table != null)
                {
                    if (table.Rows.Count > 0)
                    {
                        //apply log to grid (and global)
                        SetDataSource(table, true);

                        //adjust 'Current Log File' label
                        lblCurrentLogFileValue.Text = type.ToString();
                    }
                    else
                    {
                        UiMessages.Warning(@"Modem returned 0 log entries; operation failed.");
                    }
                }
                else
                {
                    UiMessages.Warning(@"Modem returned a null result; operation failed.");
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error($"Error whilst loading the log:\n\n{ex}");
            }
        }
        private void LoadValues()
        {
            try
            {
                //Check if downloaded firmware version information doesn't exist.
                //If the information needs to be reloaded, we can avoid another fetch.
                if (FwVersion == null)
                {
                    //download firmware information
                    var fwVersion = CgiFirmwareVersion.GetFwVersion();

                    //validation
                    if (fwVersion != null)
                    {
                        //apply global value
                        FwVersion = fwVersion;

                        //render grid
                        dgvMain.DataSource = FromVersionInfo(fwVersion);
                    }
                }
                else
                {
                    //render grid
                    dgvMain.DataSource = FromVersionInfo(FwVersion);
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString(), @"Firmware Version Error");
                Close();
            }
        }
Beispiel #9
0
        public byte[] DecryptHeader()
        {
            try
            {
                if (FirmwareRaw != null)
                {
                    if (FirmwareRaw.Length > 512)
                    {
                        var iv  = FirmwareIV();
                        var key = KeyHandler.Aes256;

                        var fileName  = @"header.rawBytes";
                        var rawHeader = FirmwareSigned.ByteSelect(640, 543);

                        var dec = CryptoHandler.AesDecrypt(rawHeader, key, iv);
                        File.WriteAllBytes(fileName, dec);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }

            //default
            return(null);
        }
Beispiel #10
0
        public static bool IsArcadyanModem(bool waitWindow = true, bool silent = false)
        {
            try
            {
                if (waitWindow)
                {
                    return((bool)ArcWaitWindow.ArcWaitWindow.Show(IsArcadyanModem, @"Verifying modem...", silent));
                }

                //download login page
                var loginPage = ResourceGrab.GrabString(Endpoints.LoginHtm);

                //verify if it contains the correct string
                var validModem = loginPage.Contains(Global.VerificationString);

                //return result
                return(validModem || Global.InitToken != null);
            }
            catch (Exception ex)
            {
                if (!silent)
                {
                    UiMessages.Error($"An error occurred whilst trying to verify your modem:\n\n{ex}");
                }
            }

            //default
            return(false);
        }
Beispiel #11
0
        private void HandsetFilterHandler(object sender, EventArgs e)
        {
            try
            {
                //double check the sender's authenticity as a menu item
                if (sender.GetType() == typeof(ToolStripMenuItem))
                {
                    //recast sender to restore type
                    var handsetItem = (ToolStripMenuItem)sender;

                    //value to filter is the menu item's text
                    var filterBy = handsetItem.Text;

                    //new search context for filtering
                    var searchContext = new SearchContext
                    {
                        SearchColumn    = HandsetColumn,
                        SearchSubmitted = true,
                        SearchTerm      = filterBy
                    };

                    //set UI
                    itmSearch.Text     = @"Cancel Search";
                    itmHandset.Enabled = false;

                    //initialise search
                    DoGridSearch(searchContext);
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }
        }
Beispiel #12
0
 private void ItmAuthenticateGrant_Click(object sender, EventArgs e)
 {
     try
     {
         //test authentication
         var testLogin = ArcLogin.TestLogin();
         if (testLogin)
         {
             UiMessages.Warning(@"Authentication failed: user is already authenticated");
         }
         else if (Global.InitToken == null)
         {
             var loginSuccess = LoginForm.ShowLogin();
             if (loginSuccess)
             {
                 UpdateUIAuthenticate(true);
                 UiMessages.Info(@"Successfully authenticated user");
             }
             else
             {
                 UiMessages.Warning(@"Authentication failed: login was unsuccessful");
             }
         }
         else
         {
             UiMessages.Warning(@"Authentication failed: login data already initialised");
         }
     }
     catch (Exception ex)
     {
         UiMessages.Error(ex.ToString());
     }
 }
Beispiel #13
0
 private void ItmAuthenticateRevoke_Click(object sender, EventArgs e)
 {
     try
     {
         //test authentication
         var testLogin = ArcLogin.TestLogin();
         if (!testLogin)
         {
             UiMessages.Warning(@"Revocation failed: login not yet initiated");
         }
         else if (Global.InitToken != null)
         {
             var logoutSuccess = Global.InitToken.Revoke();
             if (logoutSuccess)
             {
                 UpdateUIAuthenticate();
                 Global.InitToken = null;
                 UiMessages.Info(@"Successfully logged user out");
             }
             else
             {
                 UiMessages.Warning(@"Revocation failed: logout was unsuccessful");
             }
         }
         else
         {
             UiMessages.Warning(@"Revocation failed: login data not yet initiated");
         }
     }
     catch (Exception ex)
     {
         UiMessages.Error(ex.ToString());
     }
 }
Beispiel #14
0
        /// <summary>
        /// What's this?<br />
        /// This will load a CgiScriptServiceInfo object, attempt to serialise it into a JSON string and then write that string to the file specified.
        /// </summary>
        /// <param name="serviceInformation"></param>
        /// <param name="filePath"></param>
        /// <returns>True on success</returns>
        public static bool SaveScript(CgiScriptServiceInfo serviceInformation, string filePath)
        {
            try
            {
                //validation
                if (serviceInformation != null && !string.IsNullOrWhiteSpace(filePath))
                {
                    //if the file exists, delete it
                    if (File.Exists(filePath))
                    {
                        File.Delete(filePath);
                    }

                    //attempt serialisation
                    var jsonString = SaveScript(serviceInformation);

                    //validation
                    if (!string.IsNullOrWhiteSpace(jsonString))
                    {
                        //write the string to a file
                        File.WriteAllText(filePath, jsonString);

                        //report success
                        return(true);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }

            //default
            return(false);
        }
Beispiel #15
0
        public void ExtractStrings()
        {
            try
            {
                using (var file = new ElfFile(StreamLoader.FromFile(ElfFilePath)))
                {
                    if (file.Header.IsValid)
                    {
                        var strings = new List <string>();

                        foreach (var strSec in file.GetStringSections())
                        {
                            foreach (var str in strSec)
                            {
                                if (str != null)
                                {
                                    var n   = str.Name;
                                    var idx = str.Index;
                                    var e   = $"STRING ENTRY: {idx} :: {n}";
                                    strings.Add(e);
                                }
                            }
                        }
                        File.WriteAllLines(@"strings.log", strings);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }
        }
Beispiel #16
0
        public Station[] GrabDevicesObject()
        {
            try
            {
                var jsonRaw = GrabJSON();

                if (!string.IsNullOrEmpty(jsonRaw))
                {
                    if (jsonRaw == @"404")
                    {
                        UiMessages.Warning(@"Modem reported inaccessible (404 result)");
                    }
                    else
                    {
                        return(JsonConvert.DeserializeObject <StationList>(jsonRaw, GenericJsonSettings.Settings)?.Stations);
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"TopologyGrab error:\n\n{ex}");
            }

            //default
            return(null);
        }
Beispiel #17
0
        private void DoTextSearch(SearchContext cxt)
        {
            var startIndex = 0;
            var foundMatch = false;

            while (startIndex < txtMain.TextLength)
            {
                var wordStartIndex = txtMain.Find(cxt.SearchTerm, startIndex, RichTextBoxFinds.None);
                if (wordStartIndex > -1)
                {
                    foundMatch = true;

                    txtMain.SelectionStart     = wordStartIndex;
                    txtMain.SelectionLength    = cxt.SearchTerm.Length;
                    txtMain.SelectionBackColor = Color.Yellow;
                }
                else
                {
                    break;
                }

                startIndex += wordStartIndex + cxt.SearchTerm.Length;
            }

            if (!foundMatch)
            {
                UiMessages.Warning($"Nothing found for '{cxt.SearchTerm}'", @"No Results");
                CancelSearch();
            }
            else
            {
                itmSearch.Text = @"Cancel Search";
            }
        }
Beispiel #18
0
        private void ItmFileOpenConfig_Click(object sender, EventArgs e)
        {
            if (Directory.Exists(ArcArchive.ExtractDir))
            {
                if (Directory.GetFiles(ArcArchive.ExtractDir).Length > 0)
                {
                    if (UiMessages.Question(@"You already have an extracted config file; load it instead?"))
                    {
                        LoadTreeView(ArcArchive.ExtractDir);
                        return;
                    }
                }
            }

            var filePath = GetFileName();

            if (!string.IsNullOrEmpty(filePath))
            {
                CurrentFile = filePath;

                //decrypt and extract
                ArcArchive.ProcessConfigArchive(filePath);

                //UI setup
                LoadTreeView(ArcArchive.ExtractDir);
                itmRefreshConfig.Enabled = true;
            }
        }
        /// <summary>
        /// What's this?<br />
        /// This will load a JSON string and attempt to deserialise it into a CgiScriptServiceInfo object.
        /// </summary>
        /// <param name="script"></param>
        /// <returns>Deserialised CgiScriptServiceInfo object</returns>
        public static CgiScriptServiceInfo LoadScript(string script)
        {
            try
            {
                //validation
                if (!string.IsNullOrWhiteSpace(script))
                {
                    //attempt json deserialisation
                    var jsonObject =
                        JsonConvert.DeserializeObject <CgiScriptServiceInfo>(script, GenericJsonSettings.Settings);

                    //validation
                    if (jsonObject != null)
                    {
                        return(jsonObject);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }

            //default
            return(null);
        }
        public static CgiFirmwareVersion GetFwVersion(bool waitWindow = true, bool silent = false)
        {
            try
            {
                //multi-threaded
                if (waitWindow)
                {
                    return((CgiFirmwareVersion)ArcWaitWindow.ArcWaitWindow.Show(GetFwVersion, @"Grabbing firmware version...", silent));
                }

                //download cgi_init.js
                var initHandler = new CgiInitScript();
                var init        = initHandler.GrabJS(false);

                //validation
                if (!string.IsNullOrEmpty(init))
                {
                    //RegEx setup
                    var regexBuild   = new Regex(@"var init_ALDK_def_version=""\s*(.*?)"";", RegexOptions.Multiline);
                    var regexVersion = new Regex(@"var init_code_version=""\s*(.*?)"";", RegexOptions.Multiline);
                    var regexModel   = new Regex(@"var def_product_name=""\s*(.*?)"";", RegexOptions.Multiline);

                    //RegEx matching
                    var build   = regexBuild.Match(init).Groups[1].Value;
                    var version = regexVersion.Match(init).Groups[1].Value;
                    var model   = regexModel.Match(init).Groups[1].Value;

                    //validation
                    if (!string.IsNullOrWhiteSpace(build) &&
                        !string.IsNullOrWhiteSpace(version) &&
                        !string.IsNullOrWhiteSpace(model))
                    {
                        //object build
                        var cgiFwVer = new CgiFirmwareVersion
                        {
                            BuildString   = build,
                            VersionString = version,
                            ModelString   = model
                        };

                        //return final result
                        return(cgiFwVer);
                    }
                }
            }
            catch (Exception ex)
            {
                if (!silent)
                {
                    UiMessages.Error(ex.ToString(), @"GetFwVersion Error");
                }
            }

            //default
            return(null);
        }
Beispiel #21
0
        public decimal AvgLinkRate(bool onlyOnlineLinks)
        {
            try
            {
                var t = (DataTable)dgvMain.DataSource;

                if (t != null)
                {
                    if (t.Rows.Count > 0)
                    {
                        var lnkRateCount = t.Rows.Count;
                        var lnkRateSum   = 0;

                        foreach (DataRow l in t.Rows)
                        {
                            try
                            {
                                var rawLinkRate = ((string)l[@"Link Rate"]).Substring(0, ((string)l[@"Link Rate"]).Length - 4);
                                var r           = Convert.ToInt32(rawLinkRate);

                                if (((string)l[@"Online"] == @"Offline" || (string)l[@"Online"] == @"0") && onlyOnlineLinks)
                                {
                                    lnkRateCount--;
                                }
                                else
                                {
                                    lnkRateSum += r;
                                }
                            }
                            catch (Exception ex)
                            {
                                UiMessages.Error(ex.ToString());
                                break;
                            }
                        }

                        //calculate average
                        var lnkAverage =
                            lnkRateSum > 0 && lnkRateCount > 0
                            ? lnkRateSum / lnkRateCount
                            : 0;

                        //return average
                        return(lnkAverage);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error($"Link rate maths error: {ex.Message}");
            }

            //default
            return(0);
        }
Beispiel #22
0
        private TimeSpan TotalTime(bool justOutTime = false)
        {
            try
            {
                if (Data != null)
                {
                    if (Data.Rows.Count > 0)
                    {
                        var totalTime = new TimeSpan(0);
                        var t         = (DataTable)dgvMain.DataSource;
                        if (t != null)
                        {
                            foreach (DataRow r in t.Rows)
                            {
                                try
                                {
                                    if (r[@"Type"] != null)
                                    {
                                        if (!r[@"Type"].ToString().Contains(@"OUT") && justOutTime)
                                        {
                                            continue;
                                        }
                                    }

                                    if (r[@"Duration"] != null)
                                    {
                                        if (!string.IsNullOrWhiteSpace((string)r[@"Duration"]))
                                        {
                                            var v = (string)r[@"Duration"];
                                            var d = TimeSpan.Parse(v);
                                            totalTime = totalTime.Add(d);
                                        }
                                    }
                                }
                                catch
                                {
                                    /* ignore */
                                }
                            }
                        }

                        return(totalTime);
                    }
                }
            }
            catch (Exception ex)
            {
                UiMessages.Error(ex.ToString());
            }

            //default
            return(TimeSpan.Zero);
        }
Beispiel #23
0
 private void UpdateUIAuthenticate(bool authenticated = false)
 {
     try
     {
         itmAuthenticateGrant.Enabled  = !authenticated;
         itmTryDefault.Enabled         = !authenticated;
         itmAuthenticateRevoke.Enabled = authenticated;
     }
     catch (Exception ex)
     {
         UiMessages.Error(ex.ToString());
     }
 }
Beispiel #24
0
        private void BtnTextSearch_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrWhiteSpace(txtTextSearchTerm.Text))
            {
                SearchTerm = txtTextSearchTerm.Text;

                DialogResult = DialogResult.OK;
                Close();
            }
            else
            {
                UiMessages.Error(@"Please correctly fill all required fields", @"Validation Error");
            }
        }
        private void DoJsonLoad()
        {
            try
            {
                //UI set
                AllTextReadOnly(true);

                //show open file dialog to locate a JSON file
                var jsonFile = FindJson();

                //validation
                if (!string.IsNullOrWhiteSpace(jsonFile))
                {
                    //try deserialisation
                    var jsonHandler = new CgiScriptFileService(jsonFile);
                    var jsonObject  = jsonHandler.ServiceAuthInfo;

                    //validation
                    if (jsonObject != null)
                    {
                        //fill UI
                        FillValues(jsonObject);

                        //clear script box
                        browserMain.DocumentText = @"";

                        //unlock fetch button
                        btnFetchScript.Enabled = true;

                        //assign globals
                        FetchEnabled  = true;
                        ScriptService = jsonHandler;

                        //report success
                        UiMessages.Info(@"Successfully loaded JSON service information");
                    }
                    else
                    {
                        UiMessages.Error(@"Deserialisation failed; service information was null");
                    }
                }

                //UI set
                AllTextReadOnly(false);
            }
            catch (Exception ex)
            {
                UiMessages.Error($"Error occurred whilst trying to load a JSON script information file:\n\n{ex}");
            }
        }
Beispiel #26
0
        private void BtnSearchGrid_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrWhiteSpace(txtSearchTerm.Text) && cbxColumn.SelectedIndex > -1)
            {
                SearchColumn = cbxColumn.SelectedItem.ToString();
                SearchTerm   = txtSearchTerm.Text;

                DialogResult = DialogResult.OK;
                Close();
            }
            else
            {
                UiMessages.Error(@"Please correctly fill all required fields", @"Validation Error");
            }
        }
Beispiel #27
0
 /// <summary>
 /// What's this?<br />
 /// Save the JavaScript file to disk, and if it isn't already downloaded, fetch a new copy and save it.
 /// </summary>
 /// <param name="fileName"></param>
 public void ToFile(string fileName)
 {
     try
     {
         if (File.Exists(fileName))
         {
             File.Delete(fileName);
         }
         File.WriteAllText(fileName, !string.IsNullOrWhiteSpace(RawJS) ? RawJS : GrabJS());
     }
     catch (Exception ex)
     {
         UiMessages.Error($"ScriptService ToFile() error:\n\n{ex})");
     }
 }
Beispiel #28
0
        public static void ExportThis(this DataTable table, ExportFormat format, string fileName, bool silent = false, bool waitWindow = true)
        {
            if (waitWindow)
            {
                ArcWaitWindow.ArcWaitWindow.Show(ExportThis, @"Exporting...", table, format, fileName, silent);
            }
            else
            {
                var success = true;

                switch (format)
                {
                case ExportFormat.Json:
                    table.ToJson(fileName);
                    break;

                case ExportFormat.Xml:
                    table.ToXml(fileName);
                    break;

                case ExportFormat.Csv:
                    table.ToCsv(fileName);
                    break;

                case ExportFormat.Cfg:
                    table.ToCfg(fileName);
                    break;

                case ExportFormat.Txt:
                    table.ToTxt(fileName);
                    break;

                default:
                    if (!silent)
                    {
                        UiMessages.Error(@"Unrecognised export format");
                        success = false;
                    }

                    break;
                }

                if (!silent && success)
                {
                    UiMessages.Info($"Successfully exported data to: {fileName}");
                }
            }
        }
Beispiel #29
0
 public void DecryptImage(string fileName = @"fw.dec")
 {
     try
     {
         if (FirmwareRaw != null)
         {
             if (FirmwareRaw.Length > 512)
             {
                 var header = DecryptHeader();
             }
         }
     }
     catch (Exception ex)
     {
         UiMessages.Error(ex.ToString());
     }
 }
Beispiel #30
0
        private void DoGridSearch(SearchContext cxt)
        {
            var query         = $"`{cxt.SearchColumn}` LIKE '%{cxt.SearchTerm}%'";
            var table         = OriginalData.Copy();
            var filteredTable = table.Select(query);

            if (filteredTable.Length > 0)
            {
                dgvMain.DataSource = filteredTable.CopyToDataTable();
                itmSearch.Text     = @"Cancel Search";
            }
            else
            {
                UiMessages.Warning($"Nothing found for '{cxt.SearchTerm}'", @"No Results");
                CancelSearch();
            }
        }