1
+ function simulate(nDays , varargin )
2
+ % GENERATE Create a list of subjects and dates to be trained on weekend
3
+ %
4
+ % Inputs:
5
+ % nDays: The number of day in the future to post water for. If empty or
6
+ % not defined, the 'nDaysInFuture' parameter is used (adjusted
7
+ % for bank holidays)
8
+ %
9
+ % Named Parameters:
10
+ % force: When true the script is run regardless of the 'minLastSent'
11
+ % parameter (default: false)
12
+ % test: When true the script is run in test mode (default: false)
13
+ %
14
+ % Examples:
15
+ % % Run using standard defaults (2 days in future)
16
+ % ww.generate
17
+ %
18
+ % % Post water for 3 days in future, regardless of when it was last run
19
+ % ww.generate(3, 'force', true)
20
+
21
+ % Ensure we're on the correct branch and up-to-date
22
+ % git.runCmd({'checkout dev', 'pull'}, 'dir', getOr(dat.paths, 'rigbox'));
23
+
24
+ send_emails = false ;
25
+
26
+ % Load the parameters
27
+ params = ww .Params ;
28
+ admins = strip(lower(params .get(' Email_admins' )));
29
+ admins_slack = params .get(' Slack_admins' );
30
+ webhook = params .get(' Slack_webhook' );
31
+ mySlackName = ' Weekend Water' ;
32
+ mySlackEmoji = ' :potable_water:' ;
33
+
34
+ % Parse input arguments (override params)
35
+ p = inputParser ;
36
+ p .addParameter(' force' , false , @islogical )
37
+ p .addParameter(' test' , params .get(' Mode' ) == 2 , @islogical )
38
+ p .parse(varargin{: })
39
+ force = p .Results .force ;
40
+ test = p .Results .test ;
41
+ debug = params .get(' Mode' ) > 0 || test ;
42
+ % Temp dir for saving log and email
43
+ tmpdir = iff(ispc , getenv(' APPDATA' ), getenv(' HOME' ));
44
+ if debug
45
+ % In debug mode we activate the log
46
+ diaryState = get(0 , ' Diary' );
47
+ diaryFile = get(0 , ' DiaryFile' );
48
+ diary(fullfile(tmpdir , ' ww.log' ))
49
+ end
50
+
51
+ % Get list of mice to be trained over the weekend. These will be marked as
52
+ % 'PIL' on the list (so long as they've been weighed)
53
+ excl = dat .loadParamProfiles(' WeekendWater' );
54
+ if isempty(fieldnames(excl )), excl = struct ; end
55
+
56
+ % Path to email file which will be sent
57
+ filename = sprintf(' mail%s .txt' , iff(test , ' -test' , ' ' ));
58
+ mail = fullfile(tmpdir , filename );
59
+
60
+ % Check when email was last generated and potentially return if too soon
61
+ mod = file .modDate(mail );
62
+ minLastSent = params .get(' minLastSent' ); % min number of days before next
63
+ if ~force && ~test && ~isempty(mod ) && (now - mod < minLastSent )
64
+ fprintf(' Email already sent in last %.2g days\n ' , minLastSent )
65
+ return
66
+ end
67
+
68
+ if send_emails
69
+ % Set email prefs for sending the email
70
+ % TODO These may no longer be required as we use curl
71
+ props = java .lang .System .getProperties ;
72
+ props .setProperty(' mail.smtp.auth' ,' true' );
73
+ props .setProperty(' mail.smtp.port' , num2str(params .get(' SMTP_Port' )));
74
+ props .setProperty(' mail.smtp.starttls.enable' ,' true' );
75
+ % We use MATLAB to send plain text warnings
76
+ internetPrefs = getpref(' Internet' );
77
+ for prop = string(fieldnames(internetPrefs ))'
78
+ setpref(' Internet' , prop , params .get(prop ))
79
+ end
80
+ end
81
+
82
+ if nargin == 0 || isempty(nDays )
83
+ % use 2 for usual weekends, 3 for long weekends etc.
84
+ [nDays , fail ] = ww .getNumDays();
85
+ if fail && ~test
86
+ if send_emails
87
+ recipients = admins ;
88
+ for iRecipient = 1 : numel(recipients )
89
+ sendmail(recipients{iRecipient }, ' Action required: Days may be incorrect' ,...
90
+ [' Weekend water script failed to determine whether there are ' ...
91
+ ' any upcoming Bank holidays. Investigate.' ]);
92
+ end
93
+ end
94
+ % send Slack notification to admins
95
+ recipients = admins_slack ;
96
+ str2send = sprintf(' %s\n%s\n%s ' , ' Action required: Days may be incorrect' ,...
97
+ [' Weekend water script failed to determine whether there are ' ...
98
+ ' any upcoming Bank holidays.' ], ' Investigate.' );
99
+ for iRecipient = 1 : numel(recipients )
100
+ SendSlackNotification(webhook , str2send , recipients{iRecipient }, mySlackName , [], mySlackEmoji )
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ % Alyx instance
107
+ ai = Alyx(' ' ,' ' );
108
+ if test
109
+ ai.BaseURL = params .get(' ALYX_DEV_URL' );
110
+ fprintf(' Using test database: %s\n ' , ai .BaseURL );
111
+ end
112
+ ai = ai .login(params .get(' ALYX_Login' ), params .get(' ALYX_Password' ));
113
+
114
+ % Table of users and their emails from database
115
+ users = ai .getData(' users' );
116
+
117
+ % Extract the data from alyx and give water to whomever needs it
118
+ [data , skipped ] = ww .simulateWeekendWater(ai , nDays , excl );
119
+ if height(data ) == 0 , return , end % Return if there are no restricted mice
120
+
121
+ if ~isempty(skipped ) && ~test
122
+ msg = sprintf([' The following mice have no weekend water information:\n\r %s\n\r ' ,...
123
+ ' This occured because a weight for today was not inputted into Alyx before 6pm. \n ' ,...
124
+ ' Please manually write the weight and water to be given on the paper sheet upstairs. ' ,...
125
+ ' For the days you will be training, please write '' PIL'' .' ], strjoin({skipped .subject }, ' \n ' ));
126
+ [~ ,I ] = intersect({users .username }, {skipped .user });
127
+ % sendmail(vertcat(users(I).email, admins),...
128
+ % 'Action required: Weekend information missing', msg);
129
+ % Single-instance message headers must be included only once in a
130
+ % message (RFC 5322). Multiple recipients to sendmail duplicates 'To'
131
+ % field, so we'll send one email per recipient instead.
132
+ if send_emails
133
+ recipients = vertcat(users(I ).email, admins );
134
+ for iRecipient = 1 : numel(recipients )
135
+ sendmail(recipients{iRecipient }, ' Action required: Weekend information missing' , msg );
136
+ end
137
+ end
138
+ % recipients = vertcat(params.get('Slack_recipients'), admins_slack);
139
+ % for iRecipient = 1:numel(recipients)
140
+ % SendSlackNotification(webhook, sprintf('%s\n%s', 'Action required: Weekend information missing', msg), ...
141
+ % recipients{iRecipient}, mySlackName, [], mySlackEmoji);
142
+ % end
143
+
144
+ end
145
+
146
+ % print nicely, 'water.png' will be saved in the current folder
147
+ slackData = ww .formatSimTable(data , ' txt' );
148
+ msg = sprintf(' %s\n%s ' , ' This is the expected weekend water table for later today.' , ...
149
+ ' You still have time to fix things (weights/PIL/override water amounts)' );
150
+ slackMessage = sprintf(' %s\n ```%s ```' , msg , slackData );
151
+
152
+ data = ww .formatTable(data , params .get(' Email_format' ));
153
+ % Get list of email recipients
154
+ recipients = strip(lower(params .get(' Email_recipients' )));
155
+ % In test mode only send to admin, otherwise send to all recipients and admins
156
+ to = iff(test , admins(1 ), union(recipients , admins ));
157
+
158
+
159
+ %% 'Weekend water',...
160
+ % Write email to file
161
+ fid = fopen(mail , ' w' , ' n' , ' UTF-8' );
162
+ fprintf(fid , [' From: Alyx Database <%s >\n ' ,...
163
+ ' Reply-To: Alyx Database <%s >\n ' ,...
164
+ ' To: %s\n Subject: Weekend Water\n ' ,...
165
+ ' Content-Type: text/html; charset="utf-8"\n ' ,...
166
+ ' Content-Transfer-Encoding: quoted-printable\n ' ,...
167
+ ' Mime-version: 1.0\n\n <!DOCTYPE html><html lang="en-GB"><head>' ,...
168
+ ' <title>Weekend Water Email</title>' ...
169
+ ' </head><body>\n ' ,...
170
+ ' Please find below the water table for this weekend. ' ,...
171
+ ' Any blank spaces must be filled in manually by the respective ' ,...
172
+ ' PILs on the paper copy. Let us know if they fail to do so. \n \r ' ,...
173
+ ' %s\n </body></html>' ],...
174
+ getpref(' Internet' ,' E_mail' ), getpref(' Internet' ,' E_mail' ),...
175
+ strjoin(to , ' , ' ), data );
176
+ fclose(fid );
177
+
178
+ % Construct curl command
179
+ cmd = sprintf([' curl "%s :%i " -v --mail-from "%s " ' ,...
180
+ ' --mail-rcpt "%s " --ssl -u %s :%s -T "%s " -k --anyauth' ],...
181
+ params .get(' SMTP_Server' ), params .get(' SMTP_Port' ), params .get(' E_mail' ), ...
182
+ strjoin(to , ' " --mail-rcpt "' ), getpref(' Internet' ,' E_mail' ),...
183
+ getpref(' Internet' ,' SMTP_Password' ), strrep(mail , ' \' , ' /' ));
184
+
185
+ % Wrap in call to git bash
186
+ gitExe = getOr(dat .paths , ' gitExe' );
187
+ bashPath = fullfile(gitExe(1 : end - 11 ), ' git-bash.exe' );
188
+ bash = @(cmd )[' "' ,bashPath ,' " -c "' ,cmd ,' "' ];
189
+
190
+ if send_emails
191
+ failed = system(bash(cmd ), ' -echo' ); % Send email
192
+ assert(~failed , ' failed to send email' )
193
+
194
+ % Restore previous preferences
195
+ for prop = string(fieldnames(internetPrefs ))'
196
+ setpref(' Internet' , prop , internetPrefs.(prop ))
197
+ end
198
+ end
199
+ % Send Slack message to the channel
200
+ recipients = vertcat(params .get(' Slack_recipients' ), admins_slack );
201
+ % recipients = vertcat(admins_slack);
202
+ for iRecipient = 1 : numel(recipients )
203
+ SendSlackNotification(webhook , slackMessage , recipients{iRecipient }, mySlackName , [], mySlackEmoji )
204
+ end
205
+ % Restore diary state
206
+ if debug , diary(diaryState ), set(0 , ' DiaryFile' , diaryFile ), end
0 commit comments