2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
6 // Any parts of this program derived from the xMule, lMule or eMule project,
7 // or contributed by third-party developers are copyrighted by their
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 2 of the License, or
13 // (at your option) any later version.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 // This file is for functions common to all three apps (amule, amuled, amulegui),
27 // but preprocessor-dependent (using theApp, thePrefs), so it is compiled seperately for each app.
32 #include <wx/cmdline.h> // Needed for wxCmdLineParser
33 #include <wx/snglinst.h> // Needed for wxSingleInstanceChecker
34 #include <wx/textfile.h> // Needed for wxTextFile
35 #include <wx/config.h> // Do_not_auto_remove (win32)
36 #include <wx/fileconf.h>
38 #include "amule.h" // Interface declarations.
39 #include <common/Format.h> // Needed for CFormat
40 #include "CFile.h" // Needed for CFile
41 #include "ED2KLink.h" // Needed for command line passing of links
42 #include "FileLock.h" // Needed for CFileLock
43 #include "GuiEvents.h" // Needed for Notify_*
44 #include "KnownFile.h"
46 #include "MagnetURI.h" // Needed for CMagnetURI
47 #include "Preferences.h"
48 #include "ScopedPtr.h"
49 #include "MuleVersion.h" // Needed for GetMuleVersion()
52 #include "DownloadQueue.h"
55 CamuleAppCommon::CamuleAppCommon()
57 m_singleInstance
= NULL
;
59 m_geometryEnabled
= false;
61 m_appName
= wxT("aMuleGUI");
62 m_configFile
= wxT("remote.conf");
63 m_logFile
= wxT("remotelogfile");
65 m_configFile
= wxT("amule.conf");
66 m_logFile
= wxT("logfile");
69 m_appName
= wxT("aMuleD");
71 m_appName
= wxT("aMule");
76 CamuleAppCommon::~CamuleAppCommon()
78 #if defined(__WXMAC__) && defined(AMULE_DAEMON)
79 //#warning TODO: fix wxSingleInstanceChecker for amuled on Mac (wx link problems)
81 delete m_singleInstance
;
85 void CamuleAppCommon::RefreshSingleInstanceChecker()
87 #if defined(__WXMAC__) && defined(AMULE_DAEMON)
88 //#warning TODO: fix wxSingleInstanceChecker for amuled on Mac (wx link problems)
90 delete m_singleInstance
;
91 m_singleInstance
= new wxSingleInstanceChecker(wxT("muleLock"), thePrefs::GetConfigDir());
95 void CamuleAppCommon::AddLinksFromFile()
97 const wxString fullPath
= thePrefs::GetConfigDir() + wxT("ED2KLinks");
98 if (!wxFile::Exists(fullPath
)) {
102 // Attempt to lock the ED2KLinks file.
103 CFileLock
lock((const char*)unicode2char(fullPath
));
105 wxTextFile
file(fullPath
);
107 for ( unsigned int i
= 0; i
< file
.GetLineCount(); i
++ ) {
108 wxString line
= file
.GetLine( i
).Strip( wxString::both
);
110 if ( !line
.IsEmpty() ) {
111 // Special case! used by a secondary running mule to raise this one.
112 if (line
== wxT("RAISE_DIALOG")) {
116 unsigned long category
= 0;
117 if (line
.AfterLast(wxT(':')).ToULong(&category
) == true) {
118 line
= line
.BeforeLast(wxT(':'));
119 } else { // If ToULong returns false the category still can have been changed!
120 // This is fixed in wx 2.9
123 theApp
->downloadqueue
->AddLink(line
, category
);
129 AddLogLineNS(_("Failed to open ED2KLinks file."));
133 wxRemoveFile(thePrefs::GetConfigDir() + wxT("ED2KLinks"));
137 // Returns a magnet ed2k URI
138 wxString
CamuleAppCommon::CreateMagnetLink(const CAbstractFile
*f
)
142 uri
.AddField(wxT("dn"), f
->GetFileName().Cleanup(false).GetPrintable());
143 uri
.AddField(wxT("xt"), wxString(wxT("urn:ed2k:")) + f
->GetFileHash().Encode().Lower());
144 uri
.AddField(wxT("xt"), wxString(wxT("urn:ed2khash:")) + f
->GetFileHash().Encode().Lower());
145 uri
.AddField(wxT("xl"), CFormat(wxT("%d")) % f
->GetFileSize());
147 return uri
.GetLink();
151 // Returns a ed2k file URL
152 wxString
CamuleAppCommon::CreateED2kLink(const CAbstractFile
*f
, bool add_source
, bool use_hostname
, bool add_cryptoptions
, bool add_AICH
)
154 wxASSERT(!(!add_source
&& (use_hostname
|| add_cryptoptions
)));
155 // Construct URL like this: ed2k://|file|<filename>|<size>|<hash>|/
156 wxString strURL
= CFormat(wxT("ed2k://|file|%s|%i|%s|"))
157 % f
->GetFileName().Cleanup(false)
158 % f
->GetFileSize() % f
->GetFileHash().Encode();
160 // Append the AICH info
162 const CKnownFile
* kf
= dynamic_cast<const CKnownFile
*>(f
);
163 if (kf
&& kf
->HasProperAICHHashSet()) {
164 strURL
<< wxT("h=") << kf
->GetAICHMasterHash() << wxT("|");
170 if (add_source
&& theApp
->IsConnected() && !theApp
->IsFirewalled()) {
171 // Create the first part of the URL
172 strURL
<< wxT("|sources,");
174 strURL
<< thePrefs::GetYourHostname();
176 uint32 clientID
= theApp
->GetID();
177 strURL
= CFormat(wxT("%s%u.%u.%u.%u"))
180 % ((clientID
>> 8) & 0xff)
181 % ((clientID
>> 16) & 0xff)
182 % ((clientID
>> 24) & 0xff);
185 strURL
<< wxT(":") <<
188 if (add_cryptoptions
) {
189 uint8 uSupportsCryptLayer
= thePrefs::IsClientCryptLayerSupported() ? 1 : 0;
190 uint8 uRequestsCryptLayer
= thePrefs::IsClientCryptLayerRequested() ? 1 : 0;
191 uint8 uRequiresCryptLayer
= thePrefs::IsClientCryptLayerRequired() ? 1 : 0;
192 uint16 byCryptOptions
= (uRequiresCryptLayer
<< 2) | (uRequestsCryptLayer
<< 1) | (uSupportsCryptLayer
<< 0) | (uSupportsCryptLayer
? 0x80 : 0x00);
194 strURL
<< wxT(":") << byCryptOptions
;
196 if (byCryptOptions
& 0x80) {
197 strURL
<< wxT(":") << thePrefs::GetUserHash().Encode();
201 } else if (add_source
) {
202 AddLogLineC(_("WARNING: You can't add yourself as a source for an eD2k link while having a lowid."));
205 // Result is "ed2k://|file|<filename>|<size>|<hash>|[h=<AICH master hash>|]/|sources,[(<ip>|<hostname>):<port>[:cryptoptions[:hash]]]|/"
210 bool CamuleAppCommon::InitCommon(int argc
, wxChar
** argv
)
212 theApp
->SetAppName(wxT("aMule"));
213 FullMuleVersion
= GetFullMuleVersion();
214 OSDescription
= wxGetOsDescription();
215 OSType
= OSDescription
.BeforeFirst( wxT(' ') );
216 if ( OSType
.IsEmpty() ) {
217 OSType
= wxT("Unknown");
220 // Parse cmdline arguments.
221 wxCmdLineParser
cmdline(argc
, argv
);
223 // Handle these arguments.
224 cmdline
.AddSwitch(wxT("v"), wxT("version"), wxT("Displays the current version number."));
225 cmdline
.AddSwitch(wxT("h"), wxT("help"), wxT("Displays this information."));
226 cmdline
.AddOption(wxT("c"), wxT("config-dir"), wxT("read config from <dir> instead of home"));
228 cmdline
.AddSwitch(wxT("f"), wxT("full-daemon"), wxT("Fork to background."));
229 cmdline
.AddOption(wxT("p"), wxT("pid-file"), wxT("After fork, create a pid-file in the given fullname file."));
230 cmdline
.AddSwitch(wxT("e"), wxT("ec-config"), wxT("Configure EC (External Connections)."));
234 // MSW shows help otions in a dialog box, and the formatting doesn't fit there
235 #define HELPTAB wxT("\t")
237 #define HELPTAB wxT("\t\t\t")
240 cmdline
.AddOption(wxT("geometry"), wxEmptyString
,
241 wxT("Sets the geometry of the app.\n")
242 HELPTAB
wxT("<str> uses the same format as standard X11 apps:\n")
243 HELPTAB
wxT("[=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]"));
244 #endif // !AMULE_DAEMON
246 cmdline
.AddSwitch(wxT("o"), wxT("log-stdout"), wxT("Print log messages to stdout."));
247 cmdline
.AddSwitch(wxT("r"), wxT("reset-config"), wxT("Resets config to default values."));
250 cmdline
.AddSwitch(wxT("s"), wxT("skip"), wxT("Skip connection dialog."));
252 // Change webserver path. This is also a config option, so this switch will go at some time.
253 cmdline
.AddOption(wxT("w"), wxT("use-amuleweb"), wxT("Specify location of amuleweb binary."));
256 cmdline
.AddSwitch(wxT("d"), wxT("disable-fatal"), wxT("Do not handle fatal exception."));
257 // Keep stdin open to run valgrind --gen_suppressions
258 cmdline
.AddSwitch(wxT("i"), wxT("enable-stdin"), wxT("Do not disable stdin."));
261 // Allow passing of links to the app
262 cmdline
.AddOption(wxT("t"), wxT("category"), wxT("Set category for passed ED2K links."), wxCMD_LINE_VAL_NUMBER
);
263 cmdline
.AddParam(wxT("ED2K link"), wxCMD_LINE_VAL_STRING
, wxCMD_LINE_PARAM_OPTIONAL
| wxCMD_LINE_PARAM_MULTIPLE
);
265 // wx asserts in debug mode if there is a check for an option that wasn't added.
266 // So we have to wrap around the same #ifdefs as above. >:(
268 // Show help on --help or invalid commands
269 if ( cmdline
.Parse() ) {
271 } else if (cmdline
.Found(wxT("help"))) {
276 if ( cmdline
.Found(wxT("version"))) {
277 // This looks silly with logging macros that add a timestamp.
278 printf("%s\n", (const char*)unicode2char(wxString(CFormat(wxT("%s (OS: %s)")) % FullMuleVersion
% OSType
)));
283 if (cmdline
.Found(wxT("config-dir"), &configdir
)) {
284 // Make an absolute path from the config dir
285 wxFileName
fn(configdir
);
287 configdir
= fn
.GetFullPath();
288 if (configdir
.Last() != wxFileName::GetPathSeparator()) {
289 configdir
+= wxFileName::GetPathSeparator();
291 thePrefs::SetConfigDir(configdir
);
293 thePrefs::SetConfigDir(/*OtherFunctions::*/GetConfigDir(m_configFile
));
296 // Backtracing works in MSW.
297 // Problem is just that the backtraces are useless, because apparently the context gets lost
298 // in the try/catch somewhere.
301 #if wxUSE_ON_FATAL_EXCEPTION
302 if ( !cmdline
.Found(wxT("disable-fatal")) ) {
303 // catch fatal exceptions
304 wxHandleFatalExceptions(true);
309 theLogger
.SetEnabledStdoutLog(cmdline
.Found(wxT("log-stdout")));
311 enable_daemon_fork
= cmdline
.Found(wxT("full-daemon"));
312 if ( cmdline
.Found(wxT("pid-file"), &m_PidFile
) ) {
313 // Remove any existing PidFile
314 if ( wxFileExists (m_PidFile
) ) wxRemoveFile (m_PidFile
);
316 ec_config
= cmdline
.Found(wxT("ec-config"));
318 enable_daemon_fork
= false;
320 // Default geometry of the GUI. Can be changed with a cmdline argument...
321 if ( cmdline
.Found(wxT("geometry"), &m_geometryString
) ) {
322 m_geometryEnabled
= true;
326 if (theLogger
.IsEnabledStdoutLog()) {
327 if ( enable_daemon_fork
) {
328 AddLogLineNS(wxT("Daemon will fork to background - log to stdout disabled")); // localization not active yet
329 theLogger
.SetEnabledStdoutLog(false);
331 AddLogLineNS(wxT("Logging to stdout enabled"));
335 AddLogLineNS(wxT("Initialising ") + FullMuleVersion
);
337 // Ensure that "~/.aMule/" is accessible.
339 if (!CheckMuleDirectory(wxT("configuration"), CPath(thePrefs::GetConfigDir()), wxEmptyString
, outDir
)) {
343 if (cmdline
.Found(wxT("reset-config"))) {
344 // Make a backup first.
345 wxRemoveFile(thePrefs::GetConfigDir() + m_configFile
+ wxT(".backup"));
346 wxRenameFile(thePrefs::GetConfigDir() + m_configFile
, thePrefs::GetConfigDir() + m_configFile
+ wxT(".backup"));
347 AddLogLineNS(CFormat(wxT("Your settings have been reset to default values.\nThe old config file has been saved as %s.backup\n")) % m_configFile
);
350 size_t linksPassed
= cmdline
.GetParamCount(); // number of links from the command line
351 // cppcheck-suppress variableScope
352 int linksActuallyPassed
= 0; // number of links that pass the syntax check
355 if (!cmdline
.Found(wxT("t"), &cat
)) {
359 wxTextFile
ed2kFile(thePrefs::GetConfigDir() + wxT("ED2KLinks"));
360 if (!ed2kFile
.Exists()) {
363 if (ed2kFile
.Open()) {
364 for (size_t i
= 0; i
< linksPassed
; i
++) {
366 if (CheckPassedLink(cmdline
.GetParam(i
), link
, cat
)) {
367 ed2kFile
.AddLine(link
);
368 linksActuallyPassed
++;
373 AddLogLineCS(wxT("Failed to open 'ED2KLinks', cannot add links."));
377 #if defined(__WXMAC__) && defined(AMULE_DAEMON)
378 //#warning TODO: fix wxSingleInstanceChecker for amuled on Mac (wx link problems)
379 AddLogLineCS(wxT("WARNING: The check for other instances is currently disabled in amuled.\n"
380 "Please make sure that no other instance of aMule is running or your files might be corrupted.\n"));
382 AddLogLineNS(wxT("Checking if there is an instance already running..."));
384 m_singleInstance
= new wxSingleInstanceChecker();
385 wxString lockfile
= IsRemoteGui() ? wxT("muleLockRGUI") : wxT("muleLock");
386 if (m_singleInstance
->Create(lockfile
, thePrefs::GetConfigDir())
387 && m_singleInstance
->IsAnotherRunning()) {
388 AddLogLineCS(CFormat(wxT("There is an instance of %s already running")) % m_appName
);
389 AddLogLineNS(CFormat(wxT("(lock file: %s%s)")) % thePrefs::GetConfigDir() % lockfile
);
391 AddLogLineNS(CFormat(wxT("passed %d %s to it, finished")) % linksActuallyPassed
392 % (linksPassed
== 1 ? wxT("link") : wxT("links")));
396 // This is very tricky. The most secure way to communicate is via ED2K links file
397 wxTextFile
ed2kFile(thePrefs::GetConfigDir() + wxT("ED2KLinks"));
398 if (!ed2kFile
.Exists()) {
402 if (ed2kFile
.Open()) {
403 ed2kFile
.AddLine(wxT("RAISE_DIALOG"));
406 AddLogLineNS(wxT("Raising current running instance."));
408 AddLogLineCS(wxT("Failed to open 'ED2KFile', cannot signal running instance."));
413 AddLogLineNS(wxT("No other instances are running."));
418 // Close standard-input
419 if ( !cmdline
.Found(wxT("enable-stdin")) ) {
420 // The full daemon will close all std file-descriptors by itself,
421 // so closing it here would lead to the closing on the first open
422 // file, which is the logfile opened below
423 if (!enable_daemon_fork
) {
429 // Create the CFG file we shall use and set the config object as the global cfg file
430 wxConfig::Set(new wxFileConfig( wxEmptyString
, wxEmptyString
, thePrefs::GetConfigDir() + m_configFile
));
432 // Make a backup of the log file
433 CPath logfileName
= CPath(thePrefs::GetConfigDir() + m_logFile
);
434 if (logfileName
.FileExists()) {
435 CPath::BackupFile(logfileName
, wxT(".bak"));
439 if (!theLogger
.OpenLogfile(logfileName
.GetRaw())) {
440 // use std err as last resolt to indicate problem
441 fputs("ERROR: unable to open log file\n", stderr
);
442 // failure to open log is serious problem
447 CPreferences::BuildItemList(thePrefs::GetConfigDir());
448 CPreferences::LoadAllItems( wxConfigBase::Get() );
451 m_skipConnectionDialog
= cmdline
.Found(wxT("skip"));
453 wxString amulewebPath
;
454 if (cmdline
.Found(wxT("use-amuleweb"), &amulewebPath
)) {
455 thePrefs::SetWSPath(amulewebPath
);
456 AddLogLineNS(CFormat(wxT("Using amuleweb in '%s'.")) % amulewebPath
);
464 * Returns a description of the version of aMule being used.
466 * @return A detailed description of the aMule version, including application
467 * name and wx information.
469 const wxString
CamuleAppCommon::GetFullMuleVersion() const
471 return GetMuleAppName() + wxT(" ") + GetMuleVersion();
474 bool CamuleAppCommon::CheckPassedLink(const wxString
&in
, wxString
&out
, int cat
)
478 // restore ASCII-encoded pipes
479 link
.Replace(wxT("%7C"), wxT("|"));
480 link
.Replace(wxT("%7c"), wxT("|"));
482 if (link
.compare(0, 7, wxT("magnet:")) == 0) {
483 link
= CMagnetED2KConverter(link
);
485 AddLogLineCS(CFormat(wxT("Cannot convert magnet link to eD2k: %s")) % in
);
491 CScopedPtr
<CED2KLink
> uri(CED2KLink::CreateLinkFromUrl(link
));
492 out
= uri
.get()->GetLink();
493 if (cat
&& uri
.get()->GetKind() == CED2KLink::kFile
) {
494 out
+= CFormat(wxT(":%d")) % cat
;
497 } catch ( const wxString
& err
) {
498 AddLogLineCS(CFormat(wxT("Invalid eD2k link \"%s\" - ERROR: %s")) % link
% err
);
505 * Checks permissions on a aMule directory, creating if needed.
507 * @param desc A description of the directory in question, used for error messages.
508 * @param directory The directory in question.
509 * @param alternative If the dir specified with 'directory' could not be created, try this instead.
510 * @param outDir Returns the used path.
511 * @return False on error.
513 bool CamuleAppCommon::CheckMuleDirectory(const wxString
& desc
, const CPath
& directory
, const wxString
& alternative
, CPath
& outDir
)
517 if (directory
.IsDir(CPath::readwritable
)) {
520 } else if (directory
.DirExists()) {
521 // Strings are not translated here because translation isn't up yet.
522 msg
= CFormat(wxT("Permissions on the %s directory too strict!\n")
523 wxT("aMule cannot proceed. To fix this, you must set read/write/exec\n")
524 wxT("permissions for the folder '%s'"))
526 } else if (CPath::MakeDir(directory
)) {
530 msg
<< CFormat(wxT("Could not create the %s directory at '%s'."))
534 // Attempt to use fallback directory.
535 const CPath
fallback(alternative
);
536 if (fallback
.IsOk() && (directory
!= fallback
)) {
537 msg
<< wxT("\nAttempting to use default directory at location \n'")
538 << alternative
<< wxT("'.");
539 if (theApp
->ShowAlert(msg
, wxT("Error accessing directory."), wxICON_ERROR
| wxOK
| wxCANCEL
) == wxCANCEL
) {
540 outDir
= CPath(wxEmptyString
);
544 return CheckMuleDirectory(desc
, fallback
, wxEmptyString
, outDir
);
547 theApp
->ShowAlert(msg
, wxT("Fatal error."), wxICON_ERROR
| wxOK
);
548 outDir
= CPath(wxEmptyString
);