aboutsummaryrefslogtreecommitdiff
path: root/build/blog/2015-06-27-automatically-clean-up-previous-mobile-applications.html
diff options
context:
space:
mode:
authorneodarz <neodarz@neodarz.net>2017-04-28 00:30:19 +0200
committerneodarz <neodarz@neodarz.net>2017-04-28 00:30:19 +0200
commit9a88e9ff0385f66e7c565a394908503dc6e916ad (patch)
tree05ea8b356163f06c5fc99c2caf67fa8d3a28d67d /build/blog/2015-06-27-automatically-clean-up-previous-mobile-applications.html
parentf1965c50670f611ef54f9471490d45a554f7d866 (diff)
downloadmy_new_personal_website-9a88e9ff0385f66e7c565a394908503dc6e916ad.tar.xz
my_new_personal_website-9a88e9ff0385f66e7c565a394908503dc6e916ad.zip
Site updated at 2017-04-28T00:29:42+02:00
source branch was at: f1965c50670f611ef54f9471490d45a554f7d866 Correct a link
Diffstat (limited to '')
-rw-r--r--build/blog/2015-06-27-automatically-clean-up-previous-mobile-applications.html157
1 files changed, 157 insertions, 0 deletions
diff --git a/build/blog/2015-06-27-automatically-clean-up-previous-mobile-applications.html b/build/blog/2015-06-27-automatically-clean-up-previous-mobile-applications.html
new file mode 100644
index 00000000..29faf9ca
--- /dev/null
+++ b/build/blog/2015-06-27-automatically-clean-up-previous-mobile-applications.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"/>
+<meta content="pandoc" name="generator"/>
+<meta content="Zhiming Wang" name="author"/>
+<meta content="2015-06-27T21:19:59-07:00" name="date"/>
+<title>Automatically clean up "Previous Mobile Applications"</title>
+<link href="/img/apple-touch-icon-152.png" rel="apple-touch-icon-precomposed"/>
+<meta content="#FFFFFF" name="msapplication-TileColor"/>
+<meta content="/img/favicon-144.png" name="msapplication-TileImage"/>
+<meta content="width=device-width, initial-scale=1" name="viewport"/>
+<link href="/css/normalize.min.css" media="all" rel="stylesheet" type="text/css"/>
+<link href="/css/theme.css" media="all" rel="stylesheet" type="text/css"/>
+<link href="/css/highlight.css" media="all" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="archival-notice">This blog has been archived.<br/>Visit my home page at <a href="https://zhimingwang.org">zhimingwang.org</a>.</div>
+<nav class="nav">
+<a class="nav-icon" href="/" title="Home"><!--blog icon--></a>
+<a class="nav-title" href="/"><!--blog title--></a>
+<a class="nav-author" href="https://github.com/zmwangx" target="_blank"><!--blog author--></a>
+</nav>
+<article class="content">
+<header class="article-header">
+<h1 class="article-title">Automatically clean up "Previous Mobile Applications"</h1>
+<div class="article-metadata">
+<time class="article-timestamp" datetime="2015-06-27T21:19:59-07:00">June 27, 2015</time>
+</div>
+</header>
+<p>iTunes keeps a "Previous Mobile Applications" folder of questionable value, which always annoys me. It eats into disk space and wastes syncing/backup cycles and bandwidth; you can easily find horror stories online about <a href="http://forums.macrumors.com/threads/5-years-of-deleted-iphone-apps-accumulated-in-my-itunes-library.1781676/#post-19749496">100GB+ PMA folders</a>. The value? You might be able to roll back to an earlier version, or restore an app pulled from the App Store. Really? I never had that need in my life<a class="footnoteRef" href="#fn1" id="fnref1"><sup>1</sup></a>; have you? Worst of all, there should be a periodic clean up option — just like how deleted mail are automatically purged after one month, but the option is missing.</p>
+<p>Therefore, I wrote a trivial Python script to do the periodic cleanup. Feel free to grab my script below (also available at <a class="uri" href="http://git.io/previous-mobile-applications">http://git.io/previous-mobile-applications</a>) to save a few minutes of hacking. It should be plugged into a daily or weekly or monthly cron job (or the equivalent), and it writes data to <code>~/.local/share/itunes/previous-mobile-applications.json</code> by default. To customize, just modify the global constants.</p>
+<div class="sourceCode"><pre class="sourceCode python"><code class="sourceCode python"><span class="co">#!/usr/bin/env python3</span>
+
+<span class="co">"""Periodically clean up "Previous Mobile Applications" of iTunes."""</span>
+
+<span class="im">import</span> arrow
+<span class="im">import</span> datetime
+<span class="im">import</span> json
+<span class="im">import</span> os
+<span class="im">import</span> sys
+
+OFFENDING_DIR <span class="op">=</span> os.path.expanduser(<span class="st">"~/Music/iTunes/iTunes Media/Mobile Applications/Previous Mobile Applications"</span>)
+STORAGE_DIR <span class="op">=</span> os.path.expanduser(<span class="st">"~/.local/share/itunes"</span>)
+STORAGE_FILE <span class="op">=</span> os.path.join(STORAGE_DIR, <span class="st">"previous-mobile-applications.json"</span>)
+
+DELETE_AFTER <span class="op">=</span> datetime.timedelta(days<span class="op">=</span><span class="dv">7</span>)
+
+<span class="kw">def</span> load_storage():
+ <span class="co">"""Load stored dictionary of seen apps from STORAGE_FILE.</span>
+
+<span class="co"> Returns</span>
+<span class="co"> -------</span>
+<span class="co"> seen_app_dict : dict</span>
+<span class="co"> Dictionary of (app_filename, first_seen_date) key-value pairs,</span>
+<span class="co"> where app_filename is str, and last_seen_date is datetime.date.</span>
+
+<span class="co"> """</span>
+ os.makedirs(STORAGE_DIR, mode<span class="op">=</span><span class="bn">0o700</span>, exist_ok<span class="op">=</span><span class="va">True</span>)
+ <span class="cf">try</span>:
+ <span class="cf">with</span> <span class="bu">open</span>(STORAGE_FILE, encoding<span class="op">=</span><span class="st">"utf-8"</span>) <span class="im">as</span> fp:
+ serializable_seen_app_dict <span class="op">=</span> json.load(fp)
+ <span class="cf">return</span> {app_filename: arrow.get(serialized_first_seen_date).date()
+ <span class="cf">for</span> app_filename, serialized_first_seen_date <span class="kw">in</span> serializable_seen_app_dict.items()}
+ <span class="cf">except</span> <span class="pp">OSError</span>:
+ <span class="cf">return</span> {}
+
+<span class="kw">def</span> write_storage(seen_app_dict):
+ <span class="co">"""Write the dictionary of seen apps to STORAGE_FILE.</span>
+
+<span class="co"> Parameters</span>
+<span class="co"> ----------</span>
+<span class="co"> seen_app_dict : dict</span>
+<span class="co"> See the return format of load_storage().</span>
+
+<span class="co"> Returns</span>
+<span class="co"> -------</span>
+<span class="co"> 0 or 1</span>
+<span class="co"> Return code indicating success or failure.</span>
+
+<span class="co"> """</span>
+ <span class="co"># convert datetime.time to str (ISO 8601)</span>
+ serializable_seen_app_dict <span class="op">=</span> {app_filename: first_seen_date.isoformat()
+ <span class="cf">for</span> app_filename, first_seen_date <span class="kw">in</span> seen_app_dict.items()}
+ os.makedirs(STORAGE_DIR, mode<span class="op">=</span><span class="bn">0o700</span>, exist_ok<span class="op">=</span><span class="va">True</span>)
+ <span class="cf">try</span>:
+ <span class="cf">with</span> <span class="bu">open</span>(STORAGE_FILE, mode<span class="op">=</span><span class="st">"w"</span>, encoding<span class="op">=</span><span class="st">"utf-8"</span>) <span class="im">as</span> fp:
+ json.dump(serializable_seen_app_dict, fp, indent<span class="op">=</span><span class="dv">2</span>, sort_keys<span class="op">=</span><span class="va">True</span>)
+ <span class="cf">return</span> <span class="dv">0</span>
+ <span class="cf">except</span> <span class="pp">OSError</span> <span class="im">as</span> err:
+ sys.stderr.write(<span class="st">"error: failed to write to '</span><span class="sc">%s</span><span class="st">': </span><span class="sc">%s</span><span class="st">"</span> <span class="op">%</span> (STORAGE_FILE, <span class="bu">str</span>(err)))
+ <span class="cf">return</span> <span class="dv">1</span>
+
+<span class="kw">def</span> main():
+ <span class="co">"""Main.</span>
+
+<span class="co"> Returns</span>
+<span class="co"> -------</span>
+<span class="co"> 0 or 1</span>
+<span class="co"> Return code indicating success or failure.</span>
+
+<span class="co"> """</span>
+ <span class="cf">if</span> <span class="kw">not</span> os.path.isdir(OFFENDING_DIR):
+ <span class="co"># good, you don't have that junk</span>
+ <span class="cf">return</span> <span class="dv">0</span>
+
+ today <span class="op">=</span> datetime.date.today()
+ seen_app_dict <span class="op">=</span> load_storage()
+ current_app_list <span class="op">=</span> os.listdir(OFFENDING_DIR)
+
+ <span class="co"># boot already disappeared apps</span>
+ <span class="cf">for</span> app <span class="kw">in</span> [app <span class="cf">for</span> app <span class="kw">in</span> seen_app_dict <span class="cf">if</span> app <span class="kw">not</span> <span class="kw">in</span> current_app_list]:
+ seen_app_dict.pop(app)
+
+ <span class="co"># add newly appeared apps</span>
+ <span class="cf">for</span> app <span class="kw">in</span> [app <span class="cf">for</span> app <span class="kw">in</span> current_app_list <span class="cf">if</span> app <span class="kw">not</span> <span class="kw">in</span> seen_app_dict]:
+ seen_app_dict[app] <span class="op">=</span> today
+
+ <span class="co"># delete expired apps</span>
+ returncode <span class="op">=</span> <span class="dv">0</span>
+ newly_deleted_apps <span class="op">=</span> []
+ <span class="cf">for</span> app <span class="kw">in</span> seen_app_dict:
+ <span class="cf">if</span> today <span class="op">&gt;=</span> seen_app_dict[app] <span class="op">+</span> DELETE_AFTER:
+ app_path <span class="op">=</span> os.path.join(OFFENDING_DIR, app)
+ <span class="cf">try</span>:
+ os.remove(app_path)
+ newly_deleted_apps.append(app)
+ <span class="cf">except</span> <span class="pp">OSError</span> <span class="im">as</span> err:
+ sys.stderr.write(<span class="st">"error: failed to remove '</span><span class="sc">%s</span><span class="st">': </span><span class="sc">%s</span><span class="st">"</span> <span class="op">%</span> (app_path, <span class="bu">str</span>(err)))
+ returncode <span class="op">=</span> <span class="dv">1</span>
+
+ <span class="cf">for</span> app <span class="kw">in</span> newly_deleted_apps:
+ seen_app_dict.pop(app)
+
+ <span class="co"># write data to disk</span>
+ returncode <span class="op">|=</span> write_storage(seen_app_dict)
+
+ <span class="cf">return</span> returncode
+
+<span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">"__main__"</span>:
+ exit(main())</code></pre></div>
+<div class="footnotes">
+<hr/>
+<ol>
+<li id="fn1"><p>Full disclosure: unlike many people, I'm not very obsessed with my phone, and I only have about two dozen third-party apps.<a class="footnotes-backlink" href="#fnref1">↩︎</a></p></li>
+</ol>
+</div>
+</article>
+<hr class="content-separator"/>
+<footer class="footer">
+<span class="rfooter">
+<a class="rss-icon" href="/rss.xml" target="_blank" title="RSS feed"><!--RSS feed icon--></a><a class="atom-icon" href="/atom.xml" target="_blank" title="Atom feed"><!--Atom feed icon--></a><a class="cc-icon" href="https://creativecommons.org/licenses/by/4.0/" target="_blank" title="Released under the Creative Commons Attribution 4.0 International license."><!--CC icon--></a>
+<a href="https://github.com/zmwangx" target="_blank">Zhiming Wang</a>
+</span>
+</footer>
+</body>
+</html>