------------------------------------------------------------ revno: 3257 committer: poy <p...@123gen.com> branch nick: trunk timestamp: Sat 2013-04-13 17:08:45 +0200 message: HTTP downloads can go straight to a file modified: dcpp/Download.cpp dcpp/FavoriteManager.cpp dcpp/FavoriteManager.h dcpp/HttpConnection.cpp dcpp/HttpConnection.h dcpp/HttpConnectionListener.h dcpp/HttpManager.cpp dcpp/HttpManager.h dcpp/HttpManagerListener.h dcpp/ShareManager.cpp dcpp/SimpleXML.cpp dcpp/Streams.h dcpp/forward.h win32/AboutDlg.cpp win32/AboutDlg.h win32/MainWindow.cpp win32/MainWindow.h win32/TransferView.cpp win32/TransferView.h
-- lp:dcplusplus https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk Your team Dcplusplus-team is subscribed to branch lp:dcplusplus. To unsubscribe from this branch go to https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk/+edit-subscription
=== modified file 'dcpp/Download.cpp' --- dcpp/Download.cpp 2013-01-18 21:28:38 +0000 +++ dcpp/Download.cpp 2013-04-13 15:08:45 +0000 @@ -140,7 +140,7 @@ output.reset(new File(target, File::WRITE, File::OPEN | File::TRUNCATE | File::CREATE)); tempTarget = target; } else if(getType() == Transfer::TYPE_PARTIAL_LIST) { - output.reset(new StringOutputStream(pfs)); + output.reset(new StringRefOutputStream(pfs)); } else if(getType() == Transfer::TYPE_TREE) { output.reset(new MerkleTreeOutputStream<TigerTree>(tt)); } === modified file 'dcpp/FavoriteManager.cpp' --- dcpp/FavoriteManager.cpp 2013-04-12 21:10:13 +0000 +++ dcpp/FavoriteManager.cpp 2013-04-13 15:08:45 +0000 @@ -761,12 +761,12 @@ return lst; } -void FavoriteManager::on(Added, HttpConnection* c) noexcept { +void FavoriteManager::on(HttpManagerListener::Added, HttpConnection* c) noexcept { if(c != this->c) { return; } fire(FavoriteManagerListener::DownloadStarting(), c->getUrl()); } -void FavoriteManager::on(Failed, HttpConnection* c, const string& str) noexcept { +void FavoriteManager::on(HttpManagerListener::Failed, HttpConnection* c, const string& str) noexcept { if(c != this->c) { return; } this->c = nullptr; lastServer++; @@ -776,14 +776,14 @@ } } -void FavoriteManager::on(Complete, HttpConnection* c, const string& buf) noexcept { +void FavoriteManager::on(HttpManagerListener::Complete, HttpConnection* c, OutputStream* stream) noexcept { if(c != this->c) { return; } this->c = nullptr; bool parseSuccess = false; if(useHttp) { if(c->getMimeType() == "application/x-bzip2") listType = TYPE_BZIP2; - parseSuccess = onHttpFinished(buf); + parseSuccess = onHttpFinished(static_cast<StringOutputStream*>(stream)->getString()); } running = false; if(parseSuccess) { === modified file 'dcpp/FavoriteManager.h' --- dcpp/FavoriteManager.h 2013-04-12 18:24:09 +0000 +++ dcpp/FavoriteManager.h 2013-04-13 15:08:45 +0000 @@ -160,9 +160,9 @@ void on(UserDisconnected, const UserPtr& user) noexcept; // HttpManagerListener - void on(Added, HttpConnection*) noexcept; - void on(Failed, HttpConnection*, const string&) noexcept; - void on(Complete, HttpConnection*, const string&) noexcept; + void on(HttpManagerListener::Added, HttpConnection*) noexcept; + void on(HttpManagerListener::Failed, HttpConnection*, const string&) noexcept; + void on(HttpManagerListener::Complete, HttpConnection*, OutputStream*) noexcept; bool onHttpFinished(const string& buf) noexcept; === modified file 'dcpp/HttpConnection.cpp' --- dcpp/HttpConnection.cpp 2013-04-12 18:24:09 +0000 +++ dcpp/HttpConnection.cpp 2013-04-13 15:08:45 +0000 @@ -22,6 +22,7 @@ #include "BufferedSocket.h" #include "format.h" #include "SettingsManager.h" +#include "TimerManager.h" #include "version.h" namespace dcpp { @@ -33,6 +34,9 @@ port("80"), size(-1), done(0), +speed(0), +lastPos(0), +lastTick(0), connState(CONN_UNKNOWN), coralizeState(coralize ? CST_DEFAULT : CST_NOCORALIZE), socket(0) @@ -86,6 +90,10 @@ size = -1; done = 0; + speed = 0; + lastPos = 0; + lastTick = GET_TICK(); + connState = CONN_UNKNOWN; connType = type; @@ -147,6 +155,20 @@ socket = NULL; } +void HttpConnection::updateSpeed() { + if(done > lastPos) { + auto tick = GET_TICK(); + + auto tickDelta = static_cast<double>(tick - lastTick); + if(tickDelta > 0) { + speed = static_cast<double>(done - lastPos) / tickDelta * 1000.0; + } + + lastPos = done; + lastTick = tick; + } +} + void HttpConnection::on(BufferedSocketListener::Connected) noexcept { dcassert(socket); socket->write("GET " + file + " HTTP/1.1\r\n"); @@ -237,13 +259,12 @@ fire(HttpConnectionListener::Failed(), this, str(F_("Endless redirection loop (%1%)") % url)); return; } + + fire(HttpConnectionListener::Redirected(), this, location); + + if(coralizeState != CST_NOCORALIZE) + coralizeState = CST_DEFAULT; url = location; - - fire(HttpConnectionListener::Redirected(), this); - - if (coralizeState != CST_NOCORALIZE) - coralizeState = CST_DEFAULT; - download(); } else if(aLine[0] == 0x0d) { @@ -297,8 +318,9 @@ return; } + done += aLen; + updateSpeed(); fire(HttpConnectionListener::Data(), this, aBuf, aLen); - done += aLen; } } // namespace dcpp === modified file 'dcpp/HttpConnection.h' --- dcpp/HttpConnection.h 2013-04-12 18:24:09 +0000 +++ dcpp/HttpConnection.h 2013-04-13 15:08:45 +0000 @@ -44,6 +44,7 @@ int64_t getSize() const { return size; } int64_t getDone() const { return done; } + double getSpeed() const { return speed; } bool coralized() const; @@ -65,6 +66,11 @@ string mimeType; int64_t size; int64_t done; + double speed; + + // counters to compute a best-effort speed + int64_t lastPos; + uint64_t lastTick; ConnectionStates connState; CoralizeStates coralizeState; @@ -75,6 +81,8 @@ void prepareRequest(RequestType type); void abortRequest(bool disconnect); + void updateSpeed(); + // BufferedSocketListener void on(Connected) noexcept; void on(Line, const string&) noexcept; === modified file 'dcpp/HttpConnectionListener.h' --- dcpp/HttpConnectionListener.h 2013-04-12 18:24:09 +0000 +++ dcpp/HttpConnectionListener.h 2013-04-13 15:08:45 +0000 @@ -42,7 +42,7 @@ virtual void on(Data, HttpConnection*, const uint8_t*, size_t) noexcept = 0; virtual void on(Failed, HttpConnection*, const string&) noexcept = 0; virtual void on(Complete, HttpConnection*) noexcept = 0; - virtual void on(Redirected, HttpConnection*) noexcept = 0; + virtual void on(Redirected, HttpConnection*, const string&) noexcept = 0; virtual void on(Retried, HttpConnection*, bool) noexcept = 0; }; === modified file 'dcpp/HttpManager.cpp' --- dcpp/HttpManager.cpp 2013-04-12 21:10:13 +0000 +++ dcpp/HttpManager.cpp 2013-04-13 15:08:45 +0000 @@ -21,6 +21,7 @@ #include "format.h" #include "HttpConnection.h" +#include "Streams.h" namespace dcpp { @@ -32,26 +33,28 @@ TimerManager::getInstance()->removeListener(this); } -HttpConnection* HttpManager::download(string url) { - auto conn = makeConn(move(url)); +HttpConnection* HttpManager::download(string url, OutputStream* stream) { + auto conn = makeConn(move(url), stream); conn->download(); return conn; } -HttpConnection* HttpManager::download(string url, const StringMap& postData) { - auto conn = makeConn(move(url)); +HttpConnection* HttpManager::download(string url, const StringMap& postData, OutputStream* stream) { + auto conn = makeConn(move(url), stream); conn->download(postData); return conn; } void HttpManager::disconnect(const string& url) { HttpConnection* c = nullptr; + OutputStream* stream; { Lock l(cs); conns.erase(std::remove_if(conns.begin(), conns.end(), [&](const Conn& conn) -> bool { if(conn.c->getUrl() == url) { c = conn.c; + stream = conn.stream; return true; } return false; @@ -62,20 +65,21 @@ fire(HttpManagerListener::Failed(), c, _("Disconnected")); fire(HttpManagerListener::Removed(), c); delete c; + delete stream; } } void HttpManager::shutdown() { Lock l(cs); - for(auto& conn: conns) { delete conn.c; } + for(auto& conn: conns) { delete conn.c; delete conn.stream; } conns.clear(); } -HttpConnection* HttpManager::makeConn(string&& url) { +HttpConnection* HttpManager::makeConn(string&& url, OutputStream* stream) { auto c = new HttpConnection(); { Lock l(cs); - Conn conn { c, string(), 0 }; + Conn conn { c, stream ? stream : new StringOutputStream(), 0 }; conns.push_back(move(conn)); } c->addListener(this); @@ -101,64 +105,56 @@ void HttpManager::on(HttpConnectionListener::Data, HttpConnection* c, const uint8_t* data, size_t len) noexcept { { Lock l(cs); - auto& buf = findConn(c)->buf; - if(buf.empty() && c->getSize() != -1) { - buf.reserve(c->getSize()); - } - buf.append(reinterpret_cast<const char*>(data), len); + findConn(c)->stream->write(data, len); } fire(HttpManagerListener::Updated(), c); - printf("size: %d\n", c->getSize()); } void HttpManager::on(HttpConnectionListener::Failed, HttpConnection* c, const string& str) noexcept { - { - Lock l(cs); - findConn(c)->buf.clear(); - } fire(HttpManagerListener::Failed(), c, str); removeLater(c); } void HttpManager::on(HttpConnectionListener::Complete, HttpConnection* c) noexcept { - string buf; + OutputStream* stream; { Lock l(cs); - buf = move(findConn(c)->buf); + stream = findConn(c)->stream; } - fire(HttpManagerListener::Complete(), c, move(buf)); + fire(HttpManagerListener::Complete(), c, stream); removeLater(c); } -void HttpManager::on(HttpConnectionListener::Redirected, HttpConnection* c) noexcept { +void HttpManager::on(HttpConnectionListener::Redirected, HttpConnection* c, const string& redirect) noexcept { fire(HttpManagerListener::Removed(), c); + c->setUrl(redirect); fire(HttpManagerListener::Added(), c); } void HttpManager::on(HttpConnectionListener::Retried, HttpConnection* c, bool connected) noexcept { if(connected) { - Lock l(cs); - findConn(c)->buf.clear(); + /// @todo reset / redirect } } void HttpManager::on(TimerManagerListener::Minute, uint64_t tick) noexcept { - vector<HttpConnection*> removed; + vector<pair<HttpConnection*, OutputStream*>> removed; { Lock l(cs); conns.erase(std::remove_if(conns.begin(), conns.end(), [tick, &removed](const Conn& conn) -> bool { if(conn.remove && tick > conn.remove) { - removed.push_back(conn.c); + removed.emplace_back(conn.c, conn.stream); return true; } return false; }), conns.end()); } - for(auto c: removed) { - fire(HttpManagerListener::Removed(), c); - delete c; + for(auto& rem: removed) { + fire(HttpManagerListener::Removed(), rem.first); + delete rem.first; + delete rem.second; } } === modified file 'dcpp/HttpManager.h' --- dcpp/HttpManager.h 2013-04-12 21:10:13 +0000 +++ dcpp/HttpManager.h 2013-04-13 15:08:45 +0000 @@ -41,22 +41,26 @@ private TimerManagerListener { public: - HttpConnection* download(string url); - HttpConnection* download(string url, const StringMap& postData); + /** Send a GET request to the designated url. If unspecified, the stream defaults to a + StringOutputStream. */ + HttpConnection* download(string url, OutputStream* stream = nullptr); + /** Send a POST request to the designated url. If unspecified, the stream defaults to a + StringOutputStream. */ + HttpConnection* download(string url, const StringMap& postData, OutputStream* stream = nullptr); void disconnect(const string& url); void shutdown(); private: - struct Conn { HttpConnection* c; string buf; uint64_t remove; }; + struct Conn { HttpConnection* c; OutputStream* stream; uint64_t remove; }; friend class Singleton<HttpManager>; HttpManager(); virtual ~HttpManager(); - HttpConnection* makeConn(string&& url); + HttpConnection* makeConn(string&& url, OutputStream* stream); Conn* findConn(HttpConnection* c); void removeLater(HttpConnection* c); @@ -64,7 +68,7 @@ void on(HttpConnectionListener::Data, HttpConnection*, const uint8_t*, size_t) noexcept; void on(HttpConnectionListener::Failed, HttpConnection*, const string&) noexcept; void on(HttpConnectionListener::Complete, HttpConnection*) noexcept; - void on(HttpConnectionListener::Redirected, HttpConnection*) noexcept; + void on(HttpConnectionListener::Redirected, HttpConnection*, const string&) noexcept; void on(HttpConnectionListener::Retried, HttpConnection*, bool) noexcept; // TimerManagerListener === modified file 'dcpp/HttpManagerListener.h' --- dcpp/HttpManagerListener.h 2013-04-12 18:24:09 +0000 +++ dcpp/HttpManagerListener.h 2013-04-13 15:08:45 +0000 @@ -42,7 +42,7 @@ virtual void on(Added, HttpConnection*) noexcept { } virtual void on(Updated, HttpConnection*) noexcept { } virtual void on(Failed, HttpConnection*, const string&) noexcept { } - virtual void on(Complete, HttpConnection*, const string&) noexcept { } + virtual void on(Complete, HttpConnection*, OutputStream*) noexcept { } virtual void on(Removed, HttpConnection*) noexcept { } }; === modified file 'dcpp/ShareManager.cpp' --- dcpp/ShareManager.cpp 2013-02-14 16:25:20 +0000 +++ dcpp/ShareManager.cpp 2013-04-13 15:08:45 +0000 @@ -861,7 +861,7 @@ string xml = SimpleXML::utf8Header; string tmp; xml += "<FileListing Version=\"1\" CID=\"" + ClientManager::getInstance()->getMe()->getCID().toBase32() + "\" Base=\"" + SimpleXML::escape(dir, tmp, true) + "\" Generator=\"" APPNAME " " VERSIONSTRING "\">\r\n"; - StringOutputStream sos(xml); + StringRefOutputStream sos(xml); string indent = "\t"; Lock l(cs); === modified file 'dcpp/SimpleXML.cpp' --- dcpp/SimpleXML.cpp 2013-01-18 21:28:38 +0000 +++ dcpp/SimpleXML.cpp 2013-04-13 15:08:45 +0000 @@ -200,10 +200,9 @@ } string SimpleXML::toXML() { - string tmp; - StringOutputStream os(tmp); + StringOutputStream os; toXML(&os); - return tmp; + return os.getString(); } } // namespace dcpp === modified file 'dcpp/Streams.h' --- dcpp/Streams.h 2013-02-14 16:25:20 +0000 +++ dcpp/Streams.h 2013-04-13 15:08:45 +0000 @@ -227,15 +227,35 @@ class StringOutputStream : public OutputStream { public: - StringOutputStream(string& out) : str(out) { } + StringOutputStream() { } virtual ~StringOutputStream() { } using OutputStream::write; virtual size_t flush() { return 0; } virtual size_t write(const void* buf, size_t len) { - str.append((char*)buf, len); - return len; - } + str.append(reinterpret_cast<const char*>(buf), len); + return len; + } + + string getString() { return move(str); } + string& stringRef() { return str; } + +private: + string str; +}; + +class StringRefOutputStream : public OutputStream { +public: + StringRefOutputStream(string& out) : str(out) { } + virtual ~StringRefOutputStream() { } + using OutputStream::write; + + virtual size_t flush() { return 0; } + virtual size_t write(const void* buf, size_t len) { + str.append(reinterpret_cast<const char*>(buf), len); + return len; + } + private: string& str; }; === modified file 'dcpp/forward.h' --- dcpp/forward.h 2013-04-12 18:24:09 +0000 +++ dcpp/forward.h 2013-04-13 15:08:45 +0000 @@ -95,6 +95,9 @@ class Socket; class SocketException; +class StringOutputStream; +class StringRefOutputStream; + class StringSearch; class Tagger; === modified file 'win32/AboutDlg.cpp' --- win32/AboutDlg.cpp 2013-04-12 18:24:09 +0000 +++ win32/AboutDlg.cpp 2013-04-13 15:08:45 +0000 @@ -24,6 +24,7 @@ #include <dcpp/HttpManager.h> #include <dcpp/SettingsManager.h> #include <dcpp/SimpleXML.h> +#include <dcpp/Streams.h> #include <dcpp/version.h> #include <dwt/widgets/Grid.h> @@ -195,7 +196,8 @@ callAsync([str, this] { completeDownload(false, str); }); } -void AboutDlg::on(HttpManagerListener::Complete, HttpConnection* c, const string& buf) noexcept { +void AboutDlg::on(HttpManagerListener::Complete, HttpConnection* c, OutputStream* stream) noexcept { if(c != this->c) { return; } - callAsync([buf, this] { completeDownload(true, buf); }); + auto str = static_cast<StringOutputStream*>(stream)->getString(); + callAsync([str, this] { completeDownload(true, str); }); } === modified file 'win32/AboutDlg.h' --- win32/AboutDlg.h 2013-04-12 18:24:09 +0000 +++ win32/AboutDlg.h 2013-04-13 15:08:45 +0000 @@ -54,7 +54,7 @@ // HttpManagerListener void on(HttpManagerListener::Failed, HttpConnection*, const string&) noexcept; - void on(HttpManagerListener::Complete, HttpConnection*, const string&) noexcept; + void on(HttpManagerListener::Complete, HttpConnection*, OutputStream*) noexcept; }; #endif // !defined(DCPLUSPLUS_WIN32_ABOUT_DLG_H) === modified file 'win32/MainWindow.cpp' --- win32/MainWindow.cpp 2013-04-12 21:10:13 +0000 +++ win32/MainWindow.cpp 2013-04-13 15:08:45 +0000 @@ -1756,13 +1756,16 @@ } } -void MainWindow::on(HttpManagerListener::Complete, HttpConnection* c, const string& buf) noexcept { +void MainWindow::on(HttpManagerListener::Complete, HttpConnection* c, OutputStream* stream) noexcept { if(c == conns[CONN_VERSION]) { - callAsync([buf, this] { completeVersionUpdate(true, buf); }); + auto str = static_cast<StringOutputStream*>(stream)->getString(); + callAsync([str, this] { completeVersionUpdate(true, str); }); } else if(c == conns[CONN_GEO_V6]) { - callAsync([buf, this] { completeGeoUpdate(true, true, buf); }); + auto str = static_cast<StringOutputStream*>(stream)->getString(); + callAsync([str, this] { completeGeoUpdate(true, true, str); }); } else if(c == conns[CONN_GEO_V4]) { - callAsync([buf, this] { completeGeoUpdate(false, true, buf); }); + auto str = static_cast<StringOutputStream*>(stream)->getString(); + callAsync([str, this] { completeGeoUpdate(false, true, str); }); } } === modified file 'win32/MainWindow.h' --- win32/MainWindow.h 2013-04-12 18:24:09 +0000 +++ win32/MainWindow.h 2013-04-13 15:08:45 +0000 @@ -233,7 +233,7 @@ // HttpManagerListener void on(HttpManagerListener::Failed, HttpConnection*, const string&) noexcept; - void on(HttpManagerListener::Complete, HttpConnection*, const string&) noexcept; + void on(HttpManagerListener::Complete, HttpConnection*, OutputStream*) noexcept; // LogManagerListener void on(LogManagerListener::Message, time_t t, const string& m) noexcept; === modified file 'win32/TransferView.cpp' --- win32/TransferView.cpp 2013-04-12 21:10:13 +0000 +++ win32/TransferView.cpp 2013-04-13 15:08:45 +0000 @@ -397,8 +397,6 @@ columns[COLUMN_PATH] = Text::toT(url); auto slash = columns[COLUMN_PATH].rfind('/'); columns[COLUMN_FILE] = slash != tstring::npos ? columns[COLUMN_PATH].substr(slash + 1) : columns[COLUMN_PATH]; - - columns[COLUMN_STATUS] = T_("Downloading"); } void TransferView::HttpInfo::update(const UpdateInfo& ui) { @@ -426,6 +424,21 @@ columns[COLUMN_SIZE].clear(); } } + + if(ui.updateMask & UpdateInfo::MASK_SPEED) { + speed = ui.speed; + columns[COLUMN_SPEED] = str(TF_("%1%/s") % Text::toT(Util::formatBytes(speed))); + } + + if((ui.updateMask & UpdateInfo::MASK_STATUS) || (ui.updateMask & UpdateInfo::MASK_TRANSFERRED) || (ui.updateMask & UpdateInfo::MASK_SPEED)) { + if(status == STATUS_RUNNING && size > 0 && speed > 0) { + timeleft = static_cast<double>(size - transferred) / speed; + columns[COLUMN_TIMELEFT] = Text::toT(Util::formatSeconds(timeleft)); + } else { + timeleft = 0; + columns[COLUMN_TIMELEFT].clear(); + } + } } void TransferView::HttpInfo::disconnect() { @@ -1028,6 +1041,7 @@ void TransferView::on(HttpManagerListener::Added, HttpConnection* c) noexcept { auto ui = makeHttpUI(c); ui->setStatus(STATUS_RUNNING); + ui->setStatusString(T_("Downloading")); ui->setTransferred(c->getDone(), c->getDone(), c->getSize()); addedConn(ui); @@ -1036,6 +1050,7 @@ void TransferView::on(HttpManagerListener::Updated, HttpConnection* c) noexcept { auto ui = makeHttpUI(c); ui->setTransferred(c->getDone(), c->getDone(), c->getSize()); + ui->setSpeed(c->getSpeed()); updatedConn(ui); } @@ -1049,10 +1064,10 @@ updatedConn(ui); } -void TransferView::on(HttpManagerListener::Complete, HttpConnection* c, const string&) noexcept { +void TransferView::on(HttpManagerListener::Complete, HttpConnection* c, OutputStream*) noexcept { auto ui = makeHttpUI(c); ui->setStatus(STATUS_WAITING); - ui->setStatusString(T_("Idle")); + ui->setStatusString(T_("Download finished")); ui->setTransferred(c->getDone(), c->getDone(), c->getSize()); updatedConn(ui); === modified file 'win32/TransferView.h' --- win32/TransferView.h 2013-04-12 21:10:13 +0000 +++ win32/TransferView.h 2013-04-13 15:08:45 +0000 @@ -180,7 +180,7 @@ UpdateInfo(const HintedUser& user, bool download, bool transferFailed = false) : updateMask(0), user(user), download(download), transferFailed(transferFailed) { } - UpdateInfo(bool download) : download(download), transferFailed(false) { } + UpdateInfo(bool download) : updateMask(0), download(download), transferFailed(false) { } uint32_t updateMask; @@ -217,7 +217,7 @@ tstring country; void setHttp() { updateMask |= MASK_HTTP; } - bool isHttp() const { return (updateMask & MASK_HTTP) == MASK_HTTP; } + bool isHttp() const { return updateMask & MASK_HTTP; } }; typedef TypedTable<ItemInfo, false, dwt::TableTree> WidgetTransfers; @@ -292,7 +292,7 @@ virtual void on(HttpManagerListener::Added, HttpConnection*) noexcept; virtual void on(HttpManagerListener::Updated, HttpConnection*) noexcept; virtual void on(HttpManagerListener::Failed, HttpConnection*, const string&) noexcept; - virtual void on(HttpManagerListener::Complete, HttpConnection*, const string&) noexcept; + virtual void on(HttpManagerListener::Complete, HttpConnection*, OutputStream*) noexcept; virtual void on(HttpManagerListener::Removed, HttpConnection*) noexcept; void addedConn(UpdateInfo* ui);
_______________________________________________ Mailing list: https://launchpad.net/~linuxdcpp-team Post to : linuxdcpp-team@lists.launchpad.net Unsubscribe : https://launchpad.net/~linuxdcpp-team More help : https://help.launchpad.net/ListHelp