kpmpgsmkii/client/src/file.c

297 lines
8.2 KiB
C

/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "network.h"
#include "packet.h"
#include "array.h"
#include "taglist.h"
#include "window.h"
#include "label.h"
#include "timer.h"
#include "msgbox.h"
#include "file.h"
#include "hangup.h"
#include "welcome.h"
// All this queue nonsense allows you to request more
// downloads while downloads are currently running.
typedef struct FileListS {
char **files;
fileCallback callback;
} FileListT;
static uint8_t _channel = 0;
static uint8_t _dialogVisible = 0;
static FileListT **_fileList = NULL;
static FileListT *_current = NULL;
static uint32_t _currentLength = 0;
static char *_currentSha256 = NULL;
static char *_file = NULL;
static FILE *_handle = NULL;
static WindowT *_winFile = NULL;
static LabelT *_lblFile = NULL;
static TimerT *_timTimer = NULL;
static void btnMsgBoxOkay(MsgBoxButtonT button);
static void fileCheckNext(void);
static void fileShowDialog(void);
static void fileShowError(char *message);
static void packetHandler(PacketDecodeDataT *packet);
static void timTimerTimeout(WidgetT *widget);
static void btnMsgBoxOkay(MsgBoxButtonT button) {
(void)button;
hangupShow(welcomeShow);
}
void fileCacheCheck(fileCallback callback, char *vpaths[]) {
FileListT *newList = NULL;
uint16_t i = 0;
// Add new entries to anything already in the queue.
NEW(FileListT, newList);
newList->callback = callback;
newList->files = NULL;
while (vpaths[i] != NULL) {
arrput(newList->files, strdup(vpaths[i]));
i++;
}
arrput(_fileList, newList);
// New queue?
if (_channel == 0) {
_dialogVisible = 0;
_channel = netChannelGet(packetHandler);
}
fileCheckNext();
}
static void fileCheckNext(void) {
PacketEncodeDataT encoded = { 0 };
uint8_t *packetData = NULL;
uint16_t length = 0;
uint32_t temp = 0;
uint8_t doRecheck = 0;
char *shaPointer = NULL;
static char *badSHA = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
do {
// This is ugly since both lists kind of depend on each other.
doRecheck = 0;
// Do we need a new entry from the queue?
if (_current == NULL) {
// End of queue?
if (arrlen(_fileList) == 0) {
logWrite("End of queue.\n");
arrfree(_fileList);
if (_dialogVisible) guiDelete(D(_winFile));
netChannelRelease(_channel);
return;
}
// Get next queue entry.
logWrite("Next queue entry.\n");
_currentLength = 0;
_current = _fileList[0];
arrdel(_fileList, 0);
}
if (_file) DEL(_file);
// End of current queue file list?
if (arrlen(_current->files) == 0) {
logWrite("End of files.\n");
// End of list!
arrfree(_current->files);
_current->callback();
DEL(_current);
// See if there's more.
doRecheck = 1;
} else {
// No. Get next file.
logWrite("Next file.\n");
_file = _current->files[0];
arrdel(_current->files, 0);
}
} while (doRecheck);
// Check next file.
temp = FILE_REQUEST_CHECK;
shaPointer = cacheSha256Get(_file);
if (!shaPointer) shaPointer = badSHA;
packetData = packetContentPack(&length, "iss", temp, shaPointer, _file);
logWrite("Checking [%d] [%s] [%s]\n", temp, shaPointer, _file);
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_FILE_REQUEST;
encoded.channel = _channel;
encoded.encrypt = 0;
packetEncode(__packetThreadData, &encoded, packetData, length);
packetSend(__packetThreadData, &encoded);
DEL(packetData);
}
static void fileShowDialog(void) {
TagItemT uiFile[] = {
T_START,
T_WINDOW, O(_winFile),
T_TITLE, P("Updating Cache"),
T_WIDTH, 200, T_HEIGHT, 100,
T_LABEL, O(_lblFile),
T_X, 41, T_Y, 25,
T_TITLE, P("Downloading..."),
T_LABEL, T_DONE,
T_TIMER, O(_timTimer),
T_EVENT, P(timTimerTimeout), // ***TODO*** This should probably be placed on the desktop so it can run while the first file is checked.
T_VALUE, 4 * 5,
T_ENABLED, 1,
T_TIMER, T_DONE,
T_WINDOW, T_DONE,
T_END
};
tagListRun(uiFile);
_dialogVisible = 1;
}
static void fileShowError(char *message) {
// ***TODO*** This should close all open windows.
// Show error. This is fatal.
if (_dialogVisible) guiDelete(D(_winFile));
netChannelRelease(_channel);
msgBoxOne("Uh Oh!", MSGBOX_ICON_QUESTION, message, "Okay", btnMsgBoxOkay);
}
static void packetHandler(PacketDecodeDataT *packet) {
FileResponseT response = 0;
PacketEncodeDataT encoded = { 0 };
uint8_t *packetData = NULL;
uint16_t length = 0;
uint32_t temp = 0;
if (packet->packetType == PACKET_TYPE_FILE_RESPONSE) {
// Extract the response type. We get more data later.
packetContentUnpack(packet->data, "i", &response);
switch (response) {
case FILE_RESPONSE_UNKNOWN:
logWrite("Unknown file transfer response.\n");
fileShowError("Unknown file transfer response.");
break;
case FILE_RESPONSE_OKAY:
// This file is already up-to-date, move on to next.
logWrite("Got FILE_RESPONSE_OKAY\n");
fileCheckNext();
break;
case FILE_RESPONSE_SEND:
// Start of new file. Get SHA and length.
logWrite("Got FILE_RESPONSE_SEND\n");
packetContentUnpack(packet->data, "iis", &response, &_currentLength, &_currentSha256);
break;
case FILE_RESPONSE_DATA:
logWrite("Got FILE_RESPONSE_DATA\n");
// Receive new file data.
if (!_dialogVisible) fileShowDialog();
// Are we starting a new file?
if (_handle == NULL) {
logWrite("Opening [%s]\n", _file);
_handle = cacheFOpen(_file, "wb");
if (!_handle) {
fileShowError("Unable to write to cache.");
break;
}
}
// Write data to file, skipping first integer stored in packet.
logWrite("Writing %d bytes to [%s]\n", packet->length - 4, _file);
fwrite(&packet->data[4], packet->length - 4, 1, _handle);
// Is this file complete?
if (ftell(_handle) >= _currentLength) {
logWrite("Closing [%s]\n", _file);
// Close this file.
cacheFClose(_handle);
_handle = NULL;
// Update cache entry to include SHA.
cacheEntryAdd(_currentSha256, cacheEntryNameGet(_file), _file);
// Next file!
fileCheckNext();
} else {
logWrite("Sending FILE_REQUEST_NEXT\n");
// Tell the server we got this bit of data, send the next.
temp = FILE_REQUEST_NEXT;
packetData = packetContentPack(&length, "i", temp);
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_FILE_REQUEST;
encoded.channel = _channel;
encoded.encrypt = 0;
packetEncode(__packetThreadData, &encoded, packetData, length);
packetSend(__packetThreadData, &encoded);
DEL(packetData);
}
// Reset timeout timer.
timerReset(_timTimer);
break;
}
}
packetDecodeDataDestroy(&packet);
}
static void timTimerTimeout(WidgetT *widget) {
uint8_t missing = 0;
(void)widget;
// Download timed out.
timerStop(_timTimer);
// If we at least have some version of the file, try to continue.
while (arrlen(_fileList) > 0) {
while (arrlen(_fileList[0]->files) > 0) {
if (cacheFilenameGet(_fileList[0]->files[0])) {
arrdel(_fileList[0]->files, 0);
} else {
missing = 1;
}
}
// If we're good so far, call the callback.
if (!missing) _fileList[0]->callback();
arrfree(_fileList[0]->files);
arrdel(_fileList, 0);
}
arrfree(_fileList);
if (missing) fileShowError("Unable to download needed files.");
}