I have written an indy http server, I want to serve file download with resume support. When I use IDM to download file, the download is single threaded:
Note that the Resume capability is Yes, but When I pause and resume the download, It starts form beginning.
My Indy Http Server is as:
void __fastcall TfrmMain::httpServerCommandGet(TIdContext *AContext,
TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
Beep(1000, 100);
string fileName = ExtractFileDir(Application->ExeName) + ARequestInfo->Document;
fileName = fileName.replace("/", "\\");
TFileStream *stream = new TFileStream(fileName, fmOpenRead | fmShareDenyNone);
int start = 0, end = 0;
string range = ARequestInfo->Range;
if(!range.empty())
{
int dash_pos = range.find("-");
start = range.substr(0, dash_pos).intValue();
end = range.substr(dash_pos + 1).intValue();
if(end == 0) // Endless Range: start-
end = stream->Size;
}
else
{
start = 0;
end = stream->Size;
}
OutputDebugStringW(string("ctx=[] start=[] end=[]") <<
IntToHex((int)AContext, 8) << start << end);
stream->Seek((__int64)start, soBeginning);
AResponseInfo->ContentStream = stream;
AResponseInfo->FreeContentStream = true;
AResponseInfo->ContentLength = stream->Size;
if(!range.empty())
{
AResponseInfo->ContentRangeStart = start;
AResponseInfo->ContentRangeEnd = end;
AResponseInfo->ResponseNo = 206;
AResponseInfo->ContentRangeInstanceLength = end + 1;
AResponseInfo->ContentLength = end - start + 1;
AResponseInfo->AcceptRanges = "bytes";
}
AResponseInfo->WriteHeader();
AResponseInfo->WriteContent();
}
any help would be appreciated.
The IdCustomHTTPServer unit has a TIdHTTPRangeStream helper class for this very purpose.
If the client requests a ranged download, create an instance of TIdHTTPRangeStream and pass it your intended TStream and the client's requested range, then assign it as the ContentStream to be sent. TIdHTTPRangeStream also has a ResponseCode property that you need to assign to the response's ResponseNo property.
For example:
void __fastcall TfrmMain::httpServerCommandGet(TIdContext *AContext, TIdHTTPRequestInfo *ARequestInfo, TIdHTTPResponseInfo *AResponseInfo)
{
// create file stream ...
// THTTPServer parses the ranges for you
if (ARequestInfo->Ranges->Count > 0)
{
if (ARequestInfo->Ranges->Count > 1)
{
AResponseInfo->ResponseNo = 416;
return;
}
TIdEntityRange *range = ARequestInfo->Ranges->Range[0];
TIdHTTPRangeStream *rstream = new TIdHTTPRangeStream(stream, range->StartPos, range->EndPos, true);
AResponseInfo->ResponseNo = rstream->ResponseCode;
AResponseInfo->ContentRangeStart = rstream->RangeStart;
AResponseInfo->ContentRangeEnd = rstream->RangeEnd;
AResponseInfo->ContentStream = rstream;
AResponseInfo->AcceptRanges = "bytes";
}
else
{
AResponseInfo->ContentStream = stream;
}
// no need to seek the target stream manually
// no need to set the ContentLength manually
// no need to call WriteHeader() and WriteContent() manually
//
// TIdHTTPServer handles all that for you internally!
}
Related
Final Update
The solution was to create a TTimer, set its Interval to any value greater than 0, and assign its OnTimer property to an empty function.
I have a TThread that adds new controls to the main form at regular intervals, via Queue(). But the queued functions are never executed until the form receives user input, or the cursor moves over it, and then it executes all the queued functions at once.
With careful logging I have determined conclusively that the functions are being queued as intended by the thread. They simply aren't being executed by the main VCL loop until the form gets user interaction.
It's as if the main application loop doesn't run when there is no user interaction.
How can I force the form to execute queued functions immediately?
If it matters, the form and TThread are created by a .dll, which is called by another .dll, which itself is called by a console application.
Like this:
console application -> dll -> dll created by C++ Builder
Edit
void __fastcall GirkovArpa::Execute() {
while (!Terminated) {
if (GlobalMessageQueue.size() > 0) {
EnterCriticalSection(&myCritSect);
std::cout << ""; // this line is required, else thread won't execute
std::string GlobalMessage = GlobalMessageQueue.at(0);
std::string copy;
copy.assign(GlobalMessage);
GlobalMessageQueue.erase(GlobalMessageQueue.begin());
LeaveCriticalSection(&myCritSect);
Queue([&, copy]() {
// do stuff
});
}
}
}
Edit 2
Node.JS console app => Node DLL addon => C++Builder GUI DLL
My console application is specifically NodeJS. It loads a "NodeJS addon" (a type of DLL), which loads the DLL created with C++ Builder, which exports this function:
void myExportedFunction(const char *str) {
EnterCriticalSection(&myCritSect);
GlobalMessageQueue.push_back(std::string(str));
// CheckSynchronize();
LeaveCriticalSection(&myCritSect);
}
If CheckSynchronize() is not commented out, I get a Segmentation Fault error.
My TThread runs on an infinite loop, checking GlobalMessageQueue, and if it finds it's not empty, it queues a lambda which creates a TControl on the main form.
But the queued lambdas are not executed until the user interacts with the window (simply moving the cursor over the window will suffice).
Edit 3
Here is my full lambda:
Queue([&, copy]() {
std::vector<std::string> words;
boost::split(words, copy, boost::is_any_of(" "));
// CREATE $TControl $Name $Text $Parent
if (words.at(0) == "CREATE") {
if (words.at(1) == "TEXTBOX") {
String formName = stringToString(words.at(4));
TForm *form = getFormByName(formName);
TEdit *textbox = new TEdit(form);
textbox->Parent = form;
textbox->Name = words.at(2).c_str();
textbox->Text = words.at(3).c_str();
textbox->Show();
textbox->OnClick = MyForm->OnClick;
}
if (words.at(1) == "RADIO") {
String formName = stringToString(words.at(4));
TForm *form = getFormByName(formName);
TRadioButton *radio = new TRadioButton(form);
radio->Parent = form;
radio->Name = words.at(2).c_str();
radio->Caption = words.at(3).c_str();
radio->Show();
radio->OnClick = MyForm->OnClick;
}
if (words.at(1) == "BUTTON") {
String formName = stringToString(words.at(4));
TForm *form = getFormByName(formName);
TButton *button = new TButton(form);
button->Parent = form;
button->Name = words.at(2).c_str();
button->Caption = words.at(3).c_str();
button->Show();
button->OnClick = MyForm->OnClick;
}
if (words.at(1) == "FORM") {
createDialog(words.at(2).c_str(), words.at(3).c_str());
}
}
if (words.at(0) == "CHANGE") {
for (int j = 0; j < Screen->FormCount; j++) {
TForm *form = Screen->Forms[j];
if (form->Name == words.at(1).c_str()) {
TRttiContext ctx;
TRttiType *type = ctx.GetType(form->ClassInfo());
TRttiProperty *prop = type->GetProperty(words.at(2).c_str());
TValue value;
if (prop->PropertyType->TypeKind == tkUString) {
value = TValue::From<UnicodeString>(words.at(3).c_str());
} else if (prop->PropertyType->TypeKind == tkInteger) {
value = TValue::From<Integer>(StrToInt(words.at(3).c_str()));
} else {
std::cout << "ERROR" << std::endl;
}
prop->SetValue(form, value);
}
for (int i = 0; i < form->ControlCount; i++) {
TControl *control = form->Controls[i];
if (control->Name == words.at(1).c_str()) {
TRttiContext ctx;
TRttiType *type = ctx.GetType(control->ClassInfo());
TRttiProperty *prop = type->GetProperty(words.at(2).c_str());
TValue value;
if (prop->PropertyType->TypeKind == tkUString) {
value = TValue::From<UnicodeString>(words.at(3).c_str());
} else if (prop->PropertyType->TypeKind == tkInteger) {
value = TValue::From<Integer>(StrToInt(words.at(3).c_str()));
} else {
std::cout << "ERROR" << std::endl;
}
prop->SetValue(control, value);
}
}
}
}
// GET NAME PROP
if (words.at(0) == "GET") {
for (int j = 0; j < Screen->FormCount; j++) {
TForm *form = Screen->Forms[j];
if (form->Name == words.at(1).c_str()) {
TRttiContext ctx;
TRttiType *type = ctx.GetType(form->ClassInfo());
TRttiProperty *prop = type->GetProperty(words.at(2).c_str());
TValue result = prop->GetValue(form);
if (result.Kind == tkUString) {
String leString = result.AsString();
std::wstring w(std::wstring(leString.t_str()));
std::string STR(w.begin(), w.end());
std::string output = words.at(1) + " " + words.at(2);
String o = output.c_str();
tellJavaScript(AnsiString(o + ": " + leString).c_str());
} else if (result.Kind == tkInteger) {
int result_int = result.AsInteger();
String result_String = IntToStr(result_int);
String name = words.at(1).c_str();
String prop = words.at(2).c_str();
tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
} else {
// assume boolean
String result_String = BoolToStr(result.AsBoolean());
String name = words.at(1).c_str();
String prop = words.at(2).c_str();
tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
}
}
for (int i = 0; i < form->ControlCount; i++) {
TControl *control = form->Controls[i];
if (control->Name == words.at(1).c_str()) {
TRttiContext ctx;
TRttiType *type = ctx.GetType(control->ClassInfo());
TRttiProperty *prop = type->GetProperty(words.at(2).c_str());
TValue result = prop->GetValue(control);
if (result.Kind == tkUString) {
String leString = result.AsString();
std::wstring w(std::wstring(leString.t_str()));
std::string STR(w.begin(), w.end());
std::string output = words.at(1) + " " + words.at(2);
String o = output.c_str();
tellJavaScript(AnsiString(o + ": " + leString).c_str());
} else if (result.Kind == tkInteger) {
int result_int = result.AsInteger();
String result_String = IntToStr(result_int);
String name = words.at(1).c_str();
String prop = words.at(2).c_str();
tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
} else {
// assume boolean
String result_String = BoolToStr(result.AsBoolean());
String name = words.at(1).c_str();
String prop = words.at(2).c_str();
tellJavaScript(AnsiString(name + " " + prop + ": " + result_String).c_str());
}
}
}
}
}
if (words.at(0) == "DELETE") {
for (int j = 0; j < Screen->FormCount; j++) {
TForm *form = Screen->Forms[j];
if (form->Name == words.at(1).c_str()) {
form->Close();
}
for (int i = 0; i < form->ControlCount; i++) {
TControl *control = form->Controls[i];
if (control->Name == words.at(1).c_str()) {
control->Free();
}
}
}
}
if (words.at(0) == "EXECUTE") {
for (int j = 0; j < Screen->FormCount; j++) {
TForm *form = Screen->Forms[j];
if (form->Name == words.at(1).c_str()) {
std::cout << "EXECUTE <<" << words.at(2) << ">>" << std::endl;
TRttiContext context;
TRttiType *rttiType = context.GetType(form->ClassType());
TRttiMethod *method = rttiType->GetMethod(words.at(2).c_str());
DynamicArray<TRttiParameter *> parameters = method->GetParameters();
TValue args[10];
if (parameters.Length) {
for (int y = parameters.Low; y <= parameters.High; y++) {
String paramType = parameters[y]->ParamType->ToString();
if (paramType == "UnicodeString") {
args[y] = TValue::From<UnicodeString>(stringToString(words.at(y + 3)));
} else if (paramType == "Integer") {
args[y] = TValue::From<Integer>(StrToInt(stringToString(words.at(y + 3))));
}
}
TValue value = method->Invoke(form, args, parameters.High);
} else {
TValue value = method->Invoke(form, NULL, -1);
}
}
for (int i = 0; i < form->ControlCount; i++) {
TControl *control = form->Controls[i];
if (control->Name == words.at(1).c_str()) {
std::cout << "EXECUTE <<" << words.at(2) << ">>" << std::endl;
TRttiContext context;
TRttiType *rttiType = context.GetType(control->ClassType());
TRttiMethod *method = rttiType->GetMethod(words.at(2).c_str());
DynamicArray<TRttiParameter *> parameters = method->GetParameters();
TValue args[10];
if (parameters.Length) {
for (int y = parameters.Low; y <= parameters.High; y++) {
String paramType = parameters[y]->ParamType->ToString();
if (paramType == "UnicodeString") {
args[y] = TValue::From<UnicodeString>(stringToString(words.at(y + 3)));
} else if (paramType == "Integer") {
args[y] = TValue::From<Integer>(StrToInt(stringToString(words.at(y + 3))));
}
}
TValue value = method->Invoke(control, args, parameters.High);
} else {
TValue value = method->Invoke(control, NULL, -1);
}
}
}
}
}
});
It's as if the main application loop doesn't run when there is no user interaction.
It doesn't, actually. Well, more accurately, when there is no pending window messages at all. Once the main thread's message queue is emptied, the VCL calls the Win32 WaitMessage() function, which blocks the calling thread until a new message appears in the message queue. Even traditional non-VCL message loops tend to block the calling thread when there are no messages to process.
How can I force the form to execute queued functions immediately?
You can't force it.
If it matters, the form and TThread are created by a .dll, which is called by another .dll, which itself is called by a console application.
It DOES matter, because TThread::Queue() and TThread:::Synchronize() DO NOT work well inside of a DLL.
TThread::Queue() and TThread::Synchronize() put their requests into an internal queue inside the RTL, set a signal to indicate the queue has pending requests, and then post a message to the TApplication window to "wake up" the main thread (in case the message loop is "sleeping" waiting for a new message to arrive). That request queue is processed at the main thread's earliest convenience.
By default, a VCL message loop processes the TThread queue only when:
the message loop enters an idle state, after all pending messages have been processed and the message queue becomes empty.
the TApplication window receives the "wake up" message.
When the TThread queue is inside a DLL, and the DLL is not sharing the same RTL instance with the main EXE, then the main message loop in the EXE does not know about the TThread queue in the DLL, so it can't process the pending requests during idle times. That just leaves the "wake up" message, which the DLL will post to its own TApplication instance, not to the main EXE's TApplication. The main thread message loop will still dispatch window messages to a DLL's TApplication window.
To solve this, you will have to either:
enable Runtime Packages in the DLL and main EXE, or even change the DLL to be a Package, so that they can share common instances of the RTL and VCL. That does mean you will have to deploy the RTL and VCL .bpl files with your app, though.
export a function from your DLL that calls the RTL's CheckSynchronize() function, and then call that DLL function in your EXE code periodically, such as in a UI timer, or in the TApplication.OnIdle event, etc.
You're correct that the main loop of a typical Windows program does not run until there's some sort of input (usually user input, but there are other kinds as well).
I'm not familiar with the C++ Builder framework.
If you have control of code that makes the main loop, you can modify it to process additional sources of information, such as watching for another thread to signal a synchronization object.
Other options:
Have the thread that's adding items to the queue post a custom message to the main thread's window (or just a thread message) whenever it does one of its regular updates.
Set up a timer on the main thread. It will periodically "wake up" the main loop, just as the user input would.
I used successfully to post a form to my webserver using MFC's function CHttpFile::SendRequest. The server respond success with return code 200.
The problem is that I want to get more data from the server like a custom process code or a custom string code. For example pFile->ReadString(str)
gives me the chance to read something but what ?
How to setup my server to send back to caller such informations ? My server runs apache.
The Read/ReadString below returns anything you want to return. Just decode each line you receive from the server.
pFile->ReadString(str)
CString MyClass::PostWebFormActivation() {
CString strHeaders;
CInternetSession session;
CString strFormData;
CHttpConnection *pConnection;
CHttpFile *pFile;
BOOL result;
DWORD dwRet;
CString strServerResponse;
CString strExceptionError;
INTERNET_PORT nPort(80);
CString strServer;
CString strObject;
CString strSubmitValue = _T("1");
CString strLine;
CString strTemp;
INT pos;
CUtils util;
// URL-encoded form variables -
// serial = "xxxxxxx", systemcode = "xxxxx", username = "xxxxxxx", restoreemail = "xxxxxxx". strCAPTCHA = "Xyz!"
strFormData = _T(SUBMIT_FORM) + _T(strSubmitValue)+ _T(SERIAL_FIELD) + _T(m_strSerial) + _T(SYSTEMCODE_FIELD) + _T(m_strSystemCode) + _T(USERNAME_FIELD) + _T(m_strUsername) + _T(RESTORE_EMAIL_FIELD) + _T(m_strRestoreEmail) + _T(CAPTCHA_FIELD);
pConnection = NULL;
pFile = NULL;
//I can use the AfxParseURL instead of the following code
strServer = ((CMainFrame *)AfxGetMainWnd())->m_strWebActivateProductURL;
strTemp = strServer;
strTemp.MakeUpper();
pos = strTemp.Find(_T("HTTP://"));
if (pos != -1) {
strServer = strServer.Mid(7); // remove http://
}
pos = strServer.Find('/',0);
strObject = strServer.Mid(pos); //the path after the domain ie /dir/index.php
strServer = strServer.Left(pos);//server is the actual domain ie www.mywebsite.gr
if (util.CheckURLFileExtension(strObject) == _T("") ) {
//folder
if (strObject.Find('/',strObject.GetLength()) == -1) {
strObject += _T("/?");
}
} else {
//file
strObject += _T("?");
}
try {
pConnection = session.GetHttpConnection( strServer , nPort);
pFile = pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST /*HTTP_VERB_GET*/,strObject /*_T("/paradox4a/activate/?") or _T("/paradox4a/activate/index.php?")*/);
//pFile->AddRequestHeaders(szHeaders); //not needed.
strHeaders = _T("Content-Type: application/x-www-form-urlencoded\r\n");
result = pFile->SendRequest(strHeaders, (LPVOID) (LPCTSTR) strFormData, strFormData.GetLength());
if (result > 0) {
pFile->QueryInfoStatusCode(dwRet);
strServerResponse.Empty();
if (dwRet == HTTP_STATUS_OK) {
while(pFile->ReadString(strLine)) {
strServerResponse += strLine + "\r\n";
}
} else {
strServerResponse = _T("ERROR");
AMLOGINFO(_T("Trying to post a form to server for activation procedure, got communication error [%d]" ), dwRet);
}
} else {
strServerResponse = _T("ERROR");
AMLOGINFO(_T("Trying to post a form to server for activation procedure, got communication error [%d]" ), result );
}
} catch (CInternetException* pEx) {
TCHAR sz[1024];
CString s = util.getInetError(pEx->m_dwError);
strServerResponse = _T("ERROR");
pEx->GetErrorMessage( sz,1024 );
pEx->Delete();
AMLOGINFO(_T("Trying to post a form to server for activation procedure, got network error [%s]" ), strExceptionError );
}
if (pFile) {
pFile->Close();
delete pFile;
}
if (pConnection) {
pConnection->Close();
delete pConnection;
}
return strServerResponse;
}
I have a form with a progress bar and a button that uploads an xml to a server.
While the button is pressed a new thread is created that creates a socket and then it sends the data to the server in chunks and in the meanwhile it updates the progress bar.
Now, when the upload button is pressed for a second time i get an access violation and in the debugger the address of the Progress Bar object is NULL.
I can't figure out why the Progress Bar is getting freed so if anyone has any idea i would be grateful.
P.S. The target OS is windows
P.S.2 If the same code runs on the main thread without the usage of a thread then i don't seem to have this issue and even if i skip the usage of the progress bar in overall in the thread it is set to null again after the first push of the upload button.
The thread Constructor:
__fastcall UploadRouteThread::UploadRouteThread(bool CreateSuspended) : TThread(CreateSuspended)
{
this->OnTerminate = OnTerminateHandler;
ioHandlerStack = new TIdIOHandlerStack();
tcpClient = new TIdTCPClient();
tcpClient->ReadTimeout = -1;
tcpClient->UseNagle = true;
tcpClient->IOHandler = ioHandlerStack;
tcpClient->OnConnected = OnConnectedHandler;
}
The OnTerminate handler:
void __fastcall UploadRouteThread::OnTerminateHandler(TObject *Sender)
{
TabbedwithNavigationForm->UploadButton->Text = "Upload";
TabbedwithNavigationForm->UploadButton->Enabled = false;
TabbedwithNavigationForm->ProgressBar->Visible = false;
tcpClient->DisconnectNotifyPeer();
ShowMessage("Data uploaded.");
delete ioHandlerStack;
delete tcpClient;
TabbedwithNavigationForm->OptionButton->Enabled = true;
TabbedwithNavigationForm->RetrieveRoutesButton->Enabled = true;
TabbedwithNavigationForm->TrackButton->Enabled = true;
TabbedwithNavigationForm->MediaButton->Enabled = true;
}
The Execute method:
void __fastcall UploadRouteThread::Execute()
{
FreeOnTerminate = true;
tcpClient->Connect();
}
Two supplumentary functions:
void __fastcall UploadRouteThread::SetHostPort(UnicodeString host, unsigned short port)
{
tcpClient->Host = host;
tcpClient->Port = port;
}
void __fastcall UploadRouteThread::SetXML(AnsiString xmlString)
{
this->xmlString = xmlString;
}
The OnConnect Handler:
void __fastcall UploadRouteThread::OnConnectedHandler(TObject *Sender)
{
NextPacketSize nps;
TIdBytes bytes;
int chunks;
int bytesLength;
nps.PacketID = BasicPacket::DATA_UPLOAD;
nps.size = xmlString.Length();
tcpClient->IOHandler->WriteDirect(RawToBytes(&nps, sizeof(nps)), sizeof(NextPacketSize));
bytes = RawToBytes(xmlString.c_str(), xmlString.Length());
bytesLength = bytes.get_length();
chunks = ceil(float(bytesLength) / 256.0);
int previousSizeSent(0);
for(int i = 1; i <= chunks; i++)
{
if(Terminated)
break;
int bytesToSend = 256;
TByteDynArray byteDynArray;
if((bytesToSend > bytesLength))
{
bytesToSend = bytesLength;
}
byteDynArray = bytes.CopyRange(previousSizeSent, bytesToSend);
tcpClient->IOHandler->WriteDirect(ToBytes(byteDynArray, byteDynArray.get_length(), 0),
byteDynArray.get_length());
sent = (float(i) / float(chunks)) * 100;
TThread::Synchronize(this, UpdateProgressBarInternal);
previousSizeSent += bytesToSend;
bytesLength -= bytesToSend;
}
}
And the Update method for the progress bar:
void __fastcall UploadRouteThread::UpdateProgressBarInternal()
{
if(!TabbedwithNavigationForm->ProgressBar->Visible)
{
TabbedwithNavigationForm->ProgressBar->Visible = true;
TabbedwithNavigationForm->ProgressBar->Max = 100;
}
TabbedwithNavigationForm->ProgressBar->Value = sent;
}
I don't see anything in this code that would cause the ProgressBar pointer to become NULL. So either you are corrupting memory, or something else in other code not shown here is the culprit. Either way, to troubleshoot this you can run your app in the IDE debugger and set a Data Breakpoint on the ProgressBar variable before you run your thread for the first time. If something changes the value of that pointer, the breakpoint will be hit, and you can look at the call stack to figure out what is happening.
With that said, your thread is not very well organized. And there is a much simpler way to handle the chunking - let Indy do it for you. It has an OnWork event that you can use for your ProgressBar updates.
Try something more like this:
__fastcall UploadRouteThread::UploadRouteThread(String host, TIdPort port, AnsiString xmlString)
: TThread(false)
{
this->FreeOnTerminate = true;
this->OnTerminate = OnTerminateHandler;
this->xmlString = xmlString;
tcpClient = new TIdTCPClient();
tcpClient->Host = host;
tcpClient->Port = port;
tcpClient->UseNagle = true;
tcpClient->OnWork = OnWorkHandler;
}
__fastcall UploadRouteThread::~UploadRouteThread()
{
delete tcpClient;
}
void __fastcall UploadRouteThread::OnTerminateHandler(TObject *Sender)
{
TabbedwithNavigationForm->UploadButton->Text = "Upload";
TabbedwithNavigationForm->UploadButton->Enabled = false;
TabbedwithNavigationForm->ProgressBar->Visible = false;
if (FatalException)
ShowMessage("Data not uploaded.");
else
ShowMessage("Data uploaded.");
TabbedwithNavigationForm->OptionButton->Enabled = true;
TabbedwithNavigationForm->RetrieveRoutesButton->Enabled = true;
TabbedwithNavigationForm->TrackButton->Enabled = true;
TabbedwithNavigationForm->MediaButton->Enabled = true;
}
void __fastcall UploadRouteThread::OnWorkHandler(TObject *ASender, TWorkMode AWorkMode, __int64 AWorkCount)
{
if (Terminated)
Sysutils::Abort();
sent = (double(AWorkCount) * 100.0) / xmlString.Length();
// consider using TThread::Queue() instead so that you don't block
// the upload waiting for the UI to be updated...
TThread::Synchronize(this, &UpdateProgressBarInternal);
}
void __fastcall UploadRouteThread::Execute()
{
tcpClient->Connect();
try
{
NextPacketSize nps;
nps.PacketID = BasicPacket::DATA_UPLOAD;
nps.size = xmlString.Length();
tcpClient->IOHandler->Write(RawToBytes(&nps, sizeof(nps)));
tcpClient->BeginWork(wmWrite, xmlString.Length());
tcpClient->IOHandler->Write(RawToBytes(xmlString.c_str(), xmlString.Length()));
tcpClient->EndWork(wmWrite);
/* alternatively:
TIdMemoryBufferStream *strm = new TIdMemoryBufferStream(xmlString.c_str(), xmlString.Length());
try
{
// optional
tcpClient->IOHandler->SendBufferSize = 256;
// this calls (Begin|End)Work() internally...
tcpClient->IOHandler->Write(strm, 0, false);
}
__finally
{
delete strm;
}
*/
}
__finally
{
tcpClient->Disconnect();
}
}
void __fastcall UploadRouteThread::UpdateProgressBarInternal()
{
if (!TabbedwithNavigationForm->ProgressBar->Visible)
{
TabbedwithNavigationForm->ProgressBar->Visible = true;
TabbedwithNavigationForm->ProgressBar->Max = 100;
}
TabbedwithNavigationForm->ProgressBar->Value = sent;
}
From the API page, I gather there's no function for what I'm trying to do. I want to read text from a file storing it as a list of strings, manipulate the text, and save the file. The first part is easy using the function:
abstract List<String> readAsLinesSync([Encoding encoding = Encoding.UTF_8])
However, there is no function that let's me write the contents of the list directly to the file e.g.
abstract void writeAsLinesSync(List<String> contents, [Encoding encoding = Encoding.UTF_8, FileMode mode = FileMode.WRITE])
Instead, I've been using:
abstract void writeAsStringSync(String contents, [Encoding encoding = Encoding.UTF_8, FileMode mode = FileMode.WRITE])
by reducing the list to a single string. I'm sure I could also use a for loop and feed to a stream line by line. I was wondering two things:
Is there a way to just hand the file a list of strings for writing?
Why is there a readAsLinesSync but no writeAsLinesSync? Is this an oversight or a design decision?
Thanks
I just made my own export class that handles writes to a file or for sending the data to a websocket.
Usage:
exportToWeb(mapOrList, 'local', 8080);
exportToFile(mapOrList, 'local/data/data.txt');
Class:
//Save data to a file.
void exportToFile(var data, String filename) =>
new _Export(data).toFile(filename);
//Send data to a websocket.
void exportToWeb(var data, String host, int port) =>
new _Export(data).toWeb(host, port);
class _Export {
HashMap mapData;
List listData;
bool isMap = false;
bool isComplex = false;
_Export(var data) {
// Check is input is List of Map data structure.
if (data.runtimeType == HashMap) {
isMap = true;
mapData = data;
} else if (data.runtimeType == List) {
listData = data;
if (data.every((element) => element is Complex)) {
isComplex = true;
}
} else {
throw new ArgumentError("input data is not valid.");
}
}
// Save to a file using an IOSink. Handles Map, List and List<Complex>.
void toFile(String filename) {
List<String> tokens = filename.split(new RegExp(r'\.(?=[^.]+$)'));
if (tokens.length == 1) tokens.add('txt');
if (isMap) {
mapData.forEach((k, v) {
File fileHandle = new File('${tokens[0]}_k$k.${tokens[1]}');
IOSink dataFile = fileHandle.openWrite();
for (var i = 0; i < mapData[k].length; i++) {
dataFile.write('${mapData[k][i].real}\t'
'${mapData[k][i].imag}\n');
}
dataFile.close();
});
} else {
File fileHandle = new File('${tokens[0]}_data.${tokens[1]}');
IOSink dataFile = fileHandle.openWrite();
if (isComplex) {
for (var i = 0; i < listData.length; i++) {
listData[i] = listData[i].cround2;
dataFile.write("${listData[i].real}\t${listData[i].imag}\n");
}
} else {
for (var i = 0; i < listData.length; i++) {
dataFile.write('${listData[i]}\n');
}
}
dataFile.close();
}
}
// Set up a websocket to send data to a client.
void toWeb(String host, int port) {
//connect with ws://localhost:8080/ws
//for echo - http://www.websocket.org/echo.html
if (host == 'local') host = '127.0.0.1';
HttpServer.bind(host, port).then((server) {
server.transform(new WebSocketTransformer()).listen((WebSocket webSocket) {
webSocket.listen((message) {
var msg = json.parse(message);
print("Received the following message: \n"
"${msg["request"]}\n${msg["date"]}");
if (isMap) {
webSocket.send(json.stringify(mapData));
} else {
if (isComplex) {
List real = new List(listData.length);
List imag = new List(listData.length);
for (var i = 0; i < listData.length; i++) {
listData[i] = listData[i].cround2;
real[i] = listData[i].real;
imag[i] = listData[i].imag;
}
webSocket.send(json.stringify({"real": real, "imag": imag}));
} else {
webSocket.send(json.stringify({"real": listData, "imag": null}));
}
}
},
onDone: () {
print('Connection closed by client: Status - ${webSocket.closeCode}'
' : Reason - ${webSocket.closeReason}');
server.close();
});
});
});
}
}
I asked Mads Agers about this. He works on the io module. He said that he decided not to add writeAsLines because he didn't find it useful. For one it is trivial to write the for loop and the other thing is that you have to parameterize it which the kind of line separator that you want to use. He said he can add it if there is a strong feeling that it would be valuable. He didn't immediately see a lot of value in it.
I have the following code which is used to Push and Pend from a queue. The caller code has multiple MsgQ objects. It is possible that the Push and the Pend functions are waiting on the _notFull->wait() and the _notEmpty->wait() conditional waits. These waits are protected by the _mut mutex. The notFull and the notEmpty waits operate on the empty and full variables.
When the destructor is called, the _deleteQueue is called internally, from which I would like to signal to the waiting threads to cleanup and stop waiting for a signal to come. Once that is done, I delete my objects. However, in the _deleteQueue function, when I attempt to do _mut->acquire(), I am unable to acquire the mutex. Even if I ignore the acquire, I am unable to broadcast to these waiting threads. Where am I going wrong?
Thanks,
Vikram.
MsgQ::~MsgQ()
{
_deleteQueue();
delete _mut;_mut=NULL;
delete _notFull;_notFull=NULL;
delete _notEmpty;_notEmpty=NULL;
delete _PostMutex; _PostMutex = NULL;
delete _PendMutex; _PendMutex = NULL;
delete _PostInProgressMutex; _PostInProgressMutex = NULL;
delete _PendInProgressMutex; _PendInProgressMutex = NULL;
delete _DisconnectMutex; _DisconnectMutex = NULL;
free( _ptrQueue ); _ptrQueue = NULL;
}
int MsgQ::Post(Message* msg)
{
_PostMutex->acquire();
_postInProgress++;
_PostMutex->release();
if (msg)
msg->print();
_mut->acquire();
while (full)
{
_notFull->wait();
}
if (!_disconnectInProgress)
_queuePush(msg);
_mut->release();
_PostMutex->acquire();
_postInProgress--;
if (_postInProgress==0)
{
_PostInProgressMutex->signal();
}
_PostMutex->release();
return _notEmpty->signal();
}
int MsgQ::Pend(Message*& msg)
{
_PendMutex->acquire();
_pendInProgress++;
_PendMutex->release();
_mut->acquire();
while (empty)
_notEmpty->wait();
if (!_disconnectInProgress)
{
_queuePop(msg);
}
_mut->release();
_PendMutex->acquire();
_pendInProgress--;
if (_pendInProgress == 0)
{
_PendInProgressMutex->signal();
}
_PendMutex->release();
return _notFull->signal();
}
void MsgQ::_deleteQueue ()
{
_PostMutex->acquire();
if (_postInProgress != 0)
{
_PostMutex->release();
TRACE("Acquiring Mutex.");
_mut->acquire();
full = 0;
_notFull->broadcast();
_mut->release();
_PostInProgressMutex->wait();
}
else
{
_PostMutex->release();
}
_PendMutex->acquire();
if (_pendInProgress != 0)
{
_PendMutex->release();
TRACE("Acquiring Mutex.");
_mut->acquire();
empty = 0;
_notEmpty->broadcast();
_mut->release();
_PendInProgressMutex->wait();
}
else
{
_PendMutex->release();
}
}
void MsgQ::_initQueue()
{
_ptrQueue = (Message **)(malloc (size * sizeof (Message*)));
if (_ptrQueue == NULL)
{
cout << "queue could not be created!" << endl;
}
else
{
for (int i = 0; i < size; i++)
*(_ptrQueue + i) = NULL;
empty = 1;
full = 0;
head = 0;
tail = 0;
try{
_mut = new ACE_Mutex() ;
_notFull = new ACE_Condition<ACE_Mutex>(*_mut);
_notEmpty = new ACE_Condition<ACE_Mutex>(*_mut);
_PostMutex = new ACE_Mutex();
_PendMutex = new ACE_Mutex();
_PostInProgressMutex = new ACE_Condition<ACE_Mutex>(*_PostMutex);
_PendInProgressMutex = new ACE_Condition<ACE_Mutex>(*_PendMutex);
_DisconnectMutex = new ACE_Mutex();
_postInProgress = 0;
_pendInProgress = 0;
_disconnectInProgress = false;
}catch(...){
cout << "you should not be here" << endl;
}
}
}
There seem to be many problems with the code so I would suggest reworking it:
You have a potential for deadlock because you are acquiring _mut before you go into a wait condition in both Post and Pend functions.
Instead of using acquire and release on a ACE_Mutex I would suggest looking at using ACE_Guard class which can acquire mutex when created and release it when destroyed.
Why not use ACE_Message_Queue instead of creating your own?