-
Notifications
You must be signed in to change notification settings - Fork 0
/
RM1.cpp
1626 lines (1307 loc) · 62.2 KB
/
RM1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Texty Web App
Twitter Clone
Programmer:
Joseph Bieselin
Networking code interfaces PHP on user machine with a server running C++ code.
"Database" emulated with C++ file handling.
Threading will be used to handle multiple requests.
Replication will be implemented as pseudo-servers running in localhost on separate ports.
Each replication manager has their own directory; the name is just a stingified int:
RM1 --> "1"
RM2 --> "2"
RM3 --> "3"
RMX --> "X+1"
Goals set by different stages for replication part of the project:
1) Simply handle all data having all data replicated on each "server"
- i.e. the each replication manager will write data sent by PHP from a user
2) Each replication manager updates its own data, then sends the serialized string to other RMs to update their own data
3) Handle any of the replication managers dying (crashing or not responding for some reason)
- a primary (leader) is not used in my design
- any replication manager can go down, and the user will only notice a small lapse in load time if the client PHP code tried to connect to a down server (RM)
- assumption is made that a replication manager does not rise from the dead while other replication managers are running
- crashed replication managers will only come back up during server down time in which case files would be copied manually from a replication manager that did not crash
- if all servers crash at once whlie running, mandatory server down time will be taken to copy files from the last replication manager that was running
4) Creating a new replication manager
- create another instance of an RMX.cpp file
- in that file update the directory and file path constants to relate to the number of the X in RMX (since path names are used since this is run on localhost on a local machine)
- in each RM.cpp file and the funcs_and_consts.php, increase the MAX_PORT constant to include this new RM
- edit the THIS_PORT constant to be the new, unique port number added to the list of MIN_PORT to MAX_PORT ports
Structure of all_users.txt:
N,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'\n'
username,email,index_i,password,firstname,lastname,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'\n'
username,email,index_j,password,firstname,lastname,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'\n'
username,email,index_k,password,firstname,lastname,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'\n'
... continues on for however many users are in the file;
Where N is index of the next user (this can go to infinity since numbers are not reused after a user deactivates their account);
We assume for now that 10 bytes corresponding to the string "999999999" will suffice for 99999999 possible unique user indexes;
Each line contains strings representing user info separated by commas;
Each line is 118 bytes where commas are put at the end of each line before the newline character so lines can be overwritten after created
Structure of followers.txt & followees.txt:
index_i,username,,,,,,,,,,,,,,,,,,,,,,,,,,'\n'
index_j,username,,,,,,,,,,,,,,,,,,,,,,,,,,'\n'
... continues on for however many users are in the file;
The user that the person is followed by (follower) or the that is following (followee) has their index and username on a line;
Each line is 28 bytes where commas are put at the end of each line before the newline character so lines can be overwritten after created
Structure of texts.txt:
This is a samaple texty from some user'\n'
This is a second sample from some user'\n'
The user can input almost any character they want, excluding new lines;
New lines delimit the end of one texty from the next;
A texty cannot be deleted... ever, unless the account is deactivated
A global map data structure is used to handle data.
The key value is a stringified int, i.e. "1" or "50".
The int represents the user's index in the server.
The data portion of the map contains an instance of my User_Dir class.
The User_Dir class is used mainly to hold a mutex and to hold instances of Lock_File.
Lock_File is another class which has a mutex to lock a specific file based on the member filename (absolute path to a file).
Each User_Dir instance has 4 Lock_File instances corresponding to followees.txt, followers.txt, texts.txt, username.txt
Every user directory stored in the "files" directory has those 4 files specific to that user based on index number.
The user directory name is the user's index number.
Member variables and functions are used to simply getting filepaths and open/closing files.
An All_Users class is also created to handle access to the all_users.txt file which stores every user in the system.
The class handles open/closing the file and also contains a mutex variable to lock the file when necessary.
*/
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <iostream>
#include <stdio.h> // perror, snprintf
#include <sys/stat.h> // provide stat functionality for directory checking
#include <string.h> // provide c string capabilities
#include <strings.h> // bzero
#include <unistd.h> // provide functionality for UNIX calls
#include <dirent.h> // used for filled in directory entries
#include <stdlib.h> // malloc, calloc, free
#include <typeinfo>
#include <time.h> // time, ctime
#include <sys/socket.h> // socket, AF_INET, SOCK_STREAM, bind, listen, accept
#include <netinet/in.h> // servaddr, INADDR_ANY, htons
#include <netdb.h> // gethostbyname() for IP address conversion
#include <netinet/in.h> // inet_aton
#include <arpa/inet.h> // inet_aton
#include <mutex>
#include <thread>
#include <condition_variable> // condition_variable
using namespace std;
/* ---------------------------------- CONSTANTS ----------------------------------------------*/
// File Handling
#define MAX_PATH 1000 // maximum file path is probably not more than 1000 chars
#define REP_MAN_DIR "/var/www/html/1" // directory path for this replication manager
#define FILES_DIR "/var/www/html/1/files" // directory path for files
#define ALL_USERS_TXT "/var/www/html/1/files/all_users.txt" // filepath for the master "all_users.txt" file
#define MAX_INDEX_BYTES 10 // maximum number of user indexes that can be used at creation of files: 10 bytes = 999999999 possible indexes for a cstring
#define MAX_USER_INFO_BYTES 118 // maximum number of bytes each line in all_users.txt will be for each user including: user's data, commas, and '\n' character
#define UN_BYTES 17 // maximum number of characters in username based on value limited in PHP file
#define EMAIL_BYTES 41 // maximum number of characters in email based on value limited in PHP file
#define PW_BYTES 17 // maximum number of characters in password based on value limited in PHP file
#define FN_BYTES 21 // maximum number of characters in first name based on value limited in PHP file
#define LN_BYTES 21 // maximum number of characters in last name based on value limited in PHP file
#define GARBAGE_BYTES 70 // length of bytes to hold characters after username and email fields in file handling
#define PHP_MSG_SIZE 130 // number of characters to hold a message from PHP which could include: 5 chars for function to call, 1 char for a comma, and 118 chars for comma separated user data
// Truncated commands strings passed by PHP to indicate which C++ function to call during network connections
#define REGISTER_STR "regst" // string for registering a user
#define DEACTIVATE_STR "deact" // string for deactivating a user
#define LOGIN_STR "login" // string for logging a user in
#define SEARCH_STR "searc" // string for searching for user
#define DISPLAY_STR "displ" // string for displaying a user's page
#define OTHER_DISPLAY_STR "ot_ds" // string for displaying another user's page
#define TEXTY_STR "texty" // string for a user to send a texty
#define FOLLOW_STR "follo" // string for a user to follow another user
#define UNFOLLOW_STR "unfol" // string for a user to unfollow another user
#define LAST_TEXTY_STR "lastT" // string to get last texty sent by a user
// Networking
#define MAXLINE 4096 // max text line length
#define BUFFSIZE 8192 // buffer size for reads and writes
#define SA struct sockaddr
#define LISTENQ 1024 // 2nd argument to listen()
#define START_PORT 13001 // smallest port number in use by the RMs
#define END_PORT 13003 // largest port number in use by the RMs
#define THIS_PORT 13001 // this RM's port number
#define HOST_NAME "localhost" // the host's name that will relate to an IP address to connect to
/* ---------------------------------- CONSTANTS ----------------------------------------------*/
// prototypes for function use in All_User's use_file function
string register_user(const string& un, const string& email, const string& pw, const string& fn, const string& ln);
bool user_exists(fstream& fh, int check, const string& un, const string& email, const string& pw);
string remove_user(fstream& fh, ofstream& temp_file, const string& un);
string search(const string& search_str);
/* ----------------------------------- CLASSES -----------------------------------------------*/
// Global file class for the master "all_users.txt" file
class All_Users {
public:
mutable mutex mut;
mutable mutex safe_lock;
fstream fh;
// filename is absolute path of the file
string filename;
// count of readers and writers
int num_readers;
int num_writers;
// read/write condition variables
condition_variable cv_read;
condition_variable cv_write;
// Constructor
All_Users() : filename(ALL_USERS_TXT), num_readers(0), num_writers(0) {}
// Copy Constructor and Assignment Operator deleted
All_Users(const All_Users& copy) = delete;
All_Users& operator=(const All_Users& right) = delete;
// Destructor
~All_Users()
{
if (fh.is_open()) {
fh.close();
}
}
// type: 0 --> read, 1 --> write
void use_file(const string& func_to_call, const string& un, const string& email, const string& pw, const string& fn, const string& ln, string& return_str, int type)
{
// lock the mutex used for the file
unique_lock<mutex> lck(mut);
if (type == 0) { // read
// wait until there are no more writers
while (num_writers != 0) cv_read.wait(lck);
// increment the number of readers
++num_readers;
// unlock the file
lck.unlock();
// since no one is writing, notify all the readers that they can read
cv_read.notify_all();
} else { // write
// wait until there are no more readers or writers
while (num_readers != 0 && num_writers != 0) cv_write.wait(lck);
// notify that there is someone writing to the file
++num_writers;
}
fh.open(filename);
if (func_to_call == REGISTER_STR) {
return_str = register_user(un, email, pw, fn, ln); // returns username,email,firstname,lastname if successful
// this writer is now finished, so decrement the writer count
--num_writers;
// notify another writer that is possibly waiting
lck.unlock();
cv_write.notify_one();
}
else if (func_to_call == DEACTIVATE_STR) {
if (!user_exists(fh, 3, un, email, pw)) {
return_str = ",";
} else {
// the user exists, so remove them from all_users.txt by creating a temp file without them, then renaming that temp file to all_users.txt
char temp_filename[MAX_PATH + 1];
strcpy(temp_filename, FILES_DIR);
strcat(temp_filename, "/temp.txt");
ofstream temp_file(temp_filename);
string remove_str = remove_user(fh, temp_file, un); // returns username if successful
// remove the old all_users.txt and rename the temp file with updated user info to all_users.txt
temp_file.close();
remove(filename.c_str());
rename(temp_filename, filename.c_str());
return_str = remove_str;
}
// this writer is now finished, so decrement the writer count
--num_writers;
// notify another writer that is possibly waiting
lck.unlock();
cv_write.notify_one();
}
else if (func_to_call == LOGIN_STR) {
if (!user_exists(fh, 2, un, email, pw)) {
return_str = ",";
} else {
return_str = un; // returns username
}
// this reader is now finished, so decrement the reader count
lck.lock();
--num_readers;
// notify other readers that are possibly waiting
lck.unlock();
cv_read.notify_all();
}
else if (func_to_call == SEARCH_STR) {
string search_string; // will contain a username or email to search for
search_string = ( (un != "") ? un : email);
return_str = search(search_string); // returns username,email,firstname,lastname if user is found
// this reader is now finished, so decrement the reader count
lck.lock();
--num_readers;
// notify other readers that are possibly waiting
lck.unlock();
cv_read.notify_all();
}
fh.close();
// if we were reading, notify a writer to go; if we were writing, notify a reader to go
if (type == 0)
cv_write.notify_one();
else
cv_read.notify_one();
}
};
// Class for locking user files
class Lock_File {
public:
mutable mutex mut;
fstream fh;
// filename is absolute path of the file
string filename;
// Constructor
Lock_File(const string& index_val, const string& name) : filename(FILES_DIR) {filename += "/" + index_val + "/" + name;}
// Copy Constructor and Assignment Operator deleted
Lock_File(const Lock_File& copy) = delete;
Lock_File& operator=(const Lock_File& right) = delete;
// Destructor
~Lock_File()
{
if (fh.is_open()) {
fh.close();
}
}
void open_file()
{
fh.open(filename);
}
void close_file()
{
fh.close();
}
// CANNOT CREATE A FILE TO LOCK A FUNCTION BECAUSE UNIQUE_LOCK & LOCK_GUARD RELEASE LOCKS AT THE END OF FUNCTION CALLS
};
// User directory class that can lock a directory to alter files or access Lock_File objects to handle a user's file data
class User_Dir {
public:
mutable mutex mut;
string user_index;
string dir_path;
// instances of a user's Lock_File objects are created in every directory
Lock_File followees;
Lock_File followers;
Lock_File texts;
Lock_File username;
// Constructor
User_Dir(const string& index_val) : user_index(index_val), dir_path(FILES_DIR), followees(index_val, "followees.txt"), followers(index_val, "followers.txt"), texts(index_val, "texts.txt"), username(index_val, "username.txt") { dir_path += "/" + index_val; }
// Copy Constructor and Assignment Operator deleted
User_Dir(const User_Dir& copy) = delete;
User_Dir& operator=(const User_Dir& right) = delete;
// Destructor
~User_Dir() {}
// unset_index is called when a user is being removed from the system; therefore, indicate that the files no longer exist with a "-1" as the directory index
void unset_index() { user_index = -1; }
// open files in a user directory
void open_followees()
{
if (user_index != "-1")
followees.open_file();
}
void open_followers()
{
if (user_index != "-1")
followers.open_file();
}
void open_texts()
{
if (user_index != "-1")
texts.open_file();
}
// returns the username of the user corresponding to this indexed directory
string get_username()
{
username.open_file();
string un;
getline(username.fh, un);
username.close_file();
return un;
}
// close files in a user directory
void close_followees() { followees.close_file(); }
void close_followers() { followers.close_file(); }
void close_texts() { texts.close_file(); }
};
/* ----------------------------------- CLASSES -----------------------------------------------*/
/* ------------------------------ GLOBAL VARIABLES -------------------------------------------*/
mutex PHP_ARGS_MUT; // used when a PHP connection is established and a string needs to be passed to a thread
mutex DIRECTORIES_MUT; // used when a new user registers or a user deactivates their account (directory is created or removed)
// global All_Users instance to handle locking and unlocking of the master "all_users.txt" file (and opening and closing of it)
All_Users all_user_file;
// global map variable used to handle all user directory and file data
map<string, User_Dir*> USER_DIRECTORIES;
// global vector of ports to other servers; the first index is this server's port number
vector<int> port_nums;
/* ------------------------------ GLOBAL VARIABLES -------------------------------------------*/
/* ---------------------------------------------- FUNCTIONS --------------------------------------------------- */
// Return true if the passed in cstring is a directory; false otherwise
bool is_dir(const char* path)
{
struct stat buf;
int status;
status = stat(path, &buf);
if (status == -1) {
// If stat does not return 0, there was an error
return false;
}
if ( (buf.st_mode & S_IFDIR) == 0)
// Directory was not created -- creating "files" directory now
return false;
return true; // no errors and path was determined to be a directory
}
// Return true if the passed in cstring is a file that exists; false otherwise
bool file_exists(const char* name)
{
struct stat buf;
int status;
status = stat(name, &buf);
return status == 0;
}
// Creates the "files" directory and "all_users.txt" file in that directory if they don't already exist (program exits on any errors in their creation)
void create_users_database()
{
int status;
// create a directory where the user info files will go
if (!is_dir(REP_MAN_DIR)) {
// If the RM's index directory doesn't exist, create it with RWX permissions for everyone
status = mkdir(REP_MAN_DIR, S_IRWXU|S_IRWXG|S_IRWXO);
if (status == -1) {
cout << endl << "ERROR: mkdir could not create RM's directory" << endl;
// exit the program if we could not create a directory to store the RM's data
exit (1);
}
chmod(REP_MAN_DIR, S_IRWXU|S_IRWXG|S_IRWXO); // give everyone RWX permissions
}
// create the files directory inside of the RM's directory
if (!is_dir(FILES_DIR)) {
// If the files directory doesn't exist, create it with RWX permissions for everyone
status = mkdir(FILES_DIR, S_IRWXU|S_IRWXG|S_IRWXO);
if (status == -1) {
cout << endl << "ERROR: mkdir could not create files directory" << endl;
// exit the program if we could not create a directory to store user info
exit(1);
}
chmod(FILES_DIR, S_IRWXU|S_IRWXG|S_IRWXO); // give everyone RWX permissions
}
// create a file to store all the user's in our system
if (!file_exists(ALL_USERS_TXT)) {
// If the file doesn't exist, create it
ofstream temp_create_file(ALL_USERS_TXT);
temp_create_file.close();
fstream fh(ALL_USERS_TXT);
if (!fh.is_open()) {
cout << endl << "ERROR: could not open all_users.txt" << endl;
// exit the program if we could not open the file to store all the users in our system
exit(2);
} else { // 1st user created will have an index of 1
// The first line of the file is the index number that the next registered user will receieve, ie: 1 (since the file did not exist yet)
string index_line = "1";
for(size_t i = 1; i < MAX_USER_INFO_BYTES - 1; ++i)
index_line += ",";
index_line += "\n";
fh << index_line; // Each line is 118 chars long, with commands appending user data to set a standard line length, and ends with a newline char
fh.close();
}
}
}
// sets up map<string, User_Dir*> USER_DIRECTORIES to contain all files and directories in the system
void create_directory_mappings()
{
/* Base code taken from StackOverflow URL: http://stackoverflow.com/questions/67273/how-do-you-iterate-through-every-file-directory-recursively-in-standard-c
Used to read directory entries in the files directory which stores created user directories
These entries are placed into a map structure which is globally available to use
*/
// create a directory pointer and a directory entry pointer
struct dirent *entry;
DIR *dp;
// open the path corresponding the files directory where all the data is stored
dp = opendir(FILES_DIR);
if (dp == NULL) {
// error opening directory so exit program
cout << "ERROR: in create_directory_mappings - could not open directory path for 'files'" << endl;
exit(1);
}
// read each directory entry until there are no more entries
while ((entry = readdir(dp))) {
// if the directory entry is a directory and not "." or "..", insert it's entry into the USER_DIRECTORIES map
if ( (entry->d_type == DT_DIR) && (strcmp(entry->d_name, ".") != 0) && (strcmp(entry->d_name, "..") != 0) ) {
// insert a new mapping: the key corresponds to a stringified int which is the user's index (and diretory name), and create a User_Dir object passing in the user's stringified int
USER_DIRECTORIES.insert(pair<string, User_Dir*> (entry->d_name, new User_Dir(entry->d_name)));
}
}
closedir(dp);
}
// sets up the ports_nums vector to contain the int values of all port numbers in use by this application (in our case its a linear set of values starting at 13001)
void create_ports_vector()
{
for (size_t port_num = START_PORT; port_num <= END_PORT; ++port_num) {
port_nums.push_back(port_num);
}
}
// Create 4 .txt files in the passed in directory: followees, followers, texts, username
void create_user_files(const char* dir, const string& un)
{
char user_path[MAX_PATH + 1];
// create 4 user files as an ofstream and immediately close it
strcpy(user_path, dir);
strcat(user_path, "/");
strcat(user_path, "followees.txt");
ofstream f1(user_path); f1.close();
strcpy(user_path, dir);
strcat(user_path, "/");
strcat(user_path, "followers.txt");
ofstream f2(user_path); f2.close();
strcpy(user_path, dir);
strcat(user_path, "/");
strcat(user_path, "texts.txt");
ofstream f3(user_path); f3.close();
// for the username.txt file, input the only line the file will contain, which is the username of this user
strcpy(user_path, dir);
strcat(user_path, "/");
strcat(user_path, "username.txt");
ofstream f4(user_path);
f4 << un;
f4.close();
}
/*
A new user needs to be created;
Each user has a directory corresponding to their index number;
Each user's directory has 4 .txt files: followees, followers, texts, username
*/
void create_user_dir(const char* user_index, const string& un)
{
char user_dir[MAX_PATH + 1];
strcpy(user_dir, FILES_DIR);
strcat(user_dir, "/");
strcat(user_dir, user_index);
int status = mkdir(user_dir, S_IRWXU|S_IRWXG|S_IRWXO);
if (status == -1) {
cout << endl << "ERROR: mkdir could not create this new user's directory" << endl;
return;
}
chmod(user_dir, S_IRWXU|S_IRWXG|S_IRWXO); // give everyone RWX permissions
create_user_files(user_dir, un);
// lock the directories because a new directory is being added to the system
lock_guard<mutex> lk(DIRECTORIES_MUT);
USER_DIRECTORIES.insert(pair<string, User_Dir*>(user_index, new User_Dir(user_index)));
}
/*
The parameter check will be used in a switch statement and do one of the following 3 checks
1) If no password is passed, return true if either un or email match the username or email of a user in all_users.txt (used for registering a new user)
2) If a password is passed, return true if both un and pw match one user's username and password (used for logging in)
3) Return true if un, email, and pw all match one user's username, email, and password (used for deactivating a user)
*/
bool user_exists(fstream& fh, int check, const string& un, const string& email, const string& pw = "")
{
// skip the first of the line of the file because it does not contain user info
string first_line_garbage;
fh.clear(); fh.seekp(0, ios::beg); getline(fh, first_line_garbage);
// test info is one line from the all_users.txt file
// test_info = un,email,index,pw,fn,ln
// char* test_un = (char*) malloc(UN_BYTES);
// char* test_email = (char*) malloc(EMAIL_BYTES);
// char* test_index = (char*) malloc(MAX_INDEX_BYTES);
// char* test_pw = (char*) malloc(PW_BYTES);
// char* garbage = (char*) malloc(GARBAGE_BYTES);
char test_un[UN_BYTES + 1];
char test_email[EMAIL_BYTES + 1];
char test_index[MAX_INDEX_BYTES + 1];
char test_pw[PW_BYTES + 1];
char garbage[GARBAGE_BYTES + 1];
while (!fh.eof()) {
// Test username
fh.get(test_un, UN_BYTES, ','); // comma separated values, so ',' is the delim parameter
// ',' is the next character in the stream, so just get that
fh.get();
fh.get(test_email, EMAIL_BYTES, ','); // comma separated values, so ',' is the delim parameter
// ',' is the next character in the stream, so just get that
fh.get();
// Test index
fh.get(test_index, MAX_INDEX_BYTES, ',');
// ',' is the next character in the stream, so just get that
fh.get();
fh.get(test_pw, PW_BYTES, ','); // comma separated values, so ',' is the delim parameter
// garbage and .get() will get the next ',' along with the rest of the user info that is not need for testing
fh.get(); //garbage, 2);
fh.getline(garbage, GARBAGE_BYTES);
// 1) test username or email, 2) test username and password, 3) test username, email, and password
switch (check)
{
case 1: // Register: both the username and email should be unique
if( (strcmp(test_un, un.c_str()) == 0) || (strcmp(test_email, email.c_str()) == 0) ) {
return true;
}
case 2: // Log-in: both the username and password should match
if( (strcmp(test_un, un.c_str()) == 0) && (strcmp(test_pw, pw.c_str()) == 0) ) {
return true;
}
case 3: // Deactivate: the username, email, and password should match
if( (strcmp(test_un, un.c_str()) == 0) && (strcmp(test_email, email.c_str()) == 0) && (strcmp(test_pw, pw.c_str()) == 0) ) {
// lock the user directory to change one its member variable's values
unique_lock<mutex> lk(DIRECTORIES_MUT);
unique_lock<mutex> dir_lk(USER_DIRECTORIES[test_index]->mut);
// for the user that will be soon be removed, set his directory entry index in the global map to "-1" indicating the user and all his/her files are deleted (or in our case in the process of being deleted)
USER_DIRECTORIES[test_index]->user_index = "-1";
return true;
}
}
}
return false; // got to the end of the file and no matches were made so return false
}
// Write over the current line that the fh stream is pointing to with a new index
void write_next_index(fstream& fh, string& next_index)
{
fh.seekp(0, ios::beg); // set the file stream to point to the beginning of the file
for (size_t i = 0; i < (MAX_USER_INFO_BYTES - next_index.size()); ++i)
next_index += ",";
//next_index += "\n";
fh << next_index;
}
// Takes a file handle and adds the strings of user data to the end of the file with the line structure in the comments at the top
void add_user(fstream& fh, const string& un, const string& email, const string& index_val, const string& pw, const string& fn, const string& ln)
{
string user_line = un + "," + email + "," + index_val + "," + pw + "," + fn + "," + ln + '\n';
// Clear any flag bits that may impede writing to the file, then set the stream to point to the end of the file
fh.clear();
fh.seekp(0, ios::end);
fh << user_line;
}
/*
The file passed in will be the followees.txt or followers.txt file containing the user we want to remove;
The index corresponds to the index of the user we will be removing
*/
void remove_user_from_files(fstream& fh, ofstream& temp_file, const string& index_val)
{
string temp, temp_index;
while (!fh.eof()) {
// go through each line getting the index for each user
getline(fh, temp);
for (size_t i = 0; i < temp.size(); ++i) {
if (temp[i] == ',')
break;
temp_index += temp[i];
}
// if the current line's index is not the user we are removing, add them to the new temp file
if (temp_index != index_val && temp != "") {
temp_file << temp;
temp_file << "\n";
}
temp_index.clear();
}
}
/*
Remove the user from all_users.txt;
Remove the user from the followees.txt file of anyone in the user's followers.txt file;
Remove the user from the followers.txt file of anyone in the user's followees.txt file;
Delete the user's followees.txt, followers.txt, and texts.txt files and the user's directory;
Return the passed in username if successful
*/
string remove_user(fstream& fh, ofstream& temp_file, const string& un)
{
string user_index;
string temp, temp_username;
/* ---------------------------- REMOVING USER FROM all_users.txt --------------------------------- */
// place the first line of all_users.txt into the new temp file
fh.clear(); fh.seekp(0, ios::beg);
getline(fh, temp);
temp_file << temp; temp_file << "\n";
// add all lines of all_users.txt that don't match the specified username into the new temp file
while (!fh.eof()) {
getline(fh, temp);
size_t i;
for (i = 0; i < temp.size(); ++i) {
if (temp[i] == ',')
break;
temp_username += temp[i];
}
// if the current line's user is the not the user we are removing, add them to the new temp file
if (temp_username != un && temp != "") {
temp_file << temp;
temp_file << "\n";
} else if (temp_username == un) { // this is the user we are removing
// first we must parse through the next data value which is the email and i is already at the index of email's first char
for (++i; i < temp.size(); ++i) {
if (temp[i] == ',')
break;
}
// get user's index value until the next comma is hit
for (++i; i < temp.size(); ++i) {
if (temp[i] == ',')
break;
user_index += temp[i];
}
}
temp_username.clear();
}
/* ---------------------------- REMOVING USER FROM all_users.txt --------------------------------- */
/* ------------------- Deleting the user's texts.txt and username.txt files -----------------------*/
// delete the user's texts.txt file
unique_lock<mutex> user_text_lk(USER_DIRECTORIES[user_index]->texts.mut);
remove(USER_DIRECTORIES[user_index]->texts.filename.c_str());
user_text_lk.unlock();
// delete the user's username.txt file
unique_lock<mutex> user_un_lk(USER_DIRECTORIES[user_index]->username.mut);
remove(USER_DIRECTORIES[user_index]->username.filename.c_str());
user_un_lk.unlock();
/* ------------------- Deleting the user's texts.txt and username.txt files -----------------------*/
// obtain locks on user's followees.txt and followers.txt files
unique_lock<mutex> user_followee_lk(USER_DIRECTORIES[user_index]->followees.mut);
unique_lock<mutex> user_follower_lk(USER_DIRECTORIES[user_index]->followers.mut);
// set up unique_lock pointer object for other user's
vector<unique_lock<mutex>*> other_lks;
unique_lock<mutex>* other_user_file_lk;
string other_user_index;
char temp_filepath[MAX_PATH + 1];
/* -------------------- REMOVING USER FROM OTHER USERS' followers.txt file ----------------------- */
// open followees file
USER_DIRECTORIES[user_index]->followees.open_file();
// for each other user in this user's followees.txt file, remove this user from the other user's followers.txt file
while (!USER_DIRECTORIES[user_index]->followees.fh.eof()) {
// go through each user in the followees.txt file
getline(USER_DIRECTORIES[user_index]->followees.fh, temp);
if (temp != "") {
// get the other user's index value from the followees.txt file
for (size_t i = 0; i < temp.size(); ++i) {
if (temp[i] == ',')
break;
other_user_index += temp[i];
}
// lock the other user's followers.txt file to remove this user from it
other_lks.push_back(new unique_lock<mutex> (USER_DIRECTORIES[other_user_index]->followers.mut));
USER_DIRECTORIES[other_user_index]->followers.open_file();
// obtain the path to the other user's directory and create temp.txt in it
strcpy(temp_filepath, USER_DIRECTORIES[other_user_index]->dir_path.c_str());
strcat(temp_filepath, "/temp.txt");
ofstream other_followers_temp_file(temp_filepath);
// remove the current user from the other user's followers.txt file
remove_user_from_files(USER_DIRECTORIES[other_user_index]->followers.fh, other_followers_temp_file, user_index);
// remove the old file with the user currently in it, and rename the new temp file to its name
remove(USER_DIRECTORIES[other_user_index]->followers.filename.c_str());
rename(temp_filepath, USER_DIRECTORIES[other_user_index]->followers.filename.c_str());
USER_DIRECTORIES[other_user_index]->followers.close_file();
other_followers_temp_file.close();
// reset all the variables in this loop for the next iteration
other_user_index.clear();
other_user_file_lk = other_lks.back();
other_user_file_lk->unlock();
delete other_user_file_lk;
other_lks.clear();
}
}
// close and delete the user's followee file
USER_DIRECTORIES[user_index]->followees.close_file();
remove(USER_DIRECTORIES[user_index]->followees.filename.c_str());
//user_followee_lk.unlock();
/* -------------------- REMOVING USER FROM OTHER USERS' followers.txt file ----------------------- */
/* -------------------- REMOVING USER FROM OTHER USERS' followees.txt file ----------------------- */
// open followers file
USER_DIRECTORIES[user_index]->followers.open_file();
// for each other user in this user's followers.txt file, remove this user from the other user's followees.txt file
while (!USER_DIRECTORIES[user_index]->followers.fh.eof()) {
// go through each user in the followers.txt file
getline(USER_DIRECTORIES[user_index]->followers.fh, temp);
if (temp != "") {
// Get the other user's index value from the followers.txt file
for (size_t j = 0; j < temp.size(); ++j) {
if( temp[j] == ',')
break;
other_user_index += temp[j];
}
// lock the other user's followees.txt file to remove this user from it
other_lks.push_back(new unique_lock<mutex> (USER_DIRECTORIES[other_user_index]->followees.mut));
USER_DIRECTORIES[other_user_index]->followees.open_file();
// obtain the path to the other user's directory and create temp.txt in it
strcpy(temp_filepath, USER_DIRECTORIES[other_user_index]->dir_path.c_str());
strcat(temp_filepath, "/temp.txt");
ofstream other_followees_temp_file(temp_filepath);
// remove the current user from the other user's followees.txt file
remove_user_from_files(USER_DIRECTORIES[other_user_index]->followees.fh, other_followees_temp_file, user_index);
// remove the old file with the user currently in it, and rename the new temp file to its name
remove(USER_DIRECTORIES[other_user_index]->followees.filename.c_str());
rename(temp_filepath, USER_DIRECTORIES[other_user_index]->followees.filename.c_str());
USER_DIRECTORIES[other_user_index]->followees.close_file();
other_followees_temp_file.close();
// reset all the variables in this loop for the next iteration
other_user_index.clear();
other_user_file_lk = other_lks.back();
other_user_file_lk->unlock();
delete other_user_file_lk;
other_lks.clear();
}
}
// close and delete the user's follower file
USER_DIRECTORIES[user_index]->followers.close_file();
remove(USER_DIRECTORIES[user_index]->followers.filename.c_str());
/* -------------------- REMOVING USER FROM OTHER USERS' followees.txt file ----------------------- */
/* ----------------------------- deleting the user's directory ---------------------------------- */
// For the user being removed, delete his/her directory
rmdir(USER_DIRECTORIES[user_index]->dir_path.c_str());
/* ----------------------------- deleting the user's directory ---------------------------------- */
return un;
}
/*
register takes in 5 strings: first name, last name, email, username, password;
register stores those strings in a CSV line in all_users.txt in the directory files;
if a failure occurs, a comma is returned;
if successful, the string returned is: un,email,fn,ln
*/
string register_user(const string& un, const string& email, const string& pw, const string& fn, const string& ln)
{
char index_val[MAX_INDEX_BYTES + 1]; // MAX_INDEX originally set to 1000000 meaning 999999 user indexes could be handled at the creation
if (!all_user_file.fh.is_open()) {
cout << endl << "ERROR: could not open all_users.txt" << endl;
return ",";
} else { // file is opened
all_user_file.fh.getline(index_val, MAX_INDEX_BYTES, ','); // index will get the first line in the file which contains a number followed by the EOL character
if (user_exists(all_user_file.fh, 1, un, email)) {
cout << endl << "ERROR: username or email already exist --> " << un << " " << email << " " << pw << " " << fn << " " << ln << endl;
return ",";
} else {
// Add this new user's info to the end of the all_users.txt file
add_user(all_user_file.fh, un, email, index_val, pw, fn, ln);
// Create the new user's directory and followees.txt, followers.txt, texts.txt files
create_user_dir(index_val, un);
}
}
// The index at the beginning of all_users.txt must be incremented and updated in the file
int next_index;
next_index = atoi(index_val);
next_index++;
// Convert the next_index int value into a string to pass to write_next_index
char next_index_buffer[MAX_INDEX_BYTES + 1];
sprintf(next_index_buffer, "%d", next_index);
string next_index_string(next_index_buffer);
write_next_index(all_user_file.fh, next_index_string); // write_next_index will move the stream to the beginning of the file
string return_str = un + "," + email + "," + fn + "," + ln;
return return_str;
// unique_lock will take care of unlocking the mutex
}
// Takes a username and returns the index of that user (returns "-1" if the user doesn't exist)
string get_user_index(const string& un)
{
// index is -1 if the username is not associated with any directories
string the_index = "-1";
/*
vector<unique_lock<mutex>*> dir_locks;
unique_lock<mutex>* lk_ptr;
for (auto& map_index : USER_DIRECTORIES) {
dir_locks.push_back(new unique_lock<mutex> (map_index.second->mut));
if (un == map_index.second->get_username())
the_index = map_index.second->user_index;
lk_ptr = dir_locks.back();
lk_ptr->unlock();
delete lk_ptr;
dir_locks.clear();
}
*/
unique_lock<mutex>* lk_ptr; // use a pointer to a lock to loop through multiple mutexes
for (auto& map_index : USER_DIRECTORIES) {
// get the lock for one user directory and store the address in lk_ptr
lk_ptr = new unique_lock<mutex> (map_index.second->mut);
if (un == map_index.second->get_username()) {
the_index = map_index.second->user_index;
break;
}
// unlock the lock and clean up space on the heap
lk_ptr->unlock();
delete lk_ptr;
}
// if we found the user, make sure to release the lock and clean up the heap
if (the_index != "-1") {
lk_ptr->unlock();
delete lk_ptr;
}
return the_index;
}
// Takes a message and appends it (along with a newline character) to the user's texts.txt file