コード例 #1
0
        public static AjaxCallResult SetBitcoinPayoutAddress(string bitcoinAddress)
        {
            AuthenticationData authData = GetAuthenticationDataAndCulture();

            if (authData == null)
            {
                throw new UnauthorizedAccessException();
            }

            // Remove whitespace from submitted address (whitespace will probably be entered in some cases)

            bitcoinAddress = bitcoinAddress.Replace(" ", string.Empty);

            // Remove a possible start of "bitcoincash:"

            if (bitcoinAddress.StartsWith("bitcoincash:"))
            {
                bitcoinAddress = bitcoinAddress.Substring("bitcoincash:".Length);
            }

            if (string.IsNullOrEmpty(authData.CurrentUser.BitcoinPayoutAddress))
            {
                if (!BitcoinUtility.IsValidBitcoinAddress(bitcoinAddress))
                {
                    return(new AjaxCallResult {
                        Success = false, DisplayMessage = "Invalid address"
                    });
                }

                authData.CurrentUser.BitcoinPayoutAddress        = bitcoinAddress;
                authData.CurrentUser.BitcoinPayoutAddressTimeSet = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture);

                // TODO: Create notifications for CEO and for user

                NotificationCustomStrings strings = new NotificationCustomStrings();
                strings["BitcoinAddress"] = bitcoinAddress;

                OutboundComm userNotify = OutboundComm.CreateNotification(authData.CurrentOrganization,
                                                                          "BitcoinPayoutAddress_Set", strings, People.FromSingle(authData.CurrentUser));

                strings["ConcernedPersonName"] = authData.CurrentUser.Canonical;

                OutboundComm adminNotify = OutboundComm.CreateNotification(authData.CurrentOrganization,
                                                                           "BitcoinPayoutAddress_Set_OfficerNotify", strings); // will send to admins of org as no people specified

                return(new AjaxCallResult {
                    Success = true
                });
            }
            else
            {
                // If the address is already set

                return(new AjaxCallResult {
                    Success = false, DisplayMessage = "Address already set"
                });
            }
        }
コード例 #2
0
        public static void VerifyBitcoinHotWallet()
        {
            // This must only be run from the backend

            if (HttpContext.Current != null)
            {
                throw new InvalidOperationException("Checking root keys cannot be done from the frontend");
            }

            // Make sure there's always a private hotwallet root, regardless of whether it's used or not

            if (!File.Exists(SystemSettings.EtcFolder + Path.DirectorySeparatorChar + "hotwallet"))
            {
                ExtKey privateRoot = new ExtKey();
                File.WriteAllText(SystemSettings.EtcFolder + Path.DirectorySeparatorChar + "hotwallet",
                                  privateRoot.GetWif(Network.Main).ToWif(), Encoding.ASCII);
                File.WriteAllText(
                    SystemSettings.EtcFolder + Path.DirectorySeparatorChar + "hotwallet-created-" +
                    DateTime.UtcNow.ToString("yyyy-MM-dd--HH-mm-ss--fff.backup"),
                    privateRoot.GetWif(Network.Main).ToWif(), Encoding.ASCII);  // an extra backup

                if (String.IsNullOrEmpty(Persistence.Key["BitcoinHotPublicRoot"]))
                {
                    Persistence.Key["BitcoinHotPublicRoot"] = privateRoot.Neuter().GetWif(Network.Main).ToWif();
                }
            }
            else
            {
                // The file exists. Does the database have the hotwallet public root?

                if (Persistence.Key["BitcoinHotPublicRoot"].Length < 3)
                {
                    // No, it has disappeared, which can happen for a few bad reasons

                    Persistence.Key["BitcoinHotPublicRoot"] =
                        BitcoinHotPrivateRoot.Neuter().GetWif(Network.Main).ToWif();
                    if (!PilotInstallationIds.IsPilot(PilotInstallationIds.DevelopmentSandbox))
                    {
                        // TODO: Log some sort of exception (the sandbox db is reset every night, so it's ok to lose the public key from there)
                    }
                }

                // Is the hotwallet public root equal to the private root, while in production environment?

                // ReSharper disable once RedundantCheckBeforeAssignment
                if (Persistence.Key["BitcoinHotPublicRoot"] !=
                    BitcoinHotPrivateRoot.Neuter().GetWif(Network.Main).ToWif() && !Debugger.IsAttached)
                {
                    // SERIOUS CONDITION - the public root key did not match the private root key. This needs to be logged somewhere.
                    OutboundComm.CreateNotification(NotificationResource.System_PublicRootReset);

                    // Reset it
                    Persistence.Key["BitcoinHotPublicRoot"] =
                        BitcoinHotPrivateRoot.Neuter().GetWif(Network.Main).ToWif();
                }
            }
        }
コード例 #3
0
        public static void UpgradeSchemata()
        {
            int    currentDbVersion  = SwarmDb.DbVersion;
            int    expectedDbVersion = SwarmDb.DbVersionExpected;
            string sql;
            bool   upgraded = false;

            while (currentDbVersion < expectedDbVersion)
            {
                currentDbVersion++;

                string fileName = String.Format("http://packages.swarmops.com/schemata/upgrade-{0:D4}.sql",
                                                currentDbVersion);

                using (WebClient client = new WebClient())
                {
                    sql = client.DownloadString(fileName);
                }

                string[] sqlCommands = sql.Split('#');
                // in the file, the commands are split by a single # sign. (Semicolons are an integral part of storedprocs, so they can't be used.)

                foreach (string sqlCommand in sqlCommands)
                {
                    try
                    {
                        SwarmDb.GetDatabaseForAdmin().ExecuteAdminCommand(sqlCommand.Trim());
                    }
                    catch (MySqlException exception)
                    {
                        SwarmDb.GetDatabaseForWriting()
                        .CreateExceptionLogEntry(DateTime.UtcNow, "DatabaseUpgrade", exception);

                        // Continue processing after logging error.
                        // TODO: Throw and abort? Tricky decision
                    }
                }

                upgraded = true;
                SwarmDb.GetDatabaseForWriting()
                .SetKeyValue("DbVersion", currentDbVersion.ToString(CultureInfo.InvariantCulture));
                // Increment after each successful run
            }

            if (upgraded)
            {
                try
                {
                    OutboundComm.CreateNotification(null, NotificationResource.System_DatabaseSchemaUpgraded);
                }
                catch (ArgumentException)
                {
                    // this is ok - if we're in the install process, person 1 or other notification targets won't exist yet
                }
            }
        }
コード例 #4
0
        public static void UpgradeSchemata()
        {
            int    currentDbVersion  = SwarmDb.DbVersion;
            int    expectedDbVersion = SwarmDb.DbVersionExpected;
            string sql;
            bool   upgraded = false;

            if (currentDbVersion < expectedDbVersion)
            {
                Console.WriteLine("Swarmops: Current DB version is {0}, but expected is {1}. A schema upgrade will take place.", currentDbVersion, expectedDbVersion);
            }

            while (currentDbVersion < expectedDbVersion)
            {
                currentDbVersion++;

                Console.Write("Schema {0} diff: Fetching...", currentDbVersion);

                string fileName = String.Format("http://packages.swarmops.com/schemata/upgrade-{0:D4}.sql",
                                                currentDbVersion);

                try
                {
                    using (WebClient client = new WebClient())
                    {
                        sql = client.DownloadString(fileName);
                    }
                }
                catch (Exception outerException)
                {
                    Console.WriteLine(" trying fallback...");

                    // Because Mono installs with an insufficient certificate store, we must disable certificate checking before accessing github

                    SupportFunctions.DisableSslCertificateChecks();

                    fileName = String.Format("https://raw.githubusercontent.com/Swarmops/Swarmops/master/Database/Schemata/upgrade-{0:D4}.sql",
                                             currentDbVersion);

                    try
                    {
                        using (WebClient client = new WebClient())
                        {
                            sql = client.DownloadString(fileName);
                        }
                    }
                    catch (Exception middleException)
                    {
                        try
                        {
                            OutboundComm.CreateNotification(null, NotificationResource.System_DatabaseUpgradeFailed);
                        }
                        catch (ArgumentException)
                        {
                            // if this happens during setup:

                            throw new Exception("Failed fetching upgrade packages:\r\n" + middleException.ToString() +
                                                "\r\n" + outerException.ToString());
                        }
                        Console.WriteLine(" FAILED! Aborting.");

                        return;
                    }
                }

                string[] sqlCommands = sql.Split('#');
                // in the file, the commands are split by a single # sign. (Semicolons are an integral part of storedprocs, so they can't be used.)

                Console.Write(" applying...");

                foreach (string sqlCommand in sqlCommands)
                {
                    try
                    {
                        string trimmedCommand = sqlCommand.Trim().TrimEnd(';').Trim(); // removes whitespace first, then any ; at the end (if left in by mistake)

                        if (!String.IsNullOrWhiteSpace(trimmedCommand))
                        {
                            SwarmDb.GetDatabaseForAdmin().ExecuteAdminCommand(trimmedCommand);
                        }
                    }
                    catch (MySqlException exception)
                    {
                        SwarmDb.GetDatabaseForWriting()
                        .CreateExceptionLogEntry(DateTime.UtcNow, "DatabaseUpgrade",
                                                 new Exception(string.Format("Exception upgrading to Db{0:D4}", currentDbVersion),
                                                               exception));

                        Console.Write(" EXCEPTION (see log)!");
                        // Continue processing after logging error.
                        // TODO: Throw and abort? Tricky decision
                    }
                }

                upgraded = true;
                SwarmDb.GetDatabaseForWriting()
                .SetKeyValue("DbVersion", currentDbVersion.ToString(CultureInfo.InvariantCulture));
                // Increment after each successful run

                Console.WriteLine(" done.");
            }

            if (upgraded)
            {
                Console.WriteLine("Swarmops database schema upgrade completed.\r\n");
                try
                {
                    OutboundComm.CreateNotification(null, NotificationResource.System_DatabaseSchemaUpgraded);
                }
                catch (ArgumentException)
                {
                    // this is ok - if we're in the install process, person 1 or other notification targets won't exist yet
                }
            }
        }
コード例 #5
0
        public static void PerformAutomated(BitcoinChain chain)
        {
            // Perform all waiting hot payouts for all orgs in the installation

            throw new NotImplementedException("Waiting for rewrite for Bitcoin Cash");

            // TODO

            DateTime utcNow = DateTime.UtcNow;

            foreach (Organization organization in Organizations.GetAll())
            {
                // If this org doesn't do hotwallet, continue
                if (organization.FinancialAccounts.AssetsBitcoinHot == null)
                {
                    continue;
                }

                Payouts orgPayouts     = Payouts.Construct(organization);
                Payouts bitcoinPayouts = new Payouts();
                Dictionary <string, Int64> satoshiPayoutLookup     = new Dictionary <string, long>();
                Dictionary <string, Int64> nativeCentsPayoutLookup = new Dictionary <string, long>();
                Dictionary <int, Int64>    satoshiPersonLookup     = new Dictionary <int, long>();
                Dictionary <int, Int64>    nativeCentsPersonLookup = new Dictionary <int, long>();
                Int64 satoshisTotal = 0;

                string currencyCode = organization.Currency.Code;

                // For each ready payout that can automate, add an output to a constructed transaction

                TransactionBuilder txBuilder = null; // TODO TODO TODO TODO new TransactionBuilder();
                foreach (Payout payout in orgPayouts)
                {
                    if (payout.ExpectedTransactionDate > utcNow)
                    {
                        continue; // payout is not due yet
                    }

                    if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length > 2 &&
                        payout.Account.Length < 4)
                    {
                        // If the payout address is still in quarantine, don't pay out yet

                        string addressSetTime = payout.RecipientPerson.BitcoinPayoutAddressTimeSet;
                        if (addressSetTime.Length > 4 && DateTime.Parse(addressSetTime, CultureInfo.InvariantCulture).AddHours(48) > utcNow)
                        {
                            continue; // still in quarantine
                        }

                        // Test the payout address - is it valid and can we handle it?

                        if (!BitcoinUtility.IsValidBitcoinAddress(payout.RecipientPerson.BitcoinPayoutAddress))
                        {
                            // Notify person that address is invalid, then clear it

                            NotificationStrings       primaryStrings   = new NotificationStrings();
                            NotificationCustomStrings secondaryStrings = new NotificationCustomStrings();
                            primaryStrings[NotificationString.OrganizationName] = organization.Name;
                            secondaryStrings["BitcoinAddress"] = payout.RecipientPerson.BitcoinPayoutAddress;

                            OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_Bad,
                                                            primaryStrings, secondaryStrings,
                                                            People.FromSingle(payout.RecipientPerson));

                            payout.RecipientPerson.BitcoinPayoutAddress = string.Empty;

                            continue; // do not add this payout
                        }

                        // Ok, so it seems we're making this payout at this time.

                        bitcoinPayouts.Add(payout);

                        int recipientPersonId = payout.RecipientPerson.Identity;

                        if (!satoshiPersonLookup.ContainsKey(recipientPersonId))
                        {
                            satoshiPersonLookup[recipientPersonId]     = 0;
                            nativeCentsPersonLookup[recipientPersonId] = 0;
                        }

                        nativeCentsPersonLookup[recipientPersonId] += payout.AmountCents;

                        // Find the amount of satoshis for this payout

                        if (organization.Currency.IsBitcoinCore)
                        {
                            satoshiPayoutLookup[payout.ProtoIdentity]     = payout.AmountCents;
                            nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents;
                            satoshisTotal += payout.AmountCents;
                            satoshiPersonLookup[recipientPersonId] += payout.AmountCents;
                        }
                        else
                        {
                            // Convert currency
                            Money payoutAmount = new Money(payout.AmountCents, organization.Currency);
                            Int64 satoshis     = payoutAmount.ToCurrency(Currency.BitcoinCore).Cents;
                            satoshiPayoutLookup[payout.ProtoIdentity]     = satoshis;
                            nativeCentsPayoutLookup[payout.ProtoIdentity] = payout.AmountCents;
                            satoshisTotal += satoshis;
                            satoshiPersonLookup[recipientPersonId] += satoshis;
                        }
                    }
                    else if (payout.RecipientPerson != null && payout.RecipientPerson.BitcoinPayoutAddress.Length < 3 && payout.Account.Length < 4)
                    {
                        // There is a payout for this person, but they don't have a bitcoin payout address set. Send notification to this effect once a day.

                        if (utcNow.Minute != 0)
                        {
                            continue;
                        }
                        if (utcNow.Hour != 12)
                        {
                            continue;
                        }

                        NotificationStrings primaryStrings = new NotificationStrings();
                        primaryStrings[NotificationString.OrganizationName] = organization.Name;
                        OutboundComm.CreateNotification(organization, NotificationResource.BitcoinPayoutAddress_PleaseSet, primaryStrings, People.FromSingle(payout.RecipientPerson));
                    }
                    else if (payout.Account.StartsWith("bitcoin:"))
                    {
                    }
                }

                if (bitcoinPayouts.Count == 0)
                {
                    // no automated payments pending for this organization, nothing more to do
                    continue;
                }

                // We now have our desired payments. The next step is to find enough inputs to reach the required amount (plus fees; we're working a little blind here still).

                BitcoinTransactionInputs inputs      = null;
                Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain) * 20; // assume max 20k transaction size

                try
                {
                    inputs = BitcoinUtility.GetInputsForAmount(organization, satoshisTotal + satoshisMaximumAnticipatedFees);
                }
                catch (NotEnoughFundsException)
                {
                    // If we're at the whole hour, send a notification to people responsible for refilling the hotwallet

                    if (utcNow.Minute != 0)
                    {
                        continue; // we're not at the whole hour, so continue with next org instead
                    }

                    // Send urgent notification to top up the damn wallet so we can make payments

                    NotificationStrings primaryStrings = new NotificationStrings();
                    primaryStrings[NotificationString.CurrencyCode]     = organization.Currency.Code;
                    primaryStrings[NotificationString.OrganizationName] = organization.Name;
                    NotificationCustomStrings secondaryStrings = new NotificationCustomStrings();
                    Int64 satoshisAvailable = HotBitcoinAddresses.ForOrganization(organization).BalanceSatoshisTotal;

                    secondaryStrings["AmountMissingMicrocoinsFloat"] =
                        ((satoshisTotal - satoshisAvailable + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2");

                    if (organization.Currency.IsBitcoinCore)
                    {
                        secondaryStrings["AmountNeededFloat"] = ((satoshisTotal + satoshisMaximumAnticipatedFees) / 100.0).ToString("N2");
                        secondaryStrings["AmountWalletFloat"] = (satoshisAvailable / 100.0).ToString("N2");
                    }
                    else
                    {
                        // convert to org native currency

                        secondaryStrings["AmountNeededFloat"] =
                            (new Money(satoshisTotal, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2");
                        secondaryStrings["AmountWalletFloat"] =
                            (new Money(satoshisAvailable, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents / 100.0).ToString("N2");
                    }

                    OutboundComm.CreateNotification(organization,
                                                    NotificationResource.Bitcoin_Shortage_Critical, primaryStrings, secondaryStrings, People.FromSingle(Person.FromIdentity(1)));

                    continue; // with next organization
                }

                // If we arrive at this point, the previous function didn't throw, and we have enough money.
                // Ensure the existence of a cost account for bitcoin miner fees.

                organization.EnsureMinerFeeAccountExists();

                // Add the inputs to the transaction.

                txBuilder = txBuilder.AddCoins(inputs.Coins);
                txBuilder = txBuilder.AddKeys(inputs.PrivateKeys);
                Int64 satoshisInput = inputs.AmountSatoshisTotal;

                // Add outputs and prepare notifications

                Int64 satoshisUsed = 0;
                Dictionary <int, List <string> > notificationSpecLookup   = new Dictionary <int, List <string> >();
                Dictionary <int, List <Int64> >  notificationAmountLookup = new Dictionary <int, List <Int64> >();
                Payout            masterPayoutPrototype = Payout.Empty;
                HotBitcoinAddress changeAddress         = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core); // TODO: CHAIN!

                foreach (Payout payout in bitcoinPayouts)
                {
                    int recipientPersonId = payout.RecipientPerson.Identity;
                    if (!notificationSpecLookup.ContainsKey(recipientPersonId))
                    {
                        notificationSpecLookup[recipientPersonId]   = new List <string>();
                        notificationAmountLookup[recipientPersonId] = new List <Int64>();
                    }
                    notificationSpecLookup[recipientPersonId].Add(payout.Specification);
                    notificationAmountLookup[recipientPersonId].Add(payout.AmountCents);

                    if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("1"))  // regular address
                    {
                        txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(payout.RecipientPerson.BitcoinPayoutAddress),
                                                   new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity]));
                    }
                    else if (payout.RecipientPerson.BitcoinPayoutAddress.StartsWith("3"))  // multisig
                    {
                        txBuilder = txBuilder.Send(new BitcoinScriptAddress(payout.RecipientPerson.BitcoinPayoutAddress, Network.Main),
                                                   new Satoshis(satoshiPayoutLookup[payout.ProtoIdentity]));
                    }
                    else
                    {
                        throw new InvalidOperationException("Unhandled bitcoin address type in Payouts.PerformAutomated(): " + payout.RecipientPerson.BitcoinPayoutAddress);
                    }

                    satoshisUsed += satoshiPayoutLookup[payout.ProtoIdentity];

                    payout.MigrateDependenciesTo(masterPayoutPrototype);
                }

                // Set change address to wallet slush

                txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address));

                // Add fee

                int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count;
                // +inputs.Count for size variability

                Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) * BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(chain);

                txBuilder     = txBuilder.SendFees(new Satoshis(feeSatoshis));
                satoshisUsed += feeSatoshis;

                // Sign transaction - ready to execute

                Transaction txReady = txBuilder.BuildTransaction(true);

                // Verify that transaction is ready

                if (!txBuilder.Verify(txReady))
                {
                    // Transaction was not signed with the correct keys. This is a serious condition.

                    NotificationStrings primaryStrings = new NotificationStrings();
                    primaryStrings[NotificationString.OrganizationName] = organization.Name;

                    OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError,
                                                    primaryStrings);

                    throw new InvalidOperationException("Transaction is not signed enough");
                }

                // Broadcast transaction

                BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash);

                // Note the transaction hash

                string transactionHash = txReady.GetHash().ToString();

                // Delete all old inputs, adjust balance for addresses (re-register unused inputs)

                inputs.AsUnspents.DeleteAll();

                // Log the new unspent created by change (if there is any)

                if (satoshisInput - satoshisUsed > 0)
                {
                    SwarmDb.GetDatabaseForWriting()
                    .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash,
                                                               +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0);
                }

                // Register new balance of change address, should have increased by (satoshisInput-satoshisUsed)

                // TODO

                // Send notifications

                foreach (int personId in notificationSpecLookup.Keys)
                {
                    Person person = Person.FromIdentity(personId);

                    string spec = string.Empty;
                    for (int index = 0; index < notificationSpecLookup[personId].Count; index++)
                    {
                        spec += String.Format(" * {0,-40} {1,14:N2} {2,-4}\r\n", notificationSpecLookup[personId][index], notificationAmountLookup[personId][index] / 100.0, currencyCode);
                    }

                    spec = spec.TrimEnd();

                    NotificationStrings       primaryStrings   = new NotificationStrings();
                    NotificationCustomStrings secondaryStrings = new NotificationCustomStrings();

                    primaryStrings[NotificationString.OrganizationName]         = organization.Name;
                    primaryStrings[NotificationString.CurrencyCode]             = organization.Currency.DisplayCode;
                    primaryStrings[NotificationString.EmbeddedPreformattedText] = spec;
                    secondaryStrings["AmountFloat"]        = (nativeCentsPersonLookup[personId] / 100.0).ToString("N2");
                    secondaryStrings["BitcoinAmountFloat"] = (satoshiPersonLookup[personId] / 100.0).ToString("N2");
                    secondaryStrings["BitcoinAddress"]     = person.BitcoinPayoutAddress; // warn: potential rare race condition here

                    OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PaidOut, primaryStrings,
                                                    secondaryStrings, People.FromSingle(person));
                }

                // Create the master payout from its prototype

                Payout masterPayout = Payout.CreateBitcoinPayoutFromPrototype(organization, masterPayoutPrototype, txReady.GetHash().ToString());

                // Finally, create ledger entries and notify

                NotificationStrings       masterPrimaryStrings   = new NotificationStrings();
                NotificationCustomStrings masterSecondaryStrings = new NotificationCustomStrings();

                masterPrimaryStrings[NotificationString.OrganizationName] = organization.Name;
                masterPrimaryStrings[NotificationString.CurrencyCode]     = organization.Currency.DisplayCode;
                masterSecondaryStrings["AmountFloat"] =
                    (new Swarmops.Logic.Financial.Money(satoshisUsed, Currency.BitcoinCore).ToCurrency(
                         organization.Currency).Cents / 100.0).ToString("N2", CultureInfo.InvariantCulture);
                masterSecondaryStrings["BitcoinAmountFloat"] = (satoshisUsed / 100.0).ToString("N2", CultureInfo.InvariantCulture);
                masterSecondaryStrings["PaymentCount"]       = bitcoinPayouts.Count.ToString("N0", CultureInfo.InvariantCulture);

                OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_Hotwallet_Outflow,
                                                masterPrimaryStrings, masterSecondaryStrings);

                // TODO: special case for native-bitcoin organizations vs. fiat-currency organizations

                FinancialTransaction ledgerTransaction = FinancialTransaction.Create(organization, utcNow,
                                                                                     "Bitcoin automated payout");

                if (organization.Currency.IsBitcoinCore)
                {
                    ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeSatoshis), null);
                    ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeSatoshis, null);
                }
                else
                {
                    // If the ledger isn't using bitcoin natively, we need to translate the miner fee paid to ledger cents before entering it into ledger

                    Int64 feeCentsLedger = new Money(feeSatoshis, Currency.BitcoinCore).ToCurrency(organization.Currency).Cents;
                    ledgerTransaction.AddRow(organization.FinancialAccounts.AssetsBitcoinHot, -(masterPayoutPrototype.AmountCents + feeCentsLedger), null).AmountForeignCents = new Money(satoshisUsed, Currency.BitcoinCore);
                    ledgerTransaction.AddRow(organization.FinancialAccounts.CostsBitcoinFees, feeCentsLedger, null);
                }

                ledgerTransaction.BlockchainHash = transactionHash;

                masterPayout.BindToTransactionAndClose(ledgerTransaction, null);
            }
        }
コード例 #6
0
        internal static void Run()
        {
            OutboundComms comms = OutboundComms.GetOpen();

            foreach (OutboundComm comm in comms)
            {
                BotLog.Write(1, "CommsTx", "OutboundComm #" + comm.Identity.ToString("N0"));

                if (!comm.Resolved)
                {
                    BotLog.Write(2, "CommsTx", "--resolving");

                    ICommsResolver resolver = FindResolver(comm);
                    resolver.Resolve(comm);
                    comm.Resolved = true;

                    int recipientCount = comm.Recipients.Count;
                    BotLog.Write(2, "CommsTx", "--resolved to " + recipientCount.ToString("N0") + " recipients");

                    if (recipientCount > 1 && comm.SenderPersonId != 0)
                    {
                        // "Your message has been queued for delivery and the recipients have been resolved.
                        // Your mail will be sent to, or be attempted to sent to, [RecipientCount] people in [Geography] in [OrganizationName]."

                        NotificationStrings       notifyStrings = new NotificationStrings();
                        NotificationCustomStrings customStrings = new NotificationCustomStrings();
                        notifyStrings[NotificationString.OrganizationName] = Organization.FromIdentity(comm.OrganizationId).Name;
                        customStrings["RecipientCount"] = comm.Recipients.Count.ToString("N0");
                        if (resolver is IHasGeography)
                        {
                            customStrings["GeographyName"] = ((IHasGeography)resolver).Geography.Localized;
                        }
                        OutboundComm.CreateNotification(Organization.FromIdentity(comm.OrganizationId),
                                                        NotificationResource.OutboundComm_Resolved, notifyStrings, customStrings,
                                                        People.FromSingle(Person.FromIdentity(comm.SenderPersonId)));
                    }

                    comm.StartTransmission();

                    continue; // continue is not strictly necessary; could continue processing the same OutboundComm after resolution
                }

                if (comm.TransmitterClass != "Swarmops.Utility.Communications.CommsTransmitterMail")
                {
                    throw new NotImplementedException();
                }

                ICommsTransmitter transmitter = new CommsTransmitterMail();

                const int batchSize = 1000;

                OutboundCommRecipients recipients = comm.GetRecipientBatch(batchSize);
                PayloadEnvelope        envelope   = PayloadEnvelope.FromXml(comm.PayloadXml);

                BotLog.Write(2, "CommsTx", "--transmitting to " + recipients.Count.ToString("N0") + " recipients");

                foreach (OutboundCommRecipient recipient in recipients)
                {
                    try
                    {
                        transmitter.Transmit(envelope, recipient.Person);
                        recipient.CloseSuccess();
                    }
                    catch (OutboundCommTransmitException e)
                    {
                        recipient.CloseFailed(e.Description);
                    }
                }

                if (recipients.Count < batchSize) // Was this the last batch?
                {
                    comm.Open = false;

                    BotLog.Write(2, "CommsTx", "--closing");

                    OutboundComm reloadedComm = OutboundComm.FromIdentity(comm.Identity);
                    // active object doesn't update as we get results, so need to load
                    // from database to get final counts of successes and fails

                    if (comm.RecipientCount > 1 && comm.SenderPersonId != 0)
                    {
                        BotLog.Write(2, "CommsTx", "--notifying");

                        ICommsResolver resolver = FindResolver(comm);

                        // "Your message to [GeographyName] has been sent to all scheduled recipients. Of the [RecipientCount] planned recipients,
                        // [RecipientsSuccess] succeeded from Swarmops' horizon. (These can fail later for a number of reasons, from broken
                        // computers to hospitalized recipients.) Time spent transmitting: [TransmissionTime]."

                        NotificationStrings       notifyStrings = new NotificationStrings();
                        NotificationCustomStrings customStrings = new NotificationCustomStrings();
                        notifyStrings[NotificationString.OrganizationName] =
                            Organization.FromIdentity(comm.OrganizationId).Name;
                        customStrings["RecipientCount"]    = reloadedComm.RecipientCount.ToString("N0");
                        customStrings["RecipientsSuccess"] = reloadedComm.RecipientsSuccess.ToString("N0");

                        TimeSpan resolveTime  = comm.StartTransmitDateTime - comm.CreatedDateTime;
                        TimeSpan transmitTime = comm.ClosedDateTime - comm.StartTransmitDateTime;
                        TimeSpan totalTime    = resolveTime + transmitTime;

                        customStrings["TransmissionTime"] = FormatTimespan(transmitTime);
                        customStrings["ResolvingTime"]    = FormatTimespan(resolveTime);
                        customStrings["TotalTime"]        = FormatTimespan(totalTime);
                        if (resolver is IHasGeography)
                        {
                            customStrings["GeographyName"] = ((IHasGeography)resolver).Geography.Localized;
                        }
                        OutboundComm.CreateNotification(Organization.FromIdentity(comm.OrganizationId),
                                                        NotificationResource.OutboundComm_Sent, notifyStrings, customStrings,
                                                        People.FromSingle(Person.FromIdentity(comm.SenderPersonId)));
                    }
                }
            }
        }
コード例 #7
0
        public static void TestMultisigPayout()
        {
            throw new InvalidOperationException("This function is only for testing purposes. It pays real money. Don't use except for dev/test.");

            // disable "code unreachable" warning for this code
            // ReSharper disable once CSharpWarnings::CS0162
            #pragma warning disable 162,219
            Organization organization = Organization.Sandbox; // a few testing cents here in test environment

            string bitcoinTestAddress = "3KS6AuQbZARSvqnaHoHfL1Xhm3bTLFAzoK";

            // Make a small test payment to a multisig address

            TransactionBuilder txBuilder = null; // TODO TODO TODO TODO  new TransactionBuilder();
            Int64 satoshis = new Money(100, Currency.FromCode("SEK")).ToCurrency(Currency.BitcoinCore).Cents;
            BitcoinTransactionInputs inputs      = null;
            Int64 satoshisMaximumAnticipatedFees = BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash) * 20; // assume max 20k transaction size

            try
            {
                inputs = BitcoinUtility.GetInputsForAmount(organization, satoshis + satoshisMaximumAnticipatedFees);
            }
            catch (NotEnoughFundsException)
            {
                Debugger.Break();
            }

            // If we arrive at this point, the previous function didn't throw, and we have enough money. Add the inputs to the transaction.

            txBuilder = txBuilder.AddCoins(inputs.Coins);
            txBuilder = txBuilder.AddKeys(inputs.PrivateKeys);
            Int64 satoshisInput = inputs.AmountSatoshisTotal;

            // Add outputs and prepare notifications

            Int64 satoshisUsed = 0;
            //Dictionary<int, List<string>> notificationSpecLookup = new Dictionary<int, List<string>>();
            //Dictionary<int, List<Int64>> notificationAmountLookup = new Dictionary<int, List<Int64>>();
            Payout            masterPayoutPrototype = Payout.Empty;
            HotBitcoinAddress changeAddress         = HotBitcoinAddress.OrganizationWalletZero(organization, BitcoinChain.Core);

            // Add the test payment

            if (bitcoinTestAddress.StartsWith("1")) // regular address
            {
                txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(bitcoinTestAddress),
                                           new Satoshis(satoshis));
            }
            else if (bitcoinTestAddress.StartsWith("3")) // multisig
            {
                txBuilder = txBuilder.Send(new BitcoinScriptAddress(bitcoinTestAddress, Network.Main),
                                           new Satoshis(satoshis));
            }
            else
            {
                throw new InvalidOperationException("Unhandled address case");
            }
            satoshisUsed += satoshis;

            // Set change address to wallet slush

            txBuilder.SetChange(new BitcoinPubKeyAddress(changeAddress.Address));

            // Add fee

            int transactionSizeBytes = txBuilder.EstimateSize(txBuilder.BuildTransaction(false)) + inputs.Count;
            // +inputs.Count for size variability

            Int64 feeSatoshis = (transactionSizeBytes / 1000 + 1) *
                                BitcoinUtility.GetRecommendedFeePerThousandBytesSatoshis(BitcoinChain.Cash);

            txBuilder     = txBuilder.SendFees(new Satoshis(feeSatoshis));
            satoshisUsed += feeSatoshis;

            // Sign transaction - ready to execute

            Transaction txReady = txBuilder.BuildTransaction(true);

            // Verify that transaction is ready

            if (!txBuilder.Verify(txReady))
            {
                // Transaction was not signed with the correct keys. This is a serious condition.

                NotificationStrings primaryStrings = new NotificationStrings();
                primaryStrings[NotificationString.OrganizationName] = organization.Name;

                OutboundComm.CreateNotification(organization, NotificationResource.Bitcoin_PrivateKeyError,
                                                primaryStrings);

                throw new InvalidOperationException("Transaction is not signed enough");
            }

            // Broadcast transaction

            BitcoinUtility.BroadcastTransaction(txReady, BitcoinChain.Cash);

            // Note the transaction hash

            string transactionHash = txReady.GetHash().ToString();

            // Delete all old inputs, adjust balance for addresses (re-register unused inputs)

            inputs.AsUnspents.DeleteAll();

            // Log the new unspent created by change (if there is any)

            if (satoshisInput - satoshisUsed > 0)
            {
                SwarmDb.GetDatabaseForWriting()
                .CreateHotBitcoinAddressUnspentConditional(changeAddress.Identity, transactionHash,
                                                           +/* the change address seems to always get index 0? is this a safe assumption? */ 0, satoshisInput - satoshisUsed, /* confirmation count*/ 0);
            }

            // Restore "code unreachable", "unsued var" warnings
            #pragma warning restore 162,219

            // This puts the ledger out of sync, so only do this on Sandbox for various small-change (cents) testing
        }
コード例 #8
0
ファイル: Salaries.cs プロジェクト: osoftware/Swarmops
        public static void CreateAnnualStatements(int year)
        {
            // This function is supposed to be running once a year, and summarize the past year of salaries.

            // Get all organizations.

            Organizations organizations = Organizations.GetAll();

            foreach (Organization organization in organizations)
            {
                // Get all salaries for this organization (filter year in logic - this function is allowed to be expensive, rather than
                // adding a custom database query for this particular operation)

                Salaries allSalaries = ForOrganization(organization, true);

                Salaries yearSalaries = new Salaries();
                yearSalaries.AddRange(allSalaries.Where(salary => salary.PayoutDate.Year == year));

                Dictionary <int, Dictionary <int, Salary> > personSalaryLookup = new Dictionary <int, Dictionary <int, Salary> >();

                // Go through the salaries and store them in the dictionary, so we can process the statement by person later

                foreach (Salary salary in yearSalaries)
                {
                    int personId = salary.PayrollItem.PersonId;

                    if (!personSalaryLookup.ContainsKey(personId))
                    {
                        personSalaryLookup[personId] = new Dictionary <int, Salary>();
                    }

                    personSalaryLookup[personId][salary.PayoutDate.Month] = salary;
                }

                // Once here, salaries are arranged by person and month. Iterate over people, create statements.

                foreach (int personId in personSalaryLookup.Keys)
                {
                    Person person      = Person.FromIdentity(personId);
                    Int64  grossTotal  = 0;
                    Int64  subTaxTotal = 0;
                    Int64  addTaxTotal = 0;
                    Int64  netTotal    = 0;

                    string preFormattedStatement = string.Empty;

                    for (int monthNumber = 1; monthNumber <= 12; monthNumber++)
                    {
                        string monthName = new DateTime(year, monthNumber, 1).ToString("MMM",
                                                                                       new CultureInfo(person.PreferredCulture));
                        monthName = monthName.Substring(0, 3);  // believe it or not some .Net localizations don't respect the three letter limit
                        string lineItem = "  " + monthName;

                        if (personSalaryLookup[personId].ContainsKey(monthNumber) && personSalaryLookup[personId][monthNumber].GrossSalaryCents > 0)
                        {
                            Salary monthSalary = personSalaryLookup[personId][monthNumber];

                            // TODO: replace "bitcoin" with actual payout method - this is just for show at the moment

                            lineItem += String.Format("  {0,10:N2}  {1,11:N2}  {2,10:N2}  bitcoin",
                                                      monthSalary.GrossSalaryCents / 100.0, -monthSalary.SubtractiveTaxCents / 100.0,
                                                      monthSalary.NetSalaryCents / 100.0);

                            grossTotal  += monthSalary.GrossSalaryCents;
                            addTaxTotal += monthSalary.AdditiveTaxCents;
                            subTaxTotal += monthSalary.SubtractiveTaxCents;
                            netTotal    += monthSalary.NetSalaryCents;
                        }

                        preFormattedStatement += lineItem + "\r\n";
                    }

                    NotificationStrings       notificationStrings = new NotificationStrings();
                    NotificationCustomStrings customStrings       = new NotificationCustomStrings();

                    notificationStrings[NotificationString.CurrencyCode]             = organization.Currency.DisplayCode;
                    notificationStrings[NotificationString.EmbeddedPreformattedText] = preFormattedStatement;
                    customStrings["LastYear"]                 = year.ToString(CultureInfo.InvariantCulture);
                    customStrings["GrossSalaryTotal"]         = String.Format("{0,10:N2}", grossTotal / 100.0);
                    customStrings["TaxDeductedTotal"]         = String.Format("{0,11:N2}", -subTaxTotal / 100.0);
                    customStrings["NetSalaryTotal"]           = String.Format("{0,10:N2}", netTotal / 100.0);
                    customStrings["TaxAdditiveTotalUnpadded"] = String.Format("{0:N2}", addTaxTotal / 100.0);

                    // Send notification if gross is nonzero

                    if (grossTotal > 0)
                    {
                        OutboundComm.CreateNotification(organization, NotificationResource.Salary_LastYearSummary,
                                                        notificationStrings, customStrings, People.FromSingle(person));
                    }
                }
            }
        }
コード例 #9
0
        static public AjaxInputCallResult StoreCallback(string newValue, string cookie)
        {
            AjaxInputCallResult result   = new AjaxInputCallResult();
            AuthenticationData  authData = GetAuthenticationDataAndCulture();

            if (!authData.Authority.HasAccess(new Access(AccessAspect.Administration, AccessType.Write)))
            {
                result.Success    = false; // this is default on init, but let's be explicit about it
                result.FailReason = AjaxInputCallResult.ErrorAccessDenied;
                return(result);            // fail silently
            }

            switch (cookie)
            {
            case "Smtp":
                Match match = Regex.Match(newValue, "((?<user>[a-z0-9_]+)(:(?<pass>[^@]+))?@)?(?<host>[a-z0-9_\\-\\.]+)(:(?<port>[0-9]+))?", RegexOptions.IgnoreCase);
                if (match.Success)
                {
                    string user       = match.Groups["user"].Value;
                    string pass       = match.Groups["pass"].Value;
                    string host       = match.Groups["host"].Value;
                    string portString = match.Groups["port"].Value;
                    int    port       = 25;

                    if (!string.IsNullOrEmpty(portString))
                    {
                        try
                        {
                            port = Int32.Parse(portString);
                        }
                        catch (FormatException)
                        {
                            result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpHostPort;
                            result.FailReason     = AjaxInputCallResult.ErrorInvalidFormat;
                            result.Success        = false;
                            return(result);    // return early
                        }
                    }

                    SystemSettings.SmtpUser     = user ?? string.Empty;
                    SystemSettings.SmtpPassword = pass ?? string.Empty;
                    SystemSettings.SmtpHost     = host;
                    SystemSettings.SmtpPort     = port;

                    OutboundComm.CreateNotification(Organization.Sandbox, Logic.Communications.Payload.NotificationResource.System_MailServerTest);

                    result.Success        = true;
                    result.NewValue       = FormatSmtpAccessString(user, pass, host, port);
                    result.DisplayMessage = Resources.Pages.Admin.SystemSettings_TestMailSent;
                }
                else
                {
                    result.Success        = false;
                    result.FailReason     = AjaxInputCallResult.ErrorInvalidFormat;
                    result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpSyntax;
                }
                break;

            case "ExtUrl":
                if (!newValue.EndsWith("/"))
                {
                    newValue = newValue + "/";
                }
                if (!newValue.StartsWith("http://") && !newValue.StartsWith("https://"))
                {
                    newValue = "https://" + newValue;
                }

                SystemSettings.ExternalUrl = newValue;

                result.NewValue = newValue;
                result.Success  = true;
                break;

            case "InstallationName":
                result.NewValue = newValue.Trim();
                result.Success  = true;
                SystemSettings.InstallationName = result.NewValue;
                break;

            case "AdminSender":
                result.NewValue = newValue.Trim();
                result.Success  = true;
                SystemSettings.InstallationName = result.NewValue;
                break;

            case "AdminAddress":
                result.NewValue = newValue.Trim();
                result.Success  = true;
                SystemSettings.AdminNotificationAddress = result.NewValue;
                break;

            case "BackendHost":
                result.NewValue = newValue.Trim();
                result.Success  = true;
                SystemSettings.BackendHostnameOverride = result.NewValue;
                break;

            case "WebsocketFrontend":
                try
                {
                    int newPort = Int32.Parse(newValue);
                    if (newPort < 1 || newPort > 32767)
                    {
                        throw new ArgumentException();
                    }
                    result.NewValue = newValue.Trim();
                    result.Success  = true;
                    SystemSettings.WebsocketPortFrontend = newPort;
                }
                catch (Exception)
                {
                    result.Success    = false;
                    result.FailReason = AjaxInputCallResult.ErrorInvalidFormat;
                    result.NewValue   = SystemSettings.WebsocketPortFrontend.ToString(CultureInfo.InvariantCulture);
                }
                break;

            case "WebsocketBackend":
                try
                {
                    int newPort = Int32.Parse(newValue);
                    if (newPort < 1 || newPort > 32767)
                    {
                        throw new ArgumentException();
                    }

                    result.NewValue = newValue.Trim();
                    result.Success  = true;
                    SystemSettings.WebsocketPortBackend = newPort;
                }
                catch (Exception)
                {
                    result.Success    = false;
                    result.FailReason = AjaxInputCallResult.ErrorInvalidFormat;
                    result.NewValue   = SystemSettings.WebsocketPortBackend.ToString(CultureInfo.InvariantCulture);
                }
                break;

            default:
                throw new NotImplementedException("Unknown cookie in StoreCallback");
            }

            return(result);
        }
コード例 #10
0
ファイル: Program.cs プロジェクト: aciofini/Swarmops
        private static void Main(string[] args)
        {
            testMode = false;

            UnixSignal[] killSignals =
            {
                new UnixSignal(Signum.SIGINT),
                new UnixSignal(Signum.SIGTERM)
            };

            BotLog.Write(0, "MainCycle", string.Empty);
            BotLog.Write(0, "MainCycle", "-----------------------------------------------");
            BotLog.Write(0, "MainCycle", string.Empty);

            if (args.Length > 0)
            {
                if (args[0].ToLower() == "test")
                {
/*
 *                  BotLog.Write(0, "MainCycle", "Running self-tests");
 *                  HeartBeater.Instance.Beat(heartbeatFile);  // Otherwise Heartbeater.Beat() will fail in various places
 *
 *                  testMode = true;
 *                  Console.WriteLine("Testing All Maintenance Processes (except membership-changing ones).");
 *                  PWLog.Write(PWLogItem.None, 0, PWLogAction.SystemTest, string.Empty, string.Empty);
 *
 *                  Console.WriteLine("\r\n10-second intervals:");
 *                  OnEveryTenSeconds();
 *                  Console.WriteLine("\r\nEvery minute:");
 *                  OnEveryMinute();
 *                  Console.WriteLine("\r\nEvery five minutes:");
 *                  OnEveryFiveMinutes();
 *                  Console.WriteLine("\r\nEvery hour:");
 *                  OnEveryHour();
 *                  Console.WriteLine("\r\nNoon:");
 *                  OnNoon();
 *                  Console.WriteLine("\r\nMidnight:");
 *                  OnMidnight();
 */

                    Console.WriteLine("Testing database access...");

                    Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).Name);
                    Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).PasswordHash);

                    Console.WriteLine("Creating OutboundComm...");

                    OutboundComm.CreateNotification(null, NotificationResource.System_Startup);

                    Console.WriteLine("Transmitting...");

                    OutboundComms comms = OutboundComms.GetOpen();

                    Console.WriteLine("{0} open items in outbound comms.", comms.Count);

                    foreach (OutboundComm comm in comms)
                    {
                        if (comm.TransmitterClass != "Swarmops.Utility.Communications.CommsTransmitterMail")
                        {
                            throw new NotImplementedException();
                        }

                        ICommsTransmitter transmitter = new CommsTransmitterMail();

                        OutboundCommRecipients recipients = comm.Recipients;
                        PayloadEnvelope        envelope   = PayloadEnvelope.FromXml(comm.PayloadXml);

                        foreach (OutboundCommRecipient recipient in recipients)
                        {
                            transmitter.Transmit(envelope, recipient.Person);
                        }
                    }


                    Console.Write("\r\nAll tests run. Waiting for mail queue to flush... ");
                    while (!MailTransmitter.CanExit)
                    {
                        Thread.Sleep(50);
                    }

                    Console.WriteLine("done.");
                    BotLog.Write(0, "MainCycle", "Exiting self-tests");
                    return;
                }

                if (args[0].ToLower() == "console")
                {
                    Console.WriteLine("\r\nRunning Swarmops-Backend in CONSOLE mode.\r\n");

                    // -------------------------------------------------------------------------------------
                    // -------------------------------------------------------------------------------------
                    // -------------------------------------------------------------------------------------

                    // -----------------------    INSERT ANY ONE-OFF ACTIONS HERE  -------------------------


                    Console.Write("\r\nWaiting for mail queue to flush... ");

                    while (!MailTransmitter.CanExit)
                    {
                        Thread.Sleep(50);
                    }

                    Console.WriteLine("done.");

                    return;
                }

                if (args[0].ToLower() == "rsm")
                {
                    Console.WriteLine("Testing character encoding: räksmörgås RÄKSMÖRGÅS");
                    return;
                }
            }

            /*
             * MailMessage message = new MailMessage();
             * message.From = new MailAddress(Strings.MailSenderAddress, Strings.MailSenderName);
             * message.To.Add (new MailAddress ("*****@*****.**", "Rick Falkvinge (Piratpartiet)"));
             * message.Subject = "Räksmörgåsarnas ékÖNÖMÏåvdëlnïng";
             * message.Body = "Hejsan hoppsan Räksmörgåsar.";
             * message.BodyEncoding = Encoding.Default;
             * message.SubjectEncoding = Encoding.Default;
             *
             * SmtpClient smtpClient = new SmtpClient ("localhost");
             * smtpClient.Credentials = null; // mono bug
             * smtpClient.Send (message);*/

            Console.WriteLine(" * Swarmops Backend starting");

            BotLog.Write(0, "MainCycle", "Backend STARTING");

            OutboundComm.CreateNotification(null, NotificationResource.System_Startup);

            // Checking for schemata upgrade

            DatabaseMaintenance.UpgradeSchemata();

            // Check for existence of installation ID. If not, create one. Warning: has privacy implications when communicated.

            if (Persistence.Key["SwarmopsInstallationId"] == string.Empty)
            {
                Persistence.Key["SwarmopsInstallationId"] = Guid.NewGuid().ToString();
            }

            DateTime cycleStartTime = DateTime.Now;

            int lastSecond = cycleStartTime.Second;
            int lastMinute = cycleStartTime.Minute;
            int lastHour   = cycleStartTime.Hour;

            bool exitFlag = false;

            while (!exitFlag) // exit is handled by signals handling at end of loop
            {
                BotLog.Write(0, "MainCycle", "Cycle Start");

                cycleStartTime = DateTime.Now;

                try
                {
                    OnEveryTenSeconds();

                    if (cycleStartTime.Second < lastSecond)
                    {
                        OnEveryMinute();

                        if (cycleStartTime.Minute % 5 == 0)
                        {
                            OnEveryFiveMinutes();
                        }
                    }

                    if (cycleStartTime.Minute < lastMinute)
                    {
                        OnEveryHour();

                        if (DateTime.Now.Hour == 10 && DateTime.Today.DayOfWeek == DayOfWeek.Tuesday)
                        {
                            OnTuesdayMorning();
                        }
                    }

                    if (cycleStartTime.Hour >= 12 && lastHour < 12)
                    {
                        OnNoon();
                    }

                    if (cycleStartTime.Hour < lastHour)
                    {
                        OnMidnight();
                    }
                }

                catch (Exception e)
                {
                    // Note each "OnEvery..." catches its own errors and sends Exception mails,
                    // so that failure in one should not stop the others from running. This particular
                    // code should never run.

                    ExceptionMail.Send(new Exception("Failed in swarmops-backend main loop", e), true);
                }

                lastSecond = cycleStartTime.Second;
                lastMinute = cycleStartTime.Minute;
                lastHour   = cycleStartTime.Hour;

                // Wait for a maximum of ten seconds

                while (DateTime.Now < cycleStartTime.AddSeconds(10) && !exitFlag)
                {
                    // block until a SIGINT or SIGTERM signal is generated, or one second has passed.
                    int signalIndex = UnixSignal.WaitAny(killSignals, 1000);

                    if (signalIndex < 1000)
                    {
                        exitFlag = true;
                        Console.WriteLine("Caught signal " + killSignals[signalIndex].Signum + ", exiting");
                        BotLog.Write(0, "MainCycle",
                                     "EXIT SIGNAL (" + killSignals[signalIndex].Signum + "), terminating backend");
                    }
                }
            }

            Console.WriteLine(" * Swarmops Backend stopping");
            BotLog.Write(0, "MainCycle", "BACKEND EXITING, sending backend-termination notices");

            /*
             * if (HeartBeater.Instance.WasKilled)
             * {
             *  // removed unconditional delete, cron job that restarts bot uses it to know that it is intentionally down.
             *  ExceptionMail.Send(new Exception("HeartBeater triggered restart of Swarmops Backend. Will commence after 800 seconds."), false);
             * }*/

            BotLog.Write(0, "MainCycle", "...done");

            /*
             * while (!MailTransmitter.CanExit)
             * {
             *  System.Threading.Thread.Sleep(50);
             * }*/

            Thread.Sleep(2000);
        }
コード例 #11
0
        private static void Main(string[] args)
        {
            // Are we running yet?

            if (!SystemSettings.DatabaseInitialized)
            {
                // will restart the service every 15s until db initialized on OOBE
                // also, the read of DatabaseInitialized can and will fail if
                // we're not initalized enough to even have a database

                throw new InvalidOperationException();
            }

            // Checking for schemata upgrade first of all, after seeing that db exists

            int startupDbVersion = Database.SwarmDb.DbVersion;

            DatabaseMaintenance.UpgradeSchemata();

            testMode = false;

            SystemSettings.BackendHostname = Dns.GetHostName();


            // Other one-time initializations

            FinancialTransactions.FixAllUnsequenced();
            SupportFunctions.OperatingTopology = OperatingTopology.Backend;

            // Begin main loop

            UnixSignal[] killSignals = null;

            if (!Debugger.IsAttached)
            {
                killSignals = new UnixSignal[] { new UnixSignal(Signum.SIGINT), new UnixSignal(Signum.SIGTERM) };
            }

            BotLog.Write(0, "MainCycle", string.Empty);
            BotLog.Write(0, "MainCycle", "-----------------------------------------------");
            BotLog.Write(0, "MainCycle", string.Empty);

            if (args.Length > 0)
            {
                if (args[0].ToLower() == "test")
                {
/*
 *                  BotLog.Write(0, "MainCycle", "Running self-tests");
 *                  HeartBeater.Instance.Beat(heartbeatFile);  // Otherwise Heartbeater.Beat() will fail in various places
 *
 *                  testMode = true;
 *                  Console.WriteLine("Testing All Maintenance Processes (except membership-changing ones).");
 *                  PWLog.Write(PWLogItem.None, 0, PWLogAction.SystemTest, string.Empty, string.Empty);
 *
 *                  Console.WriteLine("\r\n10-second intervals:");
 *                  OnEveryTenSeconds();
 *                  Console.WriteLine("\r\nEvery minute:");
 *                  OnEveryMinute();
 *                  Console.WriteLine("\r\nEvery five minutes:");
 *                  OnEveryFiveMinutes();
 *                  Console.WriteLine("\r\nEvery hour:");
 *                  OnEveryHour();
 *                  Console.WriteLine("\r\nNoon:");
 *                  OnNoon();
 *                  Console.WriteLine("\r\nMidnight:");
 *                  OnMidnight();
 */

                    Console.WriteLine("Testing database access...");

                    Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).Name);
                    Console.WriteLine(SwarmDb.GetDatabaseForReading().GetPerson(1).PasswordHash);

                    Console.WriteLine("Creating OutboundComm...");

                    OutboundComm.CreateNotification(null, NotificationResource.System_Startup_Backend);

                    Console.WriteLine("Transmitting...");

                    OutboundComms comms = OutboundComms.GetOpen();

                    Console.WriteLine("{0} open items in outbound comms.", comms.Count);

                    foreach (OutboundComm comm in comms)
                    {
                        if (comm.TransmitterClass != "Swarmops.Utility.Communications.CommsTransmitterMail")
                        {
                            throw new NotImplementedException();
                        }

                        ICommsTransmitter transmitter = new CommsTransmitterMail();

                        OutboundCommRecipients recipients = comm.Recipients;
                        PayloadEnvelope        envelope   = PayloadEnvelope.FromXml(comm.PayloadXml);

                        foreach (OutboundCommRecipient recipient in recipients)
                        {
                            transmitter.Transmit(envelope, recipient.Person);
                        }
                    }


                    Console.Write("\r\nAll tests run. Waiting for mail queue to flush... ");
                    while (!MailTransmitter.CanExit)
                    {
                        Thread.Sleep(50);
                    }

                    Console.WriteLine("done.");
                    BotLog.Write(0, "MainCycle", "Exiting self-tests");
                    return;
                }

                if (args[0].ToLower() == "console")
                {
                    Console.WriteLine("\r\nRunning Swarmops-Backend in CONSOLE mode.\r\n");

                    // -------------------------------------------------------------------------------------
                    // -------------------------------------------------------------------------------------
                    // -------------------------------------------------------------------------------------

                    // -----------------------    INSERT ANY ONE-OFF ACTIONS HERE  -------------------------


                    Console.Write("\r\nWaiting for mail queue to flush... ");

                    while (!MailTransmitter.CanExit)
                    {
                        Thread.Sleep(50);
                    }

                    Console.WriteLine("done.");

                    return;
                }

                if (args[0].ToLowerInvariant() == "pdfregen")
                {
                    if (args.Length > 1)
                    {
                        int docId = Int32.Parse(args[1]);
                        PdfProcessor.Rerasterize(Document.FromIdentity(docId));
                    }
                    else
                    {
                        Console.WriteLine("Regenerating all bitmaps from PDF uploads.");
                        PdfProcessor.RerasterizeAll();
                        Console.WriteLine("Done.");
                    }

                    return;
                }


                if (args[0].ToLower() == "rsm")
                {
                    Console.WriteLine("Testing character encoding: räksmörgås RÄKSMÖRGÅS");
                    return;
                }
            }

            /*
             * MailMessage message = new MailMessage();
             * message.From = new MailAddress(Strings.MailSenderAddress, Strings.MailSenderName);
             * message.To.Add (new MailAddress ("*****@*****.**", "Rick Falkvinge (Piratpartiet)"));
             * message.Subject = "Räksmörgåsarnas ékÖNÖMÏåvdëlnïng";
             * message.Body = "Hejsan hoppsan Räksmörgåsar.";
             * message.BodyEncoding = Encoding.Default;
             * message.SubjectEncoding = Encoding.Default;
             *
             * SmtpClient smtpClient = new SmtpClient ("localhost");
             * smtpClient.Credentials = null; // mono bug
             * smtpClient.Send (message);*/

            Console.WriteLine(" * Swarmops Backend starting");

            BotLog.Write(0, "MainCycle", "Backend STARTING");

            // Disable certificate checking due to Mono not installing with a certificate repository - this is UTTERLY broken

            SupportFunctions.DisableSslCertificateChecks(); // MONO BUG/MISFEATURE: Mono has no root certificates, so can't verify cert

            // Tell sysop we're starting

            OutboundComm.CreateNotification(null, NotificationResource.System_Startup_Backend);

            // Check for existence of installation ID. If not, create one. Warning: has privacy implications when communicated.

            if (Persistence.Key["SwarmopsInstallationId"] == string.Empty)
            {
                Persistence.Key["SwarmopsInstallationId"] = Guid.NewGuid().ToString();
            }

            // Check for existence of bitcoin hotwallet root

            BitcoinUtility.VerifyBitcoinHotWallet();

            // Initialize backend socket server

            int backendSocketPort = SystemSettings.WebsocketPortBackend;

            _socketServer = new WebSocketServer(backendSocketPort);
            _socketServer.AddWebSocketService <BackendServices>("/Backend");
            _socketServer.Start();

            // Initialize socket client to Blockchain.Info (pending our own services)

            using (
                _blockChainInfoSocket =
                    new WebSocket("wss://ws.blockchain.info/inv?api_code=" + SystemSettings.BlockchainSwarmopsApiKey))
            {
                // Begin maintenance loop

                DateTime cycleStartTime = DateTime.UtcNow;
                DateTime cycleEndTime;

                int lastSecond = cycleStartTime.Second;
                int lastMinute = cycleStartTime.Minute;
                int lastHour   = cycleStartTime.Hour;

                bool exitFlag = false;

                _blockChainInfoSocket.OnOpen    += new EventHandler(OnBlockchainOpen);
                _blockChainInfoSocket.OnError   += new EventHandler <ErrorEventArgs>(OnBlockchainError);
                _blockChainInfoSocket.OnClose   += new EventHandler <CloseEventArgs>(OnBlockchainClose);
                _blockChainInfoSocket.OnMessage += new EventHandler <MessageEventArgs>(OnBlockchainMessage);

                _blockChainInfoSocket.Connect();

                while (!exitFlag) // exit is handled by signals handling at end of loop
                {
                    BotLog.Write(0, "MainCycle", "Cycle Start");

                    cycleStartTime = DateTime.UtcNow;
                    cycleEndTime   = cycleStartTime.AddSeconds(10);

                    try
                    {
                        OnEveryTenSeconds();

                        if (cycleStartTime.Second < lastSecond)
                        {
                            OnEveryMinute();

                            if (cycleStartTime.Minute % 5 == 0)
                            {
                                OnEveryFiveMinutes();
                            }
                        }

                        if (cycleStartTime.Minute < lastMinute)
                        {
                            OnEveryHour();

                            if (DateTime.Now.Hour == 10 && DateTime.Today.DayOfWeek == DayOfWeek.Tuesday)
                            {
                                OnTuesdayMorning();
                            }

                            if (DateTime.Now.Hour == 7 && DateTime.Today.DayOfWeek == DayOfWeek.Monday)
                            {
                                OnMondayMorning();
                            }
                        }

                        if (cycleStartTime.Hour >= 12 && lastHour < 12)
                        {
                            OnNoon();
                        }

                        if (cycleStartTime.Hour < lastHour)
                        {
                            OnMidnight();
                        }
                    }

                    catch (Exception e)
                    {
                        // Note each "OnEvery..." catches its own errors and sends Exception mails,
                        // so that failure in one should not stop the others from running. This particular
                        // code should never run.

                        ExceptionMail.Send(new Exception("Failed in swarmops-backend main loop", e), true);
                    }

                    lastSecond = cycleStartTime.Second;
                    lastMinute = cycleStartTime.Minute;
                    lastHour   = cycleStartTime.Hour;

                    // Wait for a maximum of ten seconds (the difference between cycleStartTime and cycleEndTime)

                    int      iterationCount = 0;
                    DateTime utcNow         = DateTime.UtcNow;
                    while (utcNow < cycleEndTime && !exitFlag)
                    {
                        int signalIndex = 250;

                        // Handle important service orders (those that can't be lost in a random loss
                        // of connection of a socket):

                        BackendServiceOrders backendOrders = BackendServiceOrders.GetNextBatch(5);
                        backendOrders.Execute(); // takes at most 250ms per BSO reqs

                        // Block until a SIGINT or SIGTERM signal is generated, or 1/4 second has passed.
                        // However, we can't do that in a development environment - it won't have the
                        // Mono.Posix assembly, and won't understand UnixSignals. So people running this in
                        // a dev environment will need to stop it manually.

                        if (!Debugger.IsAttached)
                        {
                            signalIndex = UnixSignal.WaitAny(killSignals, 250);
                        }
                        else
                        {
                            TimeSpan timeLeft = (cycleEndTime - utcNow);

                            BotLog.Write(0, "MainCycle Debug",
                                         string.Format(CultureInfo.InvariantCulture,
                                                       "Waiting for {0:F2} more seconds for cycle end",
                                                       timeLeft.TotalMilliseconds / 1000.0));
                            Thread.Sleep(250);
                        }

                        if (signalIndex < 250)
                        {
                            exitFlag = true;
                            Console.WriteLine("Caught signal " + killSignals[signalIndex].Signum + ", exiting");
                            BotLog.Write(0, "MainCycle",
                                         "EXIT SIGNAL (" + killSignals[signalIndex].Signum + "), terminating backend");
                        }

                        utcNow = DateTime.UtcNow;

                        // Every second, send an internal heartbeat

                        if (iterationCount++ % 4 == 0)
                        {
                            InternalHeartbeat();
                        }
                    }
                }
            }

            Console.WriteLine(" * Swarmops Backend stopping");
            BotLog.Write(0, "MainCycle", "BACKEND EXITING, sending backend-termination notices");

            /*
             * if (HeartBeater.Instance.WasKilled)
             * {
             *  // removed unconditional delete, cron job that restarts bot uses it to know that it is intentionally down.
             *  ExceptionMail.Send(new Exception("HeartBeater triggered restart of Swarmops Backend. Will commence after 800 seconds."), false);
             * }*/

            BotLog.Write(0, "MainCycle", "...done");

            /*
             * while (!MailTransmitter.CanExit)
             * {
             *  System.Threading.Thread.Sleep(50);
             * }*/

            _socketServer.Stop();

            Thread.Sleep(2000);
        }
コード例 #12
0
        static public AjaxTextBox.CallbackResult StoreCallback(string newValue, string cookie)
        {
            AjaxTextBox.CallbackResult result = new AjaxTextBox.CallbackResult();

            switch (cookie)
            {
            case "Smtp":
                Match match = Regex.Match(newValue, "((?<user>[a-z0-9_]+)(:(?<pass>[^@]+))?@)?(?<host>[a-z0-9_\\-\\.]+)(:(?<port>[0-9]+))?", RegexOptions.IgnoreCase);
                if (match.Success)
                {
                    string user       = match.Groups["user"].Value;
                    string pass       = match.Groups["pass"].Value;
                    string host       = match.Groups["host"].Value;
                    string portString = match.Groups["port"].Value;
                    int    port       = 25;

                    if (!string.IsNullOrEmpty(portString))
                    {
                        try
                        {
                            port = Int32.Parse(portString);
                        }
                        catch (FormatException)
                        {
                            result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpHostPort;
                            result.ResultCode     = AjaxTextBox.CodeInvalid;
                            return(result);    // return early
                        }
                    }

                    SystemSettings.SmtpUser     = user ?? string.Empty;
                    SystemSettings.SmtpPassword = pass ?? string.Empty;
                    SystemSettings.SmtpHost     = host;
                    SystemSettings.SmtpPort     = port;

                    OutboundComm.CreateNotification(Organization.Sandbox, Logic.Communications.Transmission.NotificationResource.System_MailServerTest);

                    result.ResultCode     = AjaxTextBox.CodeChanged;
                    result.NewData        = FormatSmtpAccessString(user, pass, host, port);
                    result.DisplayMessage = Resources.Pages.Admin.SystemSettings_TestMailSent;
                }
                else
                {
                    result.ResultCode     = AjaxTextBox.CodeInvalid;
                    result.DisplayMessage = Resources.Pages.Admin.SystemSettings_Error_SmtpSyntax;
                }
                break;

            case "ExtUrl":
                if (!newValue.EndsWith("/"))
                {
                    newValue = newValue + "/";
                }
                if (!newValue.StartsWith("http://") && !newValue.StartsWith("https://"))
                {
                    newValue = "https://" + newValue;
                }

                SystemSettings.ExternalUrl = newValue;

                result.NewData    = newValue;
                result.ResultCode = AjaxTextBox.CodeSuccess;
                break;

            case "InstallationName":
                result.NewData    = newValue.Trim();
                result.ResultCode = AjaxTextBox.CodeSuccess;
                SystemSettings.InstallationName = result.NewData;
                break;

            case "AdminSender":
                result.NewData    = newValue.Trim();
                result.ResultCode = AjaxTextBox.CodeSuccess;
                SystemSettings.InstallationName = result.NewData;
                break;

            case "AdminAddress":
                result.NewData    = newValue.Trim();
                result.ResultCode = AjaxTextBox.CodeSuccess;
                SystemSettings.AdminNotificationAddress = result.NewData;
                break;

            default:
                throw new NotImplementedException("Unknown cookie in StoreCallback");
            }

            return(result);
        }
コード例 #13
0
        public void Transmit(PayloadEnvelope envelope, Person person)
        {
            // Create the renderer via reflection of the static FromXml method

            Assembly assembly = typeof(PayloadEnvelope).Assembly;

            Type payloadType = assembly.GetType(envelope.PayloadClass);

            if (payloadType == null)
            {
                NotificationCustomStrings customStrings = new NotificationCustomStrings();
                customStrings["UnrecognizedPayloadType"] = envelope.PayloadClass;
                OutboundComm.CreateNotification(null, NotificationResource.System_UnrecognizedPayload, customStrings);

                throw new OutboundCommTransmitException("Unrecognized or uninstantiable payload type: " + envelope.PayloadClass);
            }

            var methodInfo = payloadType.GetMethod("FromXml", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);

            ICommsRenderer renderer = (ICommsRenderer)(methodInfo.Invoke(null, new object[] { envelope.PayloadXml }));
            RenderedComm   comm     = renderer.RenderComm(person);

            MailMessage mail = new MailMessage();

            // This is a rather simple mail (no images or stuff like that)

            mail.Subject         = (string)comm[CommRenderPart.Subject];
            mail.SubjectEncoding = Encoding.UTF8;

            try
            {
                mail.From = new MailAddress((string)comm[CommRenderPart.SenderMail], (string)comm[CommRenderPart.SenderName],
                                            Encoding.UTF8);

                // SPECIAL CASE for sandbox mails -- ugly code but wtf
                if (person.Identity == 1 && PilotInstallationIds.DevelopmentSandbox == SystemSettings.InstallationId && mail.Subject.Contains("|"))
                {
                    string[] separated = mail.Subject.Split('|');
                    mail.Subject = separated[0];
                    mail.To.Add(new MailAddress(separated[1], "Swarmops Sandbox Administrator"));
                }
                else // regular case to be used... like everywhere else except for the sandbox test
                {
                    if (string.IsNullOrWhiteSpace(person.Mail))
                    {
                        throw new OutboundCommTransmitException("No valid mail address for " + person.Canonical);
                    }

                    mail.To.Add(new MailAddress(person.Mail, person.Name));
                }
            }
            catch (ArgumentException e)
            {
                // Address failure -- either sender or recipient

                _cacheReloadTime = Constants.DateTimeLow;
                throw new OutboundCommTransmitException("Cannot send mail to " + person.Mail, e);
            }

            string mailBodyText = (string)comm[CommRenderPart.BodyText];

            mailBodyText = mailBodyText.Replace("[Addressee]", person.Canonical);

            string mailBodyHtml = comm.ContainsKey(CommRenderPart.BodyHtml)? (string)comm[CommRenderPart.BodyHtml]: string.Empty;

            mailBodyHtml = mailBodyHtml.Replace("[Addressee]", person.Canonical);

            mail.Body         = mailBodyText;
            mail.BodyEncoding = Encoding.UTF8;

            string smtpServer   = _smtpServerCache;
            int    smtpPort     = _smtpPortCache;
            string smtpUser     = _smtpUserCache;
            string smtpPassword = _smtpPasswordCache;

            DateTime now = DateTime.Now;

            if (now > _cacheReloadTime)
            {
                smtpServer   = _smtpServerCache = SystemSettings.SmtpHost;
                smtpPort     = _smtpPortCache = SystemSettings.SmtpPort;
                smtpUser     = _smtpUserCache = SystemSettings.SmtpUser;
                smtpPassword = _smtpPasswordCache = SystemSettings.SmtpPassword;

                _cacheReloadTime = now.AddMinutes(5);
            }

            if (string.IsNullOrEmpty(smtpServer))
            {
                smtpServer = "localhost";
                smtpPort   = 25;
                // For development use only - invalidate cache instead of this, forcing re-reload
                _cacheReloadTime = Constants.DateTimeLow;
            }

            SmtpClient mailClient = new SmtpClient(smtpServer, smtpPort);

            if (!string.IsNullOrEmpty(smtpUser))
            {
                mailClient.Credentials = new System.Net.NetworkCredential(smtpUser, smtpPassword);
            }

            try
            {
                mailClient.Send(mail);
            }
            catch (Exception e)
            {
                _cacheReloadTime = Constants.DateTimeLow;
                throw new OutboundCommTransmitException("Cannot send mail to " + person.Mail, e);
            }
        }
コード例 #14
0
ファイル: FrontendLoop.cs プロジェクト: mikran/Swarmops
        private static void Main(string[] args)
        {
            // Are we running yet?

            if (!SystemSettings.DatabaseInitialized)
            {
                // will restart the service every 15s until db initialized on OOBE
                // also, the read of DatabaseInitialized can and will fail if
                // we're not initalized enough to even have a database

                throw new InvalidOperationException();
            }

            // Check if we're Sandbox

            if (PilotInstallationIds.IsPilot(PilotInstallationIds.DevelopmentSandbox))
            {
                _isSandbox = true;
            }

            // Disable SSL cert checking (because Mono doesn't have a cert repo, dammit)
            Swarmops.Logic.Support.SupportFunctions.DisableSslCertificateChecks();

            // Other one-off initializations
            SupportFunctions.OperatingTopology = OperatingTopology.FrontendSocket;

            // Initiate main loop

            UnixSignal[] killSignals = null;
            _activeAlarms = new Dictionary <FrontendMalfunctions, bool>();

            if (!Debugger.IsAttached)
            {
                killSignals = new UnixSignal[] { new UnixSignal(Signum.SIGINT), new UnixSignal(Signum.SIGTERM) };
            }

            Console.WriteLine(" * Swarmops Frontend Socket Server starting up.");

            OutboundComm.CreateNotification(null, NotificationResource.System_Startup_Frontend);

            // Initialize socket server and client

            _socketServer = new WebSocketServer(SystemSettings.WebsocketPortFrontend);
            _socketServer.AddWebSocketService <FrontendServices>("/Front");
            // _socketServer.KeepClean = false; // as per the author's recommendation - this may be bad in the long run
            _socketServer.Start();

            DateTime cycleStartTime = DateTime.UtcNow;
            DateTime cycleEndTime;

            int lastSecond = cycleStartTime.Second;
            int lastMinute = cycleStartTime.Minute;
            int lastHour   = cycleStartTime.Hour;

            string backendHostname = SystemSettings.BackendHostnameOverride;

            if (String.IsNullOrEmpty(backendHostname))
            {
                backendHostname = SystemSettings.BackendHostname;
            }

            string backendSocketUri = "ws://" + backendHostname + ":" +
                                      SystemSettings.WebsocketPortBackend.ToString(CultureInfo.InvariantCulture) +
                                      "/Backend?Auth=" + Authority.GetSystemAuthorityToken("Frontend");

            bool exitFlag = false;

            Console.WriteLine("Connecting backend socket " + backendSocketUri);

            using (_backendSocket = new WebSocket(backendSocketUri))
            {
                _backendSocket.OnMessage += new EventHandler <MessageEventArgs>(OnBackendMessage);
                _backendSocket.OnOpen    += new EventHandler(OnBackendOpen);
                _backendSocket.OnClose   += new EventHandler <CloseEventArgs>(OnBackendClose);
                _backendSocket.OnError   += new EventHandler <ErrorEventArgs>(OnBackendError);
                _backendSocket.Connect();

                while (!exitFlag) // exit is handled by signals handling at end of loop
                {
                    cycleStartTime = DateTime.UtcNow;
                    cycleEndTime   = cycleStartTime.AddSeconds(10);

                    try
                    {
                        OnEveryTenSeconds();

                        if (cycleStartTime.Second < lastSecond)
                        {
                            OnEveryMinute();

                            if (cycleStartTime.Minute % 5 == 0)
                            {
                                OnEveryFiveMinutes();
                            }

                            if (cycleStartTime.Minute % 30 == 0)
                            {
                                OnEveryHalfHour();
                            }
                        }

                        if (cycleStartTime.Minute < lastMinute)
                        {
                            OnEveryHour();

                            if (DateTime.Now.Hour == 10 && DateTime.Today.DayOfWeek == DayOfWeek.Tuesday)
                            {
                                // OnTuesdayMorning();
                            }
                        }

                        if (cycleStartTime.Hour >= 12 && lastHour < 12)
                        {
                            // OnNoon();
                        }

                        if (cycleStartTime.Hour < lastHour)
                        {
                            // OnMidnight();
                        }
                    }

                    catch (Exception e)
                    {
                        Console.WriteLine(e.ToString());

                        // Note each "OnEvery..." catches its own errors and sends Exception mails,
                        // so that failure in one should not stop the others from running. This particular
                        // code should never run.

                        // ExceptionMail.Send (new Exception ("Failed in swarmops-backend main loop", e), true);
                    }

                    lastSecond = cycleStartTime.Second;
                    lastMinute = cycleStartTime.Minute;
                    lastHour   = cycleStartTime.Hour;

                    // Wait for a maximum of ten seconds (the difference between cycleStartTime and cycleEndTime)

                    DateTime utcNow = DateTime.UtcNow;
                    while (utcNow < cycleEndTime && !exitFlag)
                    {
                        int signalIndex = 250;

                        // Block until a SIGINT or SIGTERM signal is generated, or 1/4 second has passed.
                        // However, we can't do that in a development environment - it won't have the
                        // Mono.Posix assembly, and won't understand UnixSignals. So people running this in
                        // a dev environment will need to stop it manually.

                        if (!Debugger.IsAttached)
                        {
                            signalIndex = UnixSignal.WaitAny(killSignals, 250);
                        }
                        else
                        {
                            Thread.Sleep(250);
                        }

                        if (signalIndex < 250)
                        {
                            exitFlag = true;
                            Console.WriteLine(" * Swarmops Frontend Socket Server caught signal " +
                                              killSignals[signalIndex].Signum + ", exiting");
                        }

                        utcNow = DateTime.UtcNow;
                    }
                }

                _socketServer.Stop();
                Thread.Sleep(2000);

                Console.WriteLine(" * Swarmops Frontend Socket Server exiting");
            }
        }