diff --git a/client/api.c b/client/api.c index e2f1db7c..e491eeaa 100644 --- a/client/api.c +++ b/client/api.c @@ -1634,6 +1634,9 @@ TDNFResolve( pSolvedPkgInfo->pPkgsToDowngrade || pSolvedPkgInfo->pPkgsToReinstall; + dwError = TDNFCalculateTotalDownloadSize(pSolvedPkgInfo); + BAIL_ON_TDNF_ERROR(dwError); + pSolvedPkgInfo->ppszPkgsNotResolved = ppszPkgsNotResolved; *ppSolvedPkgInfo = pSolvedPkgInfo; diff --git a/client/defines.h b/client/defines.h index 449480be..74c385ad 100644 --- a/client/defines.h +++ b/client/defines.h @@ -233,6 +233,20 @@ typedef enum "ERROR_TDNF_DOWNGRADE_NOT_ALLOWED",\ "a downgrade is not allowed below the minimal version. Check 'minversions' in the configuration."},\ {ERROR_TDNF_PERM, "ERROR_TDNF_PERM", "Operation not permitted. You have to be root."},\ + {ERROR_TDNF_INVALID_PARAMETER, "ERROR_TDNF_INVALID_PARAMETER", "Invalid argument."},\ + {ERROR_TDNF_OUT_OF_MEMORY, "ERROR_TDNF_OUT_OF_MEMORY", "Out of memory."},\ + {ERROR_TDNF_NO_DATA, "ERROR_TDNF_NO_DATA", "No data available."},\ + {ERROR_TDNF_FILE_NOT_FOUND, "ERROR_TDNF_FILE_NOT_FOUND", "File not found."},\ + {ERROR_TDNF_ACCESS_DENIED, "ERROR_TDNF_ACCESS_DENIED", "Permission denied."},\ + {ERROR_TDNF_ALREADY_EXISTS, "ERROR_TDNF_ALREADY_EXISTS", "File exists."},\ + {ERROR_TDNF_INVALID_ADDRESS, "ERROR_TDNF_INVALID_ADDRESS", "Bad address."},\ + {ERROR_TDNF_CALL_INTERRUPTED, "ERROR_TDNF_CALL_INTERRUPTED", "Interrupted syscall."},\ + {ERROR_TDNF_FILESYS_IO, "ERROR_TDNF_FILESYS_IO", "Filesystem I/O error."},\ + {ERROR_TDNF_SYM_LOOP, "ERROR_TDNF_SYM_LOOP", "Too many links."},\ + {ERROR_TDNF_NAME_TOO_LONG, "ERROR_TDNF_NAME_TOO_LONG", "File name too long."},\ + {ERROR_TDNF_CALL_NOT_SUPPORTED, "ERROR_TDNF_CALL_NOT_SUPPORTED", "Invalid syscall number."},\ + {ERROR_TDNF_INVALID_DIR, "ERROR_TDNF_INVALID_DIR", "Not a directory."},\ + {ERROR_TDNF_OVERFLOW, "ERROR_TDNF_OVERFLOW", "Value too large for defined data type."},\ {ERROR_TDNF_OPT_NOT_FOUND, "ERROR_TDNF_OPT_NOT_FOUND", "A required option was not found"},\ {ERROR_TDNF_OPERATION_ABORTED, "ERROR_TDNF_OPERATION_ABORTED", "Operation aborted."},\ {ERROR_TDNF_INVALID_INPUT, "ERROR_TDNF_INVALID_INPUT", "Invalid input."},\ diff --git a/client/packageutils.c b/client/packageutils.c index b7f5d38a..e177daa2 100644 --- a/client/packageutils.c +++ b/client/packageutils.c @@ -161,11 +161,22 @@ TDNFPopulatePkgInfoArray( &pPkgInfo->dwInstallSizeBytes); BAIL_ON_TDNF_ERROR(dwError); + dwError = SolvGetPkgDownloadSizeFromId( + pSack, + dwPkgId, + &pPkgInfo->dwDownloadSizeBytes); + BAIL_ON_TDNF_ERROR(dwError); + dwError = TDNFUtilsFormatSize( pPkgInfo->dwInstallSizeBytes, &pPkgInfo->pszFormattedSize); BAIL_ON_TDNF_ERROR(dwError); + dwError = TDNFUtilsFormatSize( + pPkgInfo->dwDownloadSizeBytes, + &pPkgInfo->pszFormattedDownloadSize); + BAIL_ON_TDNF_ERROR(dwError); + dwError = SolvGetPkgSummaryFromId( pSack, dwPkgId, @@ -933,6 +944,50 @@ TDNFCheckProtectedPkgs( return dwError; } +uint32_t +TDNFCalculateTotalDownloadSize( + PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo + ) +{ + uint32_t dwError = 0; + uint32_t dwTotalDownloadSizeBytes = 0; + uint8_t byPkgIndex = 0; + PTDNF_PKG_INFO pPkgInfo = NULL; + + if(!pSolvedPkgInfo) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_ERROR(dwError); + } + + if(!pSolvedPkgInfo->nNeedDownload) + { + pSolvedPkgInfo->dwTotalDownloadSizeBytes = 0; + BAIL_ON_TDNF_ERROR(dwError); + } + + PTDNF_PKG_INFO ppPkgsNeedDownload[4] = { + pSolvedPkgInfo->pPkgsToInstall, + pSolvedPkgInfo->pPkgsToDowngrade, + pSolvedPkgInfo->pPkgsToUpgrade, + pSolvedPkgInfo->pPkgsToReinstall + }; + + for (byPkgIndex = 0; byPkgIndex < ARRAY_SIZE(ppPkgsNeedDownload); byPkgIndex++) + { + pPkgInfo = ppPkgsNeedDownload[byPkgIndex]; + while(pPkgInfo) { + dwTotalDownloadSizeBytes += pPkgInfo->dwDownloadSizeBytes; + pPkgInfo = pPkgInfo->pNext; + } + } + + pSolvedPkgInfo->dwTotalDownloadSizeBytes = dwTotalDownloadSizeBytes; + +error: + return dwError; +} + uint32_t TDNFPopulatePkgInfos( PSolvSack pSack, @@ -1004,7 +1059,12 @@ TDNFPopulatePkgInfos( pSack, dwPkgId, &pPkgInfo->dwInstallSizeBytes); + BAIL_ON_TDNF_ERROR(dwError); + dwError = SolvGetPkgDownloadSizeFromId( + pSack, + dwPkgId, + &pPkgInfo->dwDownloadSizeBytes); BAIL_ON_TDNF_ERROR(dwError); dwError = TDNFUtilsFormatSize( @@ -1012,6 +1072,11 @@ TDNFPopulatePkgInfos( &pPkgInfo->pszFormattedSize); BAIL_ON_TDNF_ERROR(dwError); + dwError = TDNFUtilsFormatSize( + pPkgInfo->dwDownloadSizeBytes, + &pPkgInfo->pszFormattedDownloadSize); + BAIL_ON_TDNF_ERROR(dwError); + pPkgInfo->pNext = pPkgInfos; pPkgInfos = pPkgInfo; pPkgInfo = NULL; diff --git a/client/prototypes.h b/client/prototypes.h index 3d0a1946..7eb5491c 100644 --- a/client/prototypes.h +++ b/client/prototypes.h @@ -438,6 +438,11 @@ TDNFCheckProtectedPkgs( PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo ); +uint32_t +TDNFCalculateTotalDownloadSize( + PTDNF_SOLVED_PKG_INFO pSolvedPkgInfo + ); + uint32_t TDNFPopulatePkgInfoArrayDependencies( PSolvSack pSack, diff --git a/common/utils.c b/common/utils.c index 525d428d..26e87125 100644 --- a/common/utils.c +++ b/common/utils.c @@ -156,8 +156,7 @@ TDNFUtilsFormatSize( dwError = TDNFAllocateMemory(1, nMaxSize, (void**)&pszFormattedSize); BAIL_ON_TDNF_ERROR(dwError); - if(sprintf(pszFormattedSize, "%6.2f%c %lu", dSize, pszSizes[nIndex], - (unsigned long)unSize) < 0) + if(sprintf(pszFormattedSize, "%6.2f%c", dSize, pszSizes[nIndex]) < 0) { dwError = ERROR_TDNF_OUT_OF_MEMORY; BAIL_ON_TDNF_ERROR(dwError); diff --git a/include/tdnferror.h b/include/tdnferror.h index b601da80..96fb2e08 100644 --- a/include/tdnferror.h +++ b/include/tdnferror.h @@ -163,6 +163,14 @@ extern "C" { #define ERROR_TDNF_FILE_NOT_FOUND (ERROR_TDNF_SYSTEM_BASE + ENOENT) #define ERROR_TDNF_ACCESS_DENIED (ERROR_TDNF_SYSTEM_BASE + EACCES) #define ERROR_TDNF_ALREADY_EXISTS (ERROR_TDNF_SYSTEM_BASE + EEXIST) +#define ERROR_TDNF_INVALID_ADDRESS (ERROR_TDNF_SYSTEM_BASE + EFAULT) +#define ERROR_TDNF_CALL_INTERRUPTED (ERROR_TDNF_SYSTEM_BASE + EINTR) +#define ERROR_TDNF_FILESYS_IO (ERROR_TDNF_SYSTEM_BASE + EIO) +#define ERROR_TDNF_SYM_LOOP (ERROR_TDNF_SYSTEM_BASE + ELOOP) +#define ERROR_TDNF_NAME_TOO_LONG (ERROR_TDNF_SYSTEM_BASE + ENAMETOOLONG) +#define ERROR_TDNF_CALL_NOT_SUPPORTED (ERROR_TDNF_SYSTEM_BASE + ENOSYS) +#define ERROR_TDNF_INVALID_DIR (ERROR_TDNF_SYSTEM_BASE + ENOTDIR) +#define ERROR_TDNF_OVERFLOW (ERROR_TDNF_SYSTEM_BASE + EOVERFLOW) #define ERROR_TDNF_JSONDUMP 1700 diff --git a/include/tdnftypes.h b/include/tdnftypes.h index bd2da383..460b49d3 100644 --- a/include/tdnftypes.h +++ b/include/tdnftypes.h @@ -149,6 +149,7 @@ typedef struct _TDNF_PKG_INFO { uint32_t dwEpoch; uint32_t dwInstallSizeBytes; + uint32_t dwDownloadSizeBytes; char* pszName; char* pszRepoName; char* pszVersion; @@ -159,6 +160,7 @@ typedef struct _TDNF_PKG_INFO char* pszLicense; char* pszDescription; char* pszFormattedSize; + char* pszFormattedDownloadSize; char* pszRelease; char* pszLocation; char **ppszDependencies; @@ -172,6 +174,7 @@ typedef struct _TDNF_SOLVED_PKG_INFO { int nNeedAction; int nNeedDownload; + uint32_t dwTotalDownloadSizeBytes; TDNF_ALTERTYPE nAlterType; PTDNF_PKG_INFO pPkgsNotAvailable; PTDNF_PKG_INFO pPkgsExisting; diff --git a/pytests/conftest.py b/pytests/conftest.py index 05babf78..c7803a78 100644 --- a/pytests/conftest.py +++ b/pytests/conftest.py @@ -298,6 +298,28 @@ def create_repoconf(self, filename, baseurl, name): with open(filename, "w") as f: f.write(templ.format(name=name, baseurl=baseurl)) + def _get_stdout_total_download_size(self, stdout): + key = "Total download size" + for line in stdout: + if key in line: + return line.split()[-1] + + def download_size_to_bytes(self, stdout): + size_chars = 'bkMG' + raw_str = self._get_stdout_total_download_size(stdout) + size, unit = float(raw_str[:-1]), raw_str[-1] + return size * (1024 ** size_chars.index(unit)) + + def get_cached_package_sizes(self, cache_dir): + ret = self.run(['find', cache_dir, '-name', '*.rpm']) + pkg_size_map = {} + for rpm in ret['stdout']: + pkg_size_map[rpm] = os.path.getsize(rpm) + return pkg_size_map + + def floats_approx_equal(self, x, y, tol=500): + return abs(x - y) <= tol + @pytest.fixture(scope='session') def utils(): diff --git a/pytests/tests/test_cache.py b/pytests/tests/test_cache.py index 10a5b4b6..475ac50f 100644 --- a/pytests/tests/test_cache.py +++ b/pytests/tests/test_cache.py @@ -123,3 +123,40 @@ def test_enable_repo_make_cache_verbose(utils): utils.run(['tdnf', '-v', '--disablerepo=*', '--enablerepo=photon-test', 'makecache']) after = os.path.getmtime(lastrefresh) assert(before < after) + + +def test_download_vs_cache_size_single_package(utils): + clean_cache(utils) + enable_cache(utils) + + cache_dir = utils.tdnf_config.get('main', 'cachedir') + pkgname = utils.config["mulversion_pkgname"] + utils.erase_package(pkgname) + ret = utils.run(['tdnf', 'install', '-y', '--nogpgcheck', pkgname]) + + down_bytes = utils.download_size_to_bytes(ret['stdout']) + cached_rpm_bytes = sum(utils.get_cached_package_sizes(cache_dir).values()) + + assert(utils.floats_approx_equal(down_bytes, cached_rpm_bytes)) + + +def test_download_vs_cache_size_multiple_packages(utils): + clean_cache(utils) + enable_cache(utils) + + cache_dir = utils.tdnf_config.get('main', 'cachedir') + run_args = ['tdnf', 'install', '-y', '--nogpgcheck'] + pkg_list = [ + utils.config["mulversion_pkgname"], + utils.config["sglversion_pkgname"], + utils.config["sglversion2_pkgname"], + ] + for pkgname in pkg_list: + utils.erase_package(pkgname) + run_args.append(pkgname) + + ret = utils.run(run_args) + down_bytes = utils.download_size_to_bytes(ret['stdout']) + cached_rpm_bytes = sum(utils.get_cached_package_sizes(cache_dir).values()) + + assert(utils.floats_approx_equal(down_bytes, cached_rpm_bytes)) diff --git a/solv/prototypes.h b/solv/prototypes.h index 44a6fd51..c0bac809 100644 --- a/solv/prototypes.h +++ b/solv/prototypes.h @@ -95,6 +95,12 @@ SolvGetPkgInstallSizeFromId( uint32_t dwPkgId, uint32_t * pdwSize); +uint32_t +SolvGetPkgDownloadSizeFromId( + PSolvSack pSack, + uint32_t dwPkgId, + uint32_t * pdwSize); + uint32_t SolvGetPkgSummaryFromId( PSolvSack pSack, diff --git a/solv/tdnfpackage.c b/solv/tdnfpackage.c index bad66e3a..d027b6bd 100644 --- a/solv/tdnfpackage.c +++ b/solv/tdnfpackage.c @@ -498,6 +498,43 @@ SolvGetPkgInstallSizeFromId( goto cleanup;; } +uint32_t +SolvGetPkgDownloadSizeFromId( + PSolvSack pSack, + uint32_t dwPkgId, + uint32_t* pdwSize) +{ + uint32_t dwError = 0; + uint32_t dwDownloadSize = 0; + Solvable *pSolv = NULL; + + if(!pSack || !pdwSize) + { + dwError = ERROR_TDNF_INVALID_PARAMETER; + BAIL_ON_TDNF_LIBSOLV_ERROR(dwError); + } + + pSolv = pool_id2solvable(pSack->pPool, dwPkgId); + if(!pSolv) + { + dwError = ERROR_TDNF_NO_DATA; + BAIL_ON_TDNF_ERROR(dwError); + } + + dwDownloadSize = solvable_lookup_num(pSolv, SOLVABLE_DOWNLOADSIZE, 0); + *pdwSize = dwDownloadSize; + +cleanup: + return dwError; + +error: + if(pdwSize) + { + *pdwSize = 0; + } + goto cleanup;; +} + uint32_t SolvGetPkgSummaryFromId( PSolvSack pSack, diff --git a/tools/cli/lib/installcmd.c b/tools/cli/lib/installcmd.c index 5943041c..0f3b7fb3 100644 --- a/tools/cli/lib/installcmd.c +++ b/tools/cli/lib/installcmd.c @@ -602,12 +602,14 @@ PrintAction( PTDNF_PKG_INFO pPkgInfo = NULL; uint32_t dwTotalInstallSize = 0; + uint32_t dwTotalDownloadSize = 0; char* pszTotalInstallSize = NULL; + char* pszTotalDownloadSize = NULL; char *pszEmptyString = ""; - #define COL_COUNT 5 - //Name | Arch | [Epoch:]Version-Release | Repository | Install Size - int nColPercents[COL_COUNT] = {30, 15, 20, 15, 10}; + #define COL_COUNT 6 + //Name | Arch | [Epoch:]Version-Release | Repository | Install Size | Download Size + int nColPercents[COL_COUNT] = {20, 15, 20, 15, 10, 10}; int nColWidths[COL_COUNT] = {0}; #define MAX_COL_LEN 256 @@ -653,6 +655,7 @@ PrintAction( while(pPkgInfo) { dwTotalInstallSize += pPkgInfo->dwInstallSizeBytes; + dwTotalDownloadSize += pPkgInfo->dwDownloadSizeBytes; memset(szEpochVersionRelease, 0, MAX_COL_LEN); if(pPkgInfo->dwEpoch) { @@ -691,8 +694,10 @@ PrintAction( pszEmptyString : pPkgInfo->pszRepoName; ppszInfoToPrint[4] = pPkgInfo->pszFormattedSize == NULL ? pszEmptyString : pPkgInfo->pszFormattedSize; + ppszInfoToPrint[5] = pPkgInfo->pszFormattedDownloadSize == NULL ? + pszEmptyString : pPkgInfo->pszFormattedDownloadSize; pr_info( - "%-*s %-*s %-*s %-*s %*s\n", + "%-*s %-*s %-*s %-*s %-*s %*s\n", nColWidths[0], ppszInfoToPrint[0], nColWidths[1], @@ -702,18 +707,27 @@ PrintAction( nColWidths[3], ppszInfoToPrint[3], nColWidths[4], - ppszInfoToPrint[4]); + ppszInfoToPrint[4], + nColWidths[5], + ppszInfoToPrint[5]); pPkgInfo = pPkgInfo->pNext; } TDNFUtilsFormatSize(dwTotalInstallSize, &pszTotalInstallSize); pr_info("\nTotal installed size: %s\n", pszTotalInstallSize); + TDNFUtilsFormatSize(dwTotalDownloadSize, &pszTotalDownloadSize); + pr_info("Total download size: %s\n", pszTotalDownloadSize); + cleanup: if (pszTotalInstallSize) { free(pszTotalInstallSize); } + if (pszTotalDownloadSize) + { + free(pszTotalDownloadSize); + } return dwError; error: