[Solved] bank, 4 atm machine input txt files, syncing information between them with semaphores


I know it’s tagged c, but since you spammed it in Lounge<C++>, let me humor you with C++:

Live On Coliru

#include <algorithm>
#include <functional>
#include <atomic>
#include <fstream>
#include <iostream>
#include <list>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <vector>

// not thread-aware:
struct Bank {
    struct Account {
        int id;
        int password;
        int balance;

        Account(int id, int password = 0, int balance = 0)
            : id(id), password(password), balance(balance) { }

        bool operator==(Account const& other) const { return id == other.id; }
    };

    void remove(int id) {
        auto& acc = get_unverified(id);
        accounts.remove(acc);
    }

    void add(Account const& acc) {
        if (std::count(accounts.begin(), accounts.end(), acc.id))
            throw std::runtime_error("account with the same id exists"); // TODO include id?
        accounts.push_back(acc);
    }

    Account& get_unverified(int id) {
        auto it = std::find(accounts.begin(), accounts.end(), id);
        if (it != accounts.end())
            return *it;
        throw std::runtime_error("Account " + std::to_string(id) + " doesn't exist");
    }

    Account& get(int id, int password) {
        auto& acc = get_unverified(id);
        if (acc.password != password)
            throw std::runtime_error("Password for id <" + std::to_string(id) + "> is incorrect");
        return acc;
    }

    void status() const {
        std::cout << "*******************************\n";
        std::cout << "Bank status: \n";

        int totalAmount = 0;
        for (auto const &acc : accounts) {
            std::cout << "Account: " << acc.id << ", Account password: " << acc.password << ", Balance: " << acc.balance
                << "$\n";
            totalAmount += acc.balance;
        }

        // Print and Reset bank's balance to zero
        std::cout << "Bank's balance: " << totalAmount << "\n";
        std::cout << "*******************************\n";
    }

  private:
    std::list<Account> accounts;
};

// something to make access guarded:
template <typename T>
struct Locking {
    template <typename Op>
    void transactional(Op&& op) {
        std::lock_guard<std::mutex> lk(_mx);
        std::forward<Op>(op)(_value);
    }

  private:
    std::mutex _mx;
    T _value;
};

static void bankLoop(Locking<Bank>& safe, std::atomic_bool& keepRunning) {
    while (keepRunning) {
        std::this_thread::sleep_for(std::chrono::seconds(1));

        safe.transactional(std::mem_fn(&Bank::status));
    }
}

struct Log {
    Log() : ofs("log.txt") { } // TODO fix mode/fail on existing?

    void Write(std::string const &msg) {
        std::lock_guard<std::mutex> lk(_mx);
        ofs << msg << "\n";
    }

  private:
    std::mutex _mx;
    std::ofstream ofs;
} logDescriptor;

struct Atm {
    Locking<Bank>& safe;
    int const fileNum;

    void process(std::string const& fileName) {
        std::ifstream file(fileName);

        std::string line;
        while (std::getline(file, line)) {
            // totally made up this feature:
            if (line.empty()) {
                std::this_thread::sleep_for(std::chrono::milliseconds(200));
                continue;
            }

            switch (line[0]) {
                case 'O': openNewAccount(line); break;
                case 'D': depositAccount(line); break;
                case 'W': Withdrawl(line); break;
                case 'B': Balance(line); break;
                case 'Q': CloseAccount(line); break;
                case 'T': TransferAccount(line); break;
            }
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        file.close();
    }

  private:

    void TransferAccount(std::string const& msg) {
        std::cout << "Transfering to account... " << msg << "\n";

        safe.transactional([=](Bank& bank) {
            std::ostringstream msgLog;
            try {
                auto const cmd = parseCommand(msg);
                auto& source = bank.get(cmd.id, cmd.pw);
                auto& target = bank.get_unverified(cmd.id2);

                source.balance -= cmd.amount;
                target.balance += cmd.amount;

                msgLog 
                    << "<ATM ID: " << fileNum << ">: "
                    << "Transfer <" << cmd.amount << "> "
                    << "from account <" << cmd.id << "> "
                    << "to account <" << cmd.id2 << "> "
                    << "new balance is <" << source.balance << "> "
                    << "new target account balance is <" << target.balance << ">";
            } catch(std::exception const& e) {
                msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed  - " << e.what();
            }
            logDescriptor.Write(msgLog.str());
        });
    }

    void CloseAccount(std::string const& msg) {
        std::cout << "Closing account... " << msg << "\n";
        safe.transactional([=](Bank& bank) {
            std::ostringstream msgLog;
            try {
                auto const cmd = parseCommand(msg);
                auto& acc = bank.get(cmd.id, cmd.pw);
                auto const balance = acc.balance;

                bank.remove(acc.id);

                msgLog 
                    << "<ATM ID: " << fileNum << ">: "
                    << "Account <" << cmd.id << "> is now closed. "
                    << "Balance was <" << balance << ">";
            } catch(std::exception const& e) {
                msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed  - " << e.what();
            }
            logDescriptor.Write(msgLog.str());
        });
    }

    void depositAccount(std::string const& msg) {
        std::cout << "Depositing to account.. " << msg << "\n";
        safe.transactional([=](Bank& bank) {
            std::ostringstream msgLog;
            try {
                auto const cmd = parseCommand(msg);
                auto& acc = bank.get(cmd.id, cmd.pw);
                acc.balance += cmd.amount;

                msgLog 
                    << "<ATM ID: " << fileNum << ">: "
                    << "Account <" << cmd.id << "> "
                    << "new balance is <" << acc.balance << "> "
                    << "after <" << cmd.amount << "> was deposited";
            } catch(std::exception const& e) {
                msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed  - " << e.what();
            }
            logDescriptor.Write(msgLog.str());
        });
    }

    void Withdrawl(std::string const& msg) {
        std::cout << "Withdrawl from account.. " << msg << "\n";
        safe.transactional([=](Bank& bank) {
            std::ostringstream msgLog;
            try {
                auto const cmd = parseCommand(msg);
                auto& acc = bank.get(cmd.id, cmd.pw);
                acc.balance -= cmd.amount;

                msgLog 
                    << "<ATM ID: " << fileNum << ">: "
                    << "Account <" << cmd.id << "> "
                    << "new balance is <" << acc.balance << "> "
                    << "after <" << cmd.amount << "> was Withdrawl";
            } catch(std::exception const& e) {
                msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed  - " << e.what();
            }
            logDescriptor.Write(msgLog.str());
        });
    }

    void Balance(std::string const& msg) {
        std::cout << "Balance from account.. " << msg << "\n";
        safe.transactional([=](Bank& bank) {
            std::ostringstream msgLog;
            try {
                auto const cmd = parseCommand(msg);
                auto& acc = bank.get(cmd.id, cmd.pw);

                msgLog 
                    << "<ATM ID: " << fileNum << ">: "
                    << "Account <" << cmd.id << "> "
                    << "new balance is <" << acc.balance << ">";
            } catch(std::exception const& e) {
                msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed  - " << e.what();
            }
            logDescriptor.Write(msgLog.str());
        });
    }

    void openNewAccount(std::string const& msg) {
        std::cout << "Opening account... " << msg << "\n";
        safe.transactional([=](Bank& bank) {
            std::ostringstream msgLog;
            try {
                auto const cmd = parseCommand(msg);
                Bank::Account const acc(cmd.id, cmd.pw, cmd.amount);
                bank.add(acc);

                msgLog 
                    << "<ATM ID: " << fileNum << ">: "
                    << "New account id is <" << acc.id << "> with passoword <" << acc.password << "> and initial balance <" << acc.balance << ">,"; // FIXME trailing comma
            } catch(std::exception const& e) {
                msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed  - " << e.what();
            }
            logDescriptor.Write(msgLog.str());
        });
    }

  private:
    struct Cmd { int id=-1, pw=-1, amount=0, id2=-1; };
    static Cmd parseCommand(std::string const& msg) {
        Cmd result;
        char discard; // absorbs command character
        std::istringstream iss(msg);

        if (!(iss >> discard >> result.id >> result.pw))
            throw std::runtime_error("the command message is invalid");

        iss >> result.amount >> result.id2;

        return result;
    }
};

int main() {
    std::cout << "Please enter the number of ATMs you want: ";
    int n = 0;
    if (!(std::cin >> n))
        throw std::runtime_error("Input failed");

    // Create bank thread
    Locking<Bank> bank;
    std::atomic_bool keepRunning{true};
    std::thread bankThread(&bankLoop, std::ref(bank), std::ref(keepRunning));

    std::list<std::thread> atmThreads;
    for (int i = 0; i < n; i++) {
        atmThreads.emplace_back([&bank, i] {
            Atm atm { bank, i };
            atm.process("ATM_" + std::to_string(i) + "_input_file.txt");
        });
    }

    // Join ATM threads
    for (auto &atm : atmThreads)
        atm.join();

    // Join bank thread
    keepRunning = false;
    bankThread.join();
}

Let’s test it with 1 ATM file

O 123 8888 10
O 123 8888 10
W 123 8888 5
B 123 8888
B 123 7777

O 234 9999 20
D 234 9999 50
B 234 9999

W 123 8888 15
T 234 9999 30  123
Q 234 9999
Q 234 9999
B 123 what

It prints

Please enter the number of ATMs you want: 1
Opening account... O 123 8888 10
Opening account... O 123 8888 10
Withdrawl from account.. W 123 8888 5
Balance from account.. B 123 8888
Balance from account.. B 123 7777
Opening account... O 234 9999 20
Depositing to account.. D 234 9999 50
Balance from account.. B 234 9999
Withdrawl from account.. W 123 8888 15
Transfering to account... T 234 9999 30  123
Closing account... Q 234 9999
Closing account... Q 234 9999
Balance from account.. B 123 what
*******************************
Bank status: 
Account: 123, Account password: 8888, Balance: 20$
Bank's balance: 20
*******************************

And log.txt ends up like:

<ATM ID: 0>: New account id is <123> with passoword <8888> and initial balance <10>,
Error <ATM ID: 0>: Your transaction failed  - account with the same id exists
<ATM ID: 0>: Account <123> new balance is <5> after <5> was Withdrawl
<ATM ID: 0>: Account <123> new balance is <5>
Error <ATM ID: 0>: Your transaction failed  - Password for id <123> is incorrect
<ATM ID: 0>: New account id is <234> with passoword <9999> and initial balance <20>,
<ATM ID: 0>: Account <234> new balance is <70> after <50> was deposited
<ATM ID: 0>: Account <234> new balance is <70>
<ATM ID: 0>: Account <123> new balance is <-10> after <15> was Withdrawl
<ATM ID: 0>: Transfer <30> from account <234> to account <123> new balance is <40> new target account balance is <20>
<ATM ID: 0>: Account <234> is now closed. Balance was <40>
Error <ATM ID: 0>: Your transaction failed  - Account 234 doesn't exist
Error <ATM ID: 0>: Your transaction failed  - the command message is invalid

1

solved bank, 4 atm machine input txt files, syncing information between them with semaphores