Merge remote-tracking branch 'upstream/master'

This commit is contained in:
dosiecki 2017-04-19 13:02:54 -07:00
commit 63c93e0382
200 changed files with 9475 additions and 17185 deletions

View file

@ -15,7 +15,7 @@ def subscribe(request):
user = request.user
categories = MCategory.serialize()
category_titles = [c['title'] for c in categories['categories']]
subscribe_category_titles = request.REQUEST.getlist('category')
subscribe_category_titles = request.REQUEST.getlist('category') or request.REQUEST.getlist('category[]')
invalid_category_title = False
for category_title in subscribe_category_titles:

View file

@ -179,8 +179,8 @@ class EmailNewsletter:
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
listeners_count = r.publish("%s:story" % feed.pk, 'story:new:%s' % story_hash)
if listeners_count:
logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.title[:30], listeners_count))
logging.debug(" ---> [%-30s] ~FMPublished to %s subscribers" % (feed.log_title[:30], listeners_count))
except redis.ConnectionError:
logging.debug(" ***> [%-30s] ~BMRedis is unavailable for real-time." % (feed.title[:30],))
logging.debug(" ***> [%-30s] ~BMRedis is unavailable for real-time." % (feed.log_title[:30],))

View file

@ -5,6 +5,7 @@ import mongoengine as mongo
import boto
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.template.loader import render_to_string
from django.core.mail import EmailMultiAlternatives
# from django.utils.html import strip_tags
@ -17,8 +18,9 @@ from utils import log as logging
from utils import mongoengine_fields
from HTMLParser import HTMLParser
from vendor.apns import APNs, Payload
from BeautifulSoup import BeautifulSoup
from BeautifulSoup import BeautifulSoup, Tag
import types
import urlparse
class NotificationFrequency(enum.Enum):
immediately = 1
@ -191,7 +193,7 @@ class MUserFeedNotification(mongo.Document):
soup = BeautifulSoup(story['story_content'].strip())
# print story['story_content']
body = replace_with_newlines(soup)
body = truncate_chars(body.strip(), 1600)
body = truncate_chars(body.strip(), 1200)
return title, subtitle, body
@ -223,9 +225,10 @@ class MUserFeedNotification(mongo.Document):
def send_ios(self, story, user, usersub):
if not self.is_ios: return
apns = APNs(use_sandbox=True,
cert_file='/srv/newsblur/config/certificates/aps_development.pem',
key_file='/srv/newsblur/config/certificates/aps_development.pem')
apns = APNs(use_sandbox=False,
cert_file='/srv/newsblur/config/certificates/aps.pem',
key_file='/srv/newsblur/config/certificates/aps.pem',
enhanced=True)
tokens = MUserNotificationTokens.get_tokens_for_user(self.user_id)
title, subtitle, body = self.title_and_body(story, usersub)
@ -255,9 +258,11 @@ class MUserFeedNotification(mongo.Document):
def send_email(self, story, usersub):
if not self.is_email: return
feed = usersub.feed
story_content = self.sanitize_story(story['story_content'])
params = {
"story": story,
"story_content": story_content,
"feed": feed,
"feed_title": usersub.user_title or feed.feed_title,
"favicon_border": feed.favicon_color,
@ -278,6 +283,33 @@ class MUserFeedNotification(mongo.Document):
logging.user(usersub.user, '~BMStory notification by email error: ~FR%s' % e)
logging.user(usersub.user, '~BMStory notification by email: ~FY~SB%s~SN~BM~FY/~SB%s' %
(story['story_title'][:50], usersub.feed.feed_title[:50]))
def sanitize_story(self, story_content):
soup = BeautifulSoup(story_content.strip())
fqdn = Site.objects.get_current().domain
for iframe in soup("iframe"):
url = dict(iframe.attrs).get('src', "")
youtube_id = self.extract_youtube_id(url)
if youtube_id:
a = Tag(soup, 'a', [('href', url)])
img = Tag(soup, 'img', [('style', "display: block; 'background-image': \"url(https://%s/img/reader/youtube_play.png), url(http://img.youtube.com/vi/%s/0.jpg)\"" % (fqdn, youtube_id)), ('src', 'http://img.youtube.com/vi/%s/0.jpg' % youtube_id)])
a.insert(0, img)
iframe.replaceWith(a)
else:
iframe.extract()
return unicode(soup)
def extract_youtube_id(self, url):
youtube_id = None
if 'youtube.com' in url:
youtube_parts = urlparse.urlparse(url)
if '/embed/' in youtube_parts.path:
youtube_id = youtube_parts.path.replace('/embed/', '')
return youtube_id
def story_score(self, story, classifiers):
score = compute_story_score(story, classifier_titles=classifiers.get('titles', []),

View file

@ -22,7 +22,7 @@ def notifications_by_feed(request):
def set_notifications_for_feed(request):
user = get_user(request)
feed_id = request.POST['feed_id']
notification_types = request.POST.getlist('notification_types')
notification_types = request.POST.getlist('notification_types') or request.POST.getlist('notification_types[]')
notification_filter = request.POST.get('notification_filter')
try:

View file

@ -355,7 +355,7 @@ class Profile(models.Model):
def cancel_premium(self):
paypal_cancel = self.cancel_premium_paypal()
stripe_cancel = self.cancel_premium_stripe()
return paypal_cancel or stripe_cancel
return stripe_cancel or paypal_cancel
def cancel_premium_paypal(self, second_most_recent_only=False):
transactions = PayPalIPN.objects.filter(custom=self.user.username,
@ -408,6 +408,14 @@ class Profile(models.Model):
return True
@property
def latest_paypal_email(self):
ipn = PayPalIPN.objects.filter(custom=self.user.username)
if not len(ipn):
return
return ipn[0].payer_email
@classmethod
def clear_dead_spammers(self, days=30, confirm=False):
users = User.objects.filter(date_joined__gte=datetime.datetime.now()-datetime.timedelta(days=days)).order_by('-date_joined')
@ -441,7 +449,7 @@ class Profile(models.Model):
if verbose:
feed = Feed.get_by_id(feed_id)
logging.debug(" ---> [%-30s] ~SN~FBCounting subscribers for feed:~SB~FM%s~SN~FB user:~SB~FM%s" % (feed.title[:30], feed_id, user_id))
logging.debug(" ---> [%-30s] ~SN~FBCounting subscribers for feed:~SB~FM%s~SN~FB user:~SB~FM%s" % (feed.log_title[:30], feed_id, user_id))
if feed_id:
feed_ids = [feed_id]
@ -503,7 +511,7 @@ class Profile(models.Model):
r.expire(premium_key, settings.SUBSCRIBER_EXPIRE*24*60*60)
logging.info(" ---> [%-30s] ~SN~FBCounting subscribers, storing in ~SBredis~SN: ~FMt:~SB~FM%s~SN a:~SB%s~SN p:~SB%s~SN ap:~SB%s" %
(feed.title[:30], total, active, premium, active_premium))
(feed.log_title[:30], total, active, premium, active_premium))
@classmethod
def count_all_feed_subscribers_for_user(self, user):

View file

@ -413,6 +413,7 @@ def payment_history(request):
"last_seen_ip": user.profile.last_seen_ip,
"timezone": unicode(user.profile.timezone),
"stripe_id": user.profile.stripe_id,
"paypal_email": user.profile.latest_paypal_email,
"profile": user.profile,
"feeds": UserSubscription.objects.filter(user=user).count(),
"email": user.email,

View file

@ -2,6 +2,7 @@ import datetime
import time
import re
import redis
import pymongo
from operator import itemgetter
from pprint import pprint
from utils import log as logging
@ -753,9 +754,13 @@ class UserSubscription(models.Model):
cutoff_date=self.user.profile.unread_cutoff)
if not stories:
stories_db = MStory.objects(story_hash__in=unread_story_hashes)
stories = Feed.format_stories(stories_db, self.feed_id)
try:
stories_db = MStory.objects(story_hash__in=unread_story_hashes)
stories = Feed.format_stories(stories_db, self.feed_id)
except pymongo.errors.OperationFailure, e:
stories_db = MStory.objects(story_hash__in=unread_story_hashes)[:100]
stories = Feed.format_stories(stories_db, self.feed_id)
unread_stories = []
for story in stories:
if story['story_date'] < date_delta:

View file

@ -58,4 +58,6 @@ urlpatterns = patterns('',
url(r'^send_story_email', views.send_story_email, name='send-story-email'),
url(r'^retrain_all_sites', views.retrain_all_sites, name='retrain-all-sites'),
url(r'^load_tutorial', views.load_tutorial, name='load-tutorial'),
url(r'^save_search', views.save_search, name='save-search'),
url(r'^delete_search', views.delete_search, name='delete-search'),
)

View file

@ -35,7 +35,7 @@ from apps.analyzer.models import get_classifiers_for_user, sort_classifiers_by_f
from apps.profile.models import Profile
from apps.reader.models import UserSubscription, UserSubscriptionFolders, RUserStory, Feature
from apps.reader.forms import SignupForm, LoginForm, FeatureForm
from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts
from apps.rss_feeds.models import MFeedIcon, MStarredStoryCounts, MSavedSearch
from apps.notifications.models import MUserFeedNotification
from apps.search.models import MUserSearch
from apps.statistics.models import MStatistics
@ -279,6 +279,8 @@ def load_feeds(request):
if not starred_count and len(starred_counts):
starred_count = MStarredStory.objects(user_id=user.pk).count()
saved_searches = MSavedSearch.user_searches(user.pk)
social_params = {
'user_id': user.pk,
'include_favicon': include_favicons,
@ -306,6 +308,7 @@ def load_feeds(request):
'folders': json.decode(folders.folders),
'starred_count': starred_count,
'starred_counts': starred_counts,
'saved_searches': saved_searches,
'categories': categories
}
return data
@ -313,7 +316,7 @@ def load_feeds(request):
@json.json_view
def load_feed_favicons(request):
user = get_user(request)
feed_ids = request.REQUEST.getlist('feed_ids')
feed_ids = request.REQUEST.getlist('feed_ids') or request.REQUEST.getlist('feed_ids[]')
if not feed_ids:
user_subs = UserSubscription.objects.select_related('feed').filter(user=user, active=True)
@ -328,6 +331,7 @@ def load_feeds_flat(request):
include_favicons = is_true(request.REQUEST.get('include_favicons', False))
update_counts = is_true(request.REQUEST.get('update_counts', True))
include_inactive = is_true(request.REQUEST.get('include_inactive', False))
background_ios = is_true(request.REQUEST.get('background_ios', False))
feeds = {}
inactive_feeds = {}
@ -403,8 +407,9 @@ def load_feeds_flat(request):
if not user_subs:
categories = MCategory.serialize()
logging.user(request, "~FB~SBLoading ~FY%s~FB/~FM%s~FB/~FR%s~FB feeds/socials/inactive ~FMflat~FB%s" % (
len(feeds.keys()), len(social_feeds), len(inactive_feeds), '. ~FCUpdating counts.' if update_counts else ''))
logging.user(request, "~FB~SBLoading ~FY%s~FB/~FM%s~FB/~FR%s~FB feeds/socials/inactive ~FMflat~FB%s%s" % (
len(feeds.keys()), len(social_feeds), len(inactive_feeds), '. ~FCUpdating counts.' if update_counts else '',
' ~BB(background fetch)' if background_ios else ''))
data = {
"flat_folders": flat_folders,
@ -434,9 +439,9 @@ def load_feeds_flat(request):
def refresh_feeds(request):
start = datetime.datetime.now()
user = get_user(request)
feed_ids = request.REQUEST.getlist('feed_id')
feed_ids = request.REQUEST.getlist('feed_id') or request.REQUEST.getlist('feed_id[]')
check_fetch_status = request.REQUEST.get('check_fetch_status')
favicons_fetching = request.REQUEST.getlist('favicons_fetching')
favicons_fetching = request.REQUEST.getlist('favicons_fetching') or request.REQUEST.getlist('favicons_fetching[]')
social_feed_ids = [feed_id for feed_id in feed_ids if 'social:' in feed_id]
feed_ids = list(set(feed_ids) - set(social_feed_ids))
@ -514,7 +519,7 @@ def interactions_count(request):
@json.json_view
def feed_unread_count(request):
user = request.user
feed_ids = request.REQUEST.getlist('feed_id')
feed_ids = request.REQUEST.getlist('feed_id') or request.REQUEST.getlist('feed_id[]')
force = request.REQUEST.get('force', False)
social_feed_ids = [feed_id for feed_id in feed_ids if 'social:' in feed_id]
feed_ids = list(set(feed_ids) - set(social_feed_ids))
@ -766,7 +771,7 @@ def load_single_feed(request, feed_id):
# if page <= 3:
# import random
# # time.sleep(random.randint(2, 7) / 10.0)
# time.sleep(random.randint(10, 14))
# time.sleep(random.randint(1, 10))
# if page == 2:
# assert False
@ -798,7 +803,7 @@ def load_feed_page(request, feed_id):
if settings.BACKED_BY_AWS['pages_on_s3'] and feed.s3_page:
if settings.PROXY_S3_PAGES:
key = settings.S3_PAGES_BUCKET.get_key(feed.s3_pages_key)
key = settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME).get_key(feed.s3_pages_key)
if key:
compressed_data = key.get_contents_as_string()
response = HttpResponse(compressed_data, mimetype="text/html; charset=utf-8")
@ -832,7 +837,8 @@ def load_starred_stories(request):
query = request.REQUEST.get('query', '').strip()
order = request.REQUEST.get('order', 'newest')
tag = request.REQUEST.get('tag')
story_hashes = request.REQUEST.getlist('h')[:100]
story_hashes = request.REQUEST.getlist('h') or request.REQUEST.getlist('h[]')
story_hashes = story_hashes[:100]
version = int(request.REQUEST.get('v', 1))
now = localtime_for_timezone(datetime.datetime.now(), user.profile.timezone)
message = None
@ -1216,10 +1222,13 @@ def load_river_stories__redis(request):
start = time.time()
user = get_user(request)
message = None
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('feeds') if feed_id]
feed_ids = request.REQUEST.getlist('feeds') or request.REQUEST.getlist('feeds[]')
feed_ids = [int(feed_id) for feed_id in feed_ids if feed_id]
if not feed_ids:
feed_ids = request.REQUEST.getlist('f') or request.REQUEST.getlist('f[]')
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('f') if feed_id]
story_hashes = request.REQUEST.getlist('h')[:100]
story_hashes = request.REQUEST.getlist('h') or request.REQUEST.getlist('h[]')
story_hashes = story_hashes[:100]
original_feed_ids = list(feed_ids)
page = int(request.REQUEST.get('page', 1))
order = request.REQUEST.get('order', 'newest')
@ -1423,7 +1432,8 @@ def load_river_stories__redis(request):
@json.json_view
def complete_river(request):
user = get_user(request)
feed_ids = [int(feed_id) for feed_id in request.POST.getlist('feeds') if feed_id]
feed_ids = request.POST.getlist('feeds') or request.POST.getlist('feeds[]')
feed_ids = [int(feed_id) for feed_id in feed_ids if feed_id]
page = int(request.POST.get('page', 1))
read_filter = request.POST.get('read_filter', 'unread')
stories_truncated = 0
@ -1441,7 +1451,8 @@ def complete_river(request):
@json.json_view
def unread_story_hashes__old(request):
user = get_user(request)
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('feed_id') if feed_id]
feed_ids = request.REQUEST.getlist('feed_id') or request.REQUEST.getlist('feed_id[]')
feed_ids = [int(feed_id) for feed_id in feed_ids if feed_id]
include_timestamps = is_true(request.REQUEST.get('include_timestamps', False))
usersubs = {}
@ -1480,7 +1491,8 @@ def unread_story_hashes__old(request):
@json.json_view
def unread_story_hashes(request):
user = get_user(request)
feed_ids = [int(feed_id) for feed_id in request.REQUEST.getlist('feed_id') if feed_id]
feed_ids = request.REQUEST.getlist('feed_id') or request.REQUEST.getlist('feed_id[]')
feed_ids = [int(feed_id) for feed_id in feed_ids if feed_id]
include_timestamps = is_true(request.REQUEST.get('include_timestamps', False))
order = request.REQUEST.get('order', 'newest')
read_filter = request.REQUEST.get('read_filter', 'unread')
@ -1526,7 +1538,7 @@ def mark_all_as_read(request):
@ajax_login_required
@json.json_view
def mark_story_as_read(request):
story_ids = request.REQUEST.getlist('story_id')
story_ids = request.REQUEST.getlist('story_id') or request.REQUEST.getlist('story_id[]')
try:
feed_id = int(get_argument_or_404(request, 'feed_id'))
except ValueError:
@ -1561,7 +1573,7 @@ def mark_story_as_read(request):
def mark_story_hashes_as_read(request):
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
try:
story_hashes = request.REQUEST.getlist('story_hash')
story_hashes = request.REQUEST.getlist('story_hash') or request.REQUEST.getlist('story_hash[]')
except UnreadablePostError:
return dict(code=-1, message="Missing `story_hash` list parameter.")
@ -1766,7 +1778,7 @@ def mark_story_hash_as_unread(request):
@json.json_view
def mark_feed_as_read(request):
r = redis.Redis(connection_pool=settings.REDIS_PUBSUB_POOL)
feed_ids = request.REQUEST.getlist('feed_id')
feed_ids = request.POST.getlist('feed_id') or request.POST.getlist('feed_id[]')
cutoff_timestamp = int(request.REQUEST.get('cutoff_timestamp', 0))
direction = request.REQUEST.get('direction', 'older')
multiple = len(feed_ids) > 1
@ -1959,7 +1971,8 @@ def delete_feed_by_url(request):
def delete_folder(request):
folder_to_delete = request.POST.get('folder_name') or request.POST.get('folder_to_delete')
in_folder = request.POST.get('in_folder', None)
feed_ids_in_folder = [int(f) for f in request.REQUEST.getlist('feed_id') if f]
feed_ids_in_folder = request.REQUEST.getlist('feed_id') or request.REQUEST.getlist('feed_id[]')
feed_ids_in_folder = [int(f) for f in feed_ids_in_folder if f]
request.user.profile.send_opml_export_email(reason="You have deleted an entire folder of feeds, so here's a backup of all of your subscriptions just in case.")
@ -2033,8 +2046,8 @@ def rename_folder(request):
@json.json_view
def move_feed_to_folders(request):
feed_id = int(request.POST['feed_id'])
in_folders = request.POST.getlist('in_folders', '')
to_folders = request.POST.getlist('to_folders', '')
in_folders = request.POST.getlist('in_folders', '') or request.POST.getlist('in_folders[]', '')
to_folders = request.POST.getlist('to_folders', '') or request.POST.getlist('to_folders[]', '')
user_sub_folders = get_object_or_404(UserSubscriptionFolders, user=request.user)
user_sub_folders = user_sub_folders.move_feed_to_folders(feed_id, in_folders=in_folders,
@ -2176,7 +2189,8 @@ def feeds_trainer(request):
@json.json_view
def save_feed_chooser(request):
is_premium = request.user.profile.is_premium
approved_feeds = [int(feed_id) for feed_id in request.POST.getlist('approved_feeds') if feed_id]
approved_feeds = request.POST.getlist('approved_feeds') or request.POST.getlist('approved_feeds[]')
approved_feeds = [int(feed_id) for feed_id in approved_feeds if feed_id]
approve_all = False
if not is_premium:
approved_feeds = approved_feeds[:64]
@ -2274,7 +2288,7 @@ def _mark_story_as_starred(request):
feed_id = int(request.REQUEST.get('feed_id', 0))
story_id = request.REQUEST.get('story_id', None)
story_hash = request.REQUEST.get('story_hash', None)
user_tags = request.REQUEST.getlist('user_tags')
user_tags = request.REQUEST.getlist('user_tags') or request.REQUEST.getlist('user_tags[]')
message = ""
if story_hash:
story, _ = MStory.find_story(story_hash=story_hash)
@ -2480,3 +2494,31 @@ def load_tutorial(request):
return {
'newsblur_feed': newsblur_feed.canonical()
}
@required_params('query', 'feed_id')
@json.json_view
def save_search(request):
feed_id = request.POST['feed_id']
query = request.POST['query']
MSavedSearch.save_search(user_id=request.user.pk, feed_id=feed_id, query=query)
saved_searches = MSavedSearch.user_searches(request.user.pk)
return {
'saved_searches': saved_searches,
}
@required_params('query', 'feed_id')
@json.json_view
def delete_search(request):
feed_id = request.POST['feed_id']
query = request.POST['query']
MSavedSearch.delete_search(user_id=request.user.pk, feed_id=feed_id, query=query)
saved_searches = MSavedSearch.user_searches(request.user.pk)
return {
'saved_searches': saved_searches,
}

View file

@ -67,8 +67,8 @@ class IconImporter(object):
self.feed_icon.icon_url != icon_url or
self.feed_icon.not_found or
(settings.BACKED_BY_AWS.get('icons_on_s3') and not self.feed.s3_icon))):
logging.debug(" ---> [%-30s] ~SN~FBIcon difference:~FY color:%s (%s/%s) data:%s url:%s notfound:%s no-s3:%s" % (
self.feed,
logging.debug(" ---> [%-30s] ~SN~FBIcon difference:~FY color:%s (%s/%s) data:%s url:%s notfound:%s no-s3:%s" % (
self.feed.log_title[:30],
self.feed_icon.color != color, self.feed_icon.color, color,
self.feed_icon.data != image_str,
self.feed_icon.icon_url != icon_url,
@ -97,7 +97,7 @@ class IconImporter(object):
def save_to_s3(self, image_str):
expires = datetime.datetime.now() + datetime.timedelta(days=60)
expires = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")
k = Key(settings.S3_ICONS_BUCKET)
k = Key(settings.S3_CONN.get_bucket(settings.S3_ICONS_BUCKET_NAME))
k.key = self.feed.s3_icons_key
k.set_metadata('Content-Type', 'image/png')
k.set_metadata('Expires', expires)
@ -195,7 +195,7 @@ class IconImporter(object):
if self.page_data:
content = self.page_data
elif settings.BACKED_BY_AWS.get('pages_on_s3') and self.feed.s3_page:
key = settings.S3_PAGES_BUCKET.get_key(self.feed.s3_pages_key)
key = settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME).get_key(self.feed.s3_pages_key)
compressed_content = key.get_contents_as_string()
stream = StringIO(compressed_content)
gz = gzip.GzipFile(fileobj=stream)
@ -237,7 +237,10 @@ class IconImporter(object):
if not force:
url = self.feed_icon.icon_url
if not url and self.feed.feed_link and len(self.feed.feed_link) > 6:
url = urlparse.urljoin(self.feed.feed_link, 'favicon.ico')
try:
url = urlparse.urljoin(self.feed.feed_link, 'favicon.ico')
except ValueError:
url = None
if not url:
return None, None, None

View file

@ -95,13 +95,14 @@ class Feed(models.Model):
if not self.feed_title:
self.feed_title = "[Untitled]"
self.save()
return "%s (%s - %s/%s/%s)%s" % (
self.feed_title,
return "%s%s: %s - %s/%s/%s" % (
self.pk,
(" [B: %s]" % self.branch_from_feed.pk if self.branch_from_feed else ""),
self.feed_title,
self.num_subscribers,
self.active_subscribers,
self.active_premium_subscribers,
(" [B: %s]" % self.branch_from_feed.pk if self.branch_from_feed else ""))
)
@property
def title(self):
@ -110,6 +111,10 @@ class Feed(models.Model):
title = "%s*" % title[:29]
return title
@property
def log_title(self):
return self.__unicode__()
@property
def permalink(self):
return "%s/site/%s/%s" % (settings.NEWSBLUR_URL, self.pk, slugify(self.feed_title.lower()[:50]))
@ -246,7 +251,7 @@ class Feed(models.Model):
if not duplicate_feeds:
# Feed has been deleted. Just ignore it.
logging.debug(" ***> Changed to: %s - %s: %s" % (self.feed_address, self.feed_link, duplicate_feeds))
logging.debug(' ***> [%-30s] Feed deleted (%s).' % (unicode(self)[:30], self.pk))
logging.debug(' ***> [%-30s] Feed deleted (%s).' % (self.log_title[:30], self.pk))
return
for duplicate_feed in duplicate_feeds:
@ -602,17 +607,18 @@ class Feed(models.Model):
try:
feed_address, feed = _1()
except TimeoutError, e:
logging.debug(' ---> [%-30s] Feed address check timed out...' % (unicode(self)[:30]))
logging.debug(' ---> [%-30s] Feed address check timed out...' % (self.log_title[:30]))
self.save_feed_history(505, 'Timeout', e)
feed = self
feed_address = None
return bool(feed_address), feed
def save_feed_history(self, status_code, message, exception=None):
def save_feed_history(self, status_code, message, exception=None, date=None):
fetch_history = MFetchHistory.add(feed_id=self.pk,
fetch_type='feed',
code=int(status_code),
date=date,
message=message,
exception=exception)
@ -626,10 +632,11 @@ class Feed(models.Model):
self.active = True
self.save()
def save_page_history(self, status_code, message, exception=None):
def save_page_history(self, status_code, message, exception=None, date=None):
fetch_history = MFetchHistory.add(feed_id=self.pk,
fetch_type='page',
code=int(status_code),
date=date,
message=message,
exception=exception)
@ -640,6 +647,13 @@ class Feed(models.Model):
self.has_page = True
self.active = True
self.save()
def save_raw_feed(self, raw_feed, fetch_date):
MFetchHistory.add(feed_id=self.pk,
fetch_type='raw_feed',
code=200,
message=raw_feed,
date=fetch_date)
def count_errors_in_history(self, exception_type='feed', status_code=None, fetch_history=None):
if not fetch_history:
@ -667,12 +681,12 @@ class Feed(models.Model):
self.save()
logging.debug(' ---> [%-30s] ~FBCounting any errors in history: %s (%s non errors)' %
(unicode(self)[:30], len(errors), len(non_errors)))
(self.log_title[:30], len(errors), len(non_errors)))
return errors, non_errors
def count_redirects_in_history(self, fetch_type='feed', fetch_history=None):
logging.debug(' ---> [%-30s] Counting redirects in history...' % (unicode(self)[:30]))
logging.debug(' ---> [%-30s] Counting redirects in history...' % (self.log_title[:30]))
if not fetch_history:
fetch_history = MFetchHistory.feed(self.pk)
fh = fetch_history[fetch_type+'_fetch_history']
@ -703,7 +717,7 @@ class Feed(models.Model):
return True
elif last_recount:
logging.info(" ---> [%-30s] ~SN~FBFeed has expired redis subscriber counts (%s < %s), clearing..." % (
unicode(self)[:30], last_recount, subscriber_expire))
self.log_title[:30], last_recount, subscriber_expire))
r.delete(total_key, -1)
r.delete(premium_key, -1)
@ -753,7 +767,7 @@ class Feed(models.Model):
original_premium_subscribers = self.premium_subscribers
original_active_premium_subscribers = self.active_premium_subscribers
logging.info(" ---> [%-30s] ~SN~FBCounting subscribers from ~FCredis~FB: ~FMt:~SB~FM%s~SN a:~SB%s~SN p:~SB%s~SN ap:~SB%s ~SN~FC%s" %
(self.title[:30], total, active, premium, active_premium, "(%s branches)" % (len(feed_ids)-1) if len(feed_ids)>1 else ""))
(self.log_title[:30], total, active, premium, active_premium, "(%s branches)" % (len(feed_ids)-1) if len(feed_ids)>1 else ""))
else:
from apps.reader.models import UserSubscription
@ -786,7 +800,7 @@ class Feed(models.Model):
original_active_premium_subscribers = self.active_premium_subscribers
active_premium = active_premium_subscribers.count()
logging.debug(" ---> [%-30s] ~SN~FBCounting subscribers from ~FYpostgres~FB: ~FMt:~SB~FM%s~SN a:~SB%s~SN p:~SB%s~SN ap:~SB%s" %
(self.title[:30], total, active, premium, active_premium))
(self.log_title[:30], total, active, premium, active_premium))
# If any counts have changed, save them
self.num_subscribers = total
@ -1148,7 +1162,7 @@ class Feed(models.Model):
if settings.DEBUG or verbose:
logging.debug(" ---> [%-30s] ~FBChecking ~SB%s~SN new/updated against ~SB%s~SN stories" % (
self.title[:30],
self.log_title[:30],
len(stories),
len(existing_stories.keys())))
@timelimit(2)
@ -1160,7 +1174,7 @@ class Feed(models.Model):
for story in stories:
if verbose:
logging.debug(" ---> [%-30s] ~FBChecking ~SB%s~SN / ~SB%s" % (
self.title[:30],
self.log_title[:30],
story.get('title'),
story.get('guid')))
if not story.get('title'):
@ -1179,7 +1193,7 @@ class Feed(models.Model):
existing_story, story_has_changed = _1(story, story_content,
existing_stories, new_story_hashes)
except TimeoutError, e:
logging.debug(' ---> [%-30s] ~SB~FRExisting story check timed out...' % (unicode(self)[:30]))
logging.debug(' ---> [%-30s] ~SB~FRExisting story check timed out...' % (self.log_title[:30]))
existing_story = None
story_has_changed = False
@ -1424,9 +1438,9 @@ class Feed(models.Model):
original_cutoff = cutoff
cutoff = min(cutoff, 10)
try:
logging.debug(" ---> [%-30s] ~FBTrimming down to ~SB%s (instead of %s)~SN stories (~FM%s~FB)" % (self, cutoff, original_cutoff, self.last_story_date.strftime("%Y-%m-%d") if self.last_story_date else "No last story date"))
logging.debug(" ---> [%-30s] ~FBTrimming down to ~SB%s (instead of %s)~SN stories (~FM%s~FB)" % (self.log_title[:30], cutoff, original_cutoff, self.last_story_date.strftime("%Y-%m-%d") if self.last_story_date else "No last story date"))
except ValueError, e:
logging.debug(" ***> [%-30s] Error trimming: %s" % (self, e))
logging.debug(" ***> [%-30s] Error trimming: %s" % (self.log_title[:30], e))
pass
return cutoff
@ -2035,7 +2049,7 @@ class Feed(models.Model):
if verbose:
logging.debug(" ---> [%-30s] Fetched every %s min - Subs: %s/%s/%s Stories/day: %s" % (
unicode(self)[:30], total,
self.log_title[:30], total,
self.num_subscribers,
self.active_subscribers,
self.active_premium_subscribers,
@ -2053,7 +2067,7 @@ class Feed(models.Model):
if verbose:
logging.debug(' ---> [%-30s] ~FBScheduling feed fetch geometrically: '
'~SB%s errors. Time: %s min' % (
unicode(self)[:30], self.errors_since_good, total))
self.log_title[:30], self.errors_since_good, total))
random_factor = random.randint(0, total) / 4
next_scheduled_update = datetime.datetime.utcnow() + datetime.timedelta(
@ -2085,11 +2099,11 @@ class Feed(models.Model):
def schedule_feed_fetch_immediately(self, verbose=True):
r = redis.Redis(connection_pool=settings.REDIS_FEED_UPDATE_POOL)
if not self.num_subscribers:
logging.debug(' ---> [%-30s] Not scheduling feed fetch immediately, no subs.' % (unicode(self)[:30]))
logging.debug(' ---> [%-30s] Not scheduling feed fetch immediately, no subs.' % (self.log_title[:30]))
return
if verbose:
logging.debug(' ---> [%-30s] Scheduling feed fetch immediately...' % (unicode(self)[:30]))
logging.debug(' ---> [%-30s] Scheduling feed fetch immediately...' % (self.log_title[:30]))
self.next_scheduled_update = datetime.datetime.utcnow()
r.zadd('scheduled_updates', self.pk, self.next_scheduled_update.strftime('%s'))
@ -2116,7 +2130,7 @@ class Feed(models.Model):
if queue_size > 1000:
self.schedule_feed_fetch_immediately()
else:
logging.debug(' ---> [%-30s] [%s] ~FB~SBQueuing pushed stories, last pushed %s...' % (unicode(self)[:30], self.pk, latest_push_date_delta))
logging.debug(' ---> [%-30s] [%s] ~FB~SBQueuing pushed stories, last pushed %s...' % (self.log_title[:30], self.pk, latest_push_date_delta))
self.set_next_scheduled_update()
PushFeeds.apply_async(args=(self.pk, xml), queue='push_feeds')
@ -2575,7 +2589,7 @@ class MStory(mongo.Document):
r.delete('zF:%s' % story_feed_id)
# r2.delete('zF:%s' % story_feed_id)
logging.info(" ---> [%-30s] ~FMSyncing ~SB%s~SN stories to redis" % (feed and feed.title[:30] or story_feed_id, stories.count()))
logging.info(" ---> [%-30s] ~FMSyncing ~SB%s~SN stories to redis" % (feed and feed.log_title[:30] or story_feed_id, stories.count()))
p = r.pipeline()
# p2 = r2.pipeline()
for story in stories:
@ -2597,24 +2611,35 @@ class MStory(mongo.Document):
self.share_user_ids = [s['user_id'] for s in shares]
self.save()
def extract_image_urls(self, force=False):
if self.image_urls and not force:
def extract_image_urls(self, force=False, text=False):
if self.image_urls and not force and not text:
return self.image_urls
story_content = self.story_content
if not story_content and self.story_content_z:
story_content = zlib.decompress(self.story_content_z)
story_content = None
if not text:
story_content = self.story_content
if not story_content and self.story_content_z:
story_content = zlib.decompress(self.story_content_z)
elif text:
if self.original_text_z:
story_content = zlib.decompress(self.original_text_z)
if not story_content:
return
try:
soup = BeautifulSoup(story_content)
except ValueError:
return
if not text:
return self.extract_image_urls(force=force, text=True)
else:
return
images = soup.findAll('img')
if not images:
return
if not text:
return self.extract_image_urls(force=force, text=True)
else:
return
image_urls = []
for image in images:
@ -2626,8 +2651,11 @@ class MStory(mongo.Document):
image_urls.append(image_url)
if not image_urls:
return
if not text:
return self.extract_image_urls(force=force, text=True)
else:
return
self.image_urls = image_urls
return self.image_urls
@ -2638,6 +2666,8 @@ class MStory(mongo.Document):
feed = Feed.get_by_id(self.story_feed_id)
ti = TextImporter(self, feed=feed, request=request, debug=debug)
original_text = ti.fetch()
self.extract_image_urls(force=force, text=True)
self.save()
else:
logging.user(request, "~FYFetching ~FGoriginal~FY story text, ~SBfound.")
original_text = zlib.decompress(original_text_z)
@ -2921,12 +2951,81 @@ class MStarredStoryCounts(mongo.Document):
if story_count and story_count.count <= 0:
story_count.delete()
class MSavedSearch(mongo.Document):
user_id = mongo.IntField()
query = mongo.StringField(max_length=1024)
feed_id = mongo.StringField()
slug = mongo.StringField(max_length=128)
meta = {
'collection': 'saved_searches',
'indexes': ['user_id',
{'fields': ['user_id', 'feed_id', 'query'],
'unique': True,
'types': False, }],
'ordering': ['query'],
'allow_inheritance': False,
}
@property
def rss_url(self, secret_token=None):
if not secret_token:
user = User.objects.select_related('profile').get(pk=self.user_id)
secret_token = user.profile.secret_token
slug = self.slug if self.slug else ""
return "%s/reader/saved_search/%s/%s/%s" % (settings.NEWSBLUR_URL, self.user_id,
secret_token, slug)
@classmethod
def user_searches(cls, user_id):
searches = cls.objects.filter(user_id=user_id)
searches = sorted([{'query': s.query,
'feed_address': s.rss_url,
'feed_id': s.feed_id,
} for s in searches],
key=lambda x: (x.get('query', '') or '').lower())
return searches
@classmethod
def save_search(cls, user_id, feed_id, query):
user = User.objects.get(pk=user_id)
params = dict(user_id=user_id,
feed_id=feed_id,
query=query,
slug=slugify(query))
try:
saved_search = cls.objects.get(**params)
logging.user(user, "~FRSaved search already exists: ~SB%s" % query)
except cls.DoesNotExist:
logging.user(user, "~FCCreating a saved search: ~SB%s~SN/~SB%s" % (feed_id, query))
saved_search = cls.objects.create(**params)
return saved_search
@classmethod
def delete_search(cls, user_id, feed_id, query):
user = User.objects.get(pk=user_id)
params = dict(user_id=user_id,
feed_id=feed_id,
query=query)
try:
saved_search = cls.objects.get(**params)
logging.user(user, "~FCDeleting saved search: ~SB%s" % query)
saved_search.delete()
except cls.DoesNotExist:
logging.user(user, "~FRCan't delete saved search, missing: ~SB%s~SN/~SB%s" % (feed_id, query))
except cls.MultipleObjectsReturned:
logging.user(user, "~FRFound multiple saved searches, deleting: ~SB%s~SN/~SB%s" % (feed_id, query))
cls.objects(**params).delete()
class MFetchHistory(mongo.Document):
feed_id = mongo.IntField(unique=True)
feed_fetch_history = mongo.DynamicField()
page_fetch_history = mongo.DynamicField()
push_history = mongo.DynamicField()
raw_feed_history = mongo.DynamicField()
meta = {
'db_alias': 'nbanalytics',
@ -2974,11 +3073,15 @@ class MFetchHistory(mongo.Document):
history = fetch_history.page_fetch_history or []
elif fetch_type == 'push':
history = fetch_history.push_history or []
elif fetch_type == 'raw_feed':
history = fetch_history.raw_feed_history or []
history = [[date, code, message]] + history
any_exceptions = any([c for d, c, m in history if c not in [200, 304]])
if any_exceptions:
history = history[:25]
elif fetch_type == 'raw_feed':
history = history[:10]
else:
history = history[:5]
@ -2988,6 +3091,8 @@ class MFetchHistory(mongo.Document):
fetch_history.page_fetch_history = history
elif fetch_type == 'push':
fetch_history.push_history = history
elif fetch_type == 'raw_feed':
fetch_history.raw_feed_history = history
fetch_history.save()

View file

@ -63,7 +63,7 @@ class PageImporter(object):
try:
self.fetch_page_timeout(urllib_fallback=urllib_fallback, requests_exception=requests_exception)
except TimeoutError:
logging.user(self.request, ' ***> [%-30s] ~FBPage fetch ~SN~FRfailed~FB due to timeout' % (self.feed))
logging.user(self.request, ' ***> [%-30s] ~FBPage fetch ~SN~FRfailed~FB due to timeout' % (self.feed.log_title[:30]))
@timelimit(10)
def fetch_page_timeout(self, urllib_fallback=False, requests_exception=None):
@ -95,7 +95,7 @@ class PageImporter(object):
except requests.exceptions.TooManyRedirects:
response = requests.get(feed_link)
except (AttributeError, SocketError, OpenSSLError, PyAsn1Error, TypeError), e:
logging.debug(' ***> [%-30s] Page fetch failed using requests: %s' % (self.feed, e))
logging.debug(' ***> [%-30s] Page fetch failed using requests: %s' % (self.feed.log_title[:30], e))
self.save_no_page()
return
# try:
@ -127,14 +127,14 @@ class PageImporter(object):
fp = feedparser.parse(self.feed.feed_address)
feed_link = fp.feed.get('link', "")
self.feed.save()
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed, e))
logging.debug(' ***> [%-30s] Page fetch failed: %s' % (self.feed.log_title[:30], e))
except (urllib2.HTTPError), e:
self.feed.save_page_history(e.code, e.msg, e.fp.read())
except (httplib.IncompleteRead), e:
self.feed.save_page_history(500, "IncompleteRead", e)
except (requests.exceptions.RequestException,
requests.packages.urllib3.exceptions.HTTPError), e:
logging.debug(' ***> [%-30s] Page fetch failed using requests: %s' % (self.feed, e))
logging.debug(' ***> [%-30s] Page fetch failed using requests: %s' % (self.feed.log_title[:30], e))
# mail_feed_error_to_admin(self.feed, e, local_vars=locals())
return self.fetch_page(urllib_fallback=True, requests_exception=e)
except Exception, e:
@ -183,7 +183,7 @@ class PageImporter(object):
except requests.exceptions.TooManyRedirects:
response = requests.get(story_permalink)
except (AttributeError, SocketError, OpenSSLError, PyAsn1Error, requests.exceptions.ConnectionError), e:
logging.debug(' ***> [%-30s] Original story fetch failed using requests: %s' % (self.feed, e))
logging.debug(' ***> [%-30s] Original story fetch failed using requests: %s' % (self.feed.log_title[:30], e))
return
try:
data = response.text
@ -213,7 +213,7 @@ class PageImporter(object):
def save_no_page(self):
logging.debug(' ---> [%-30s] ~FYNo original page: %s' % (self.feed, self.feed.feed_link))
logging.debug(' ---> [%-30s] ~FYNo original page: %s' % (self.feed.log_title[:30], self.feed.feed_link))
self.feed.has_page = False
self.feed.save()
self.feed.save_page_history(404, "Feed has no original page.")
@ -275,7 +275,7 @@ class PageImporter(object):
feed_page = MFeedPage.objects.get(feed_id=self.feed.pk)
# feed_page.page_data = html.encode('utf-8')
if feed_page.page() == html:
logging.debug(' ---> [%-30s] ~FYNo change in page data: %s' % (self.feed.title[:30], self.feed.feed_link))
logging.debug(' ---> [%-30s] ~FYNo change in page data: %s' % (self.feed.log_title[:30], self.feed.feed_link))
else:
feed_page.page_data = html
feed_page.save()
@ -296,7 +296,7 @@ class PageImporter(object):
return True
def save_page_s3(self, html):
k = Key(settings.S3_PAGES_BUCKET)
k = Key(settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME))
k.key = self.feed.s3_pages_key
k.set_metadata('Content-Encoding', 'gzip')
k.set_metadata('Content-Type', 'text/html')
@ -307,7 +307,7 @@ class PageImporter(object):
try:
feed_page = MFeedPage.objects.get(feed_id=self.feed.pk)
feed_page.delete()
logging.debug(' ---> [%-30s] ~FYTransfering page data to S3...' % (self.feed))
logging.debug(' ---> [%-30s] ~FYTransfering page data to S3...' % (self.feed.log_title[:30]))
except MFeedPage.DoesNotExist:
pass
@ -318,7 +318,7 @@ class PageImporter(object):
return True
def delete_page_s3(self):
k = Key(settings.S3_PAGES_BUCKET)
k = Key(settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME))
k.key = self.feed.s3_pages_key
k.delete()

View file

@ -10,6 +10,7 @@ from utils.feed_functions import timelimit, TimeoutError
from OpenSSL.SSL import Error as OpenSSLError
from pyasn1.error import PyAsn1Error
from django.utils.encoding import smart_str
from BeautifulSoup import BeautifulSoup
BROKEN_URLS = [
"gamespot.com",
@ -87,13 +88,17 @@ class TextImporter:
except TypeError:
title = ""
url = resp.url
if content:
content = self.rewrite_content(content)
if content:
if self.story and not skip_save:
self.story.original_text_z = zlib.compress(smart_str(content))
try:
self.story.save()
except NotUniqueError:
except NotUniqueError, e:
logging.user(self.request, ("~SN~FYFetched ~FGoriginal text~FY: %s" % (e)), warn_color=False)
pass
logging.user(self.request, ("~SN~FYFetched ~FGoriginal text~FY: now ~SB%s bytes~SN vs. was ~SB%s bytes" % (
len(content),
@ -109,6 +114,15 @@ class TextImporter:
return content
def rewrite_content(self, content):
soup = BeautifulSoup(content)
for noscript in soup.findAll('noscript'):
if len(noscript.contents) > 0:
noscript.replaceWith(noscript.contents[0])
return unicode(soup)
@timelimit(10)
def fetch_request(self):
url = self.story_url

View file

@ -489,7 +489,6 @@ def status(request):
'feeds': feeds
}, context_instance=RequestContext(request))
@required_params('story_id', feed_id=int)
@json.json_view
def original_text(request):
story_id = request.REQUEST.get('story_id')
@ -510,8 +509,9 @@ def original_text(request):
original_text = story.fetch_original_text(force=force, request=request, debug=debug)
return {
'feed_id': feed_id,
'story_id': story_id,
'feed_id': story.story_feed_id,
'story_hash': story.story_hash,
'story_id': story.story_guid,
'original_text': original_text,
'failed': not original_text or len(original_text) < 100,
}

View file

@ -189,7 +189,8 @@ def load_river_blurblog(request):
limit = 10
start = time.time()
user = get_user(request)
social_user_ids = [int(uid) for uid in request.REQUEST.getlist('social_user_ids') if uid]
social_user_ids = request.REQUEST.getlist('social_user_ids') or request.REQUEST.getlist('social_user_ids[]')
social_user_ids = [int(uid) for uid in social_user_ids if uid]
original_user_ids = list(social_user_ids)
page = int(request.REQUEST.get('page', 1))
order = request.REQUEST.get('order', 'newest')
@ -546,7 +547,7 @@ def mark_story_as_shared(request):
comments = request.POST.get('comments', '')
source_user_id = request.POST.get('source_user_id')
relative_user_id = request.POST.get('relative_user_id') or request.user.pk
post_to_services = request.POST.getlist('post_to_services')
post_to_services = request.POST.getlist('post_to_services') or request.POST.getlist('post_to_services[]')
format = request.REQUEST.get('format', 'json')
now = datetime.datetime.now()
nowtz = localtime_for_timezone(now, request.user.profile.timezone)
@ -574,7 +575,7 @@ def mark_story_as_shared(request):
'message': 'Only premium users can share multiple stories per day from the same site.'
})
quota = 50
quota = 100
if not request.user.profile.is_premium:
quota = 3
if MSharedStory.feed_quota(request.user.pk, story.story_hash, quota=quota):
@ -909,7 +910,7 @@ def shared_stories_public(request, username):
def profile(request):
user = get_user(request.user)
user_id = int(request.GET.get('user_id', user.pk))
categories = request.GET.getlist('category')
categories = request.GET.getlist('category') or request.GET.getlist('category[]')
include_activities_html = request.REQUEST.get('include_activities_html', None)
user_profile = MSocialProfile.get_user(user_id)
@ -1422,7 +1423,7 @@ def load_social_settings(request, social_user_id, username=None):
@ajax_login_required
def load_interactions(request):
user_id = request.REQUEST.get('user_id', None)
categories = request.GET.getlist('category')
categories = request.GET.getlist('category') or request.GET.getlist('category[]')
if not user_id or 'null' in user_id:
user_id = get_user(request).pk
page = max(1, int(request.REQUEST.get('page', 1)))
@ -1448,7 +1449,7 @@ def load_interactions(request):
@ajax_login_required
def load_activities(request):
user_id = request.REQUEST.get('user_id', None)
categories = request.GET.getlist('category')
categories = request.GET.getlist('category') or request.GET.getlist('category[]')
if user_id and 'null' not in user_id:
user_id = int(user_id)
user = User.objects.get(pk=user_id)

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.newsblur"
android:versionCode="135"
android:versionName="5.2.0_b2" >
android:versionCode="136"
android:versionName="5.2.0b1" >
<uses-sdk
android:minSdkVersion="16"

View file

@ -1,35 +0,0 @@
//
// ASIAuthenticationDialog.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 21/08/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class ASIHTTPRequest;
typedef enum _ASIAuthenticationType {
ASIStandardAuthenticationType = 0,
ASIProxyAuthenticationType = 1
} ASIAuthenticationType;
@interface ASIAutorotatingViewController : UIViewController
@end
@interface ASIAuthenticationDialog : ASIAutorotatingViewController <UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
ASIHTTPRequest *request;
ASIAuthenticationType type;
UITableView *tableView;
UIViewController *presentingController;
BOOL didEnableRotationNotifications;
}
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)request;
+ (void)dismiss;
@property (atomic, retain) ASIHTTPRequest *request;
@property (atomic, assign) ASIAuthenticationType type;
@property (atomic, assign) BOOL didEnableRotationNotifications;
@property (retain, nonatomic) UIViewController *presentingController;
@end

View file

@ -1,521 +0,0 @@
//
// ASIAuthenticationDialog.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 21/08/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIAuthenticationDialog.h"
#import "ASIHTTPRequest.h"
#import <QuartzCore/QuartzCore.h>
static ASIAuthenticationDialog *sharedDialog = nil;
BOOL isDismissing = NO;
static NSMutableArray *requestsNeedingAuthentication = nil;
static const NSUInteger kUsernameRow = 0;
static const NSUInteger kUsernameSection = 0;
static const NSUInteger kPasswordRow = 1;
static const NSUInteger kPasswordSection = 0;
static const NSUInteger kDomainRow = 0;
static const NSUInteger kDomainSection = 1;
@implementation ASIAutorotatingViewController
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
@end
@interface ASIAuthenticationDialog ()
- (void)showTitle;
- (void)show;
- (NSArray *)requestsRequiringTheseCredentials;
- (void)presentNextDialog;
- (void)keyboardWillShow:(NSNotification *)notification;
- (void)orientationChanged:(NSNotification *)notification;
- (void)cancelAuthenticationFromDialog:(id)sender;
- (void)loginWithCredentialsFromDialog:(id)sender;
@property (atomic, retain) UITableView *tableView;
@end
@implementation ASIAuthenticationDialog
#pragma mark init / dealloc
+ (void)initialize
{
if (self == [ASIAuthenticationDialog class]) {
requestsNeedingAuthentication = [[NSMutableArray array] retain];
}
}
+ (void)presentAuthenticationDialogForRequest:(ASIHTTPRequest *)theRequest
{
// No need for a lock here, this will always be called on the main thread
if (!sharedDialog) {
sharedDialog = [[self alloc] init];
[sharedDialog setRequest:theRequest];
if ([theRequest authenticationNeeded] == ASIProxyAuthenticationNeeded) {
[sharedDialog setType:ASIProxyAuthenticationType];
} else {
[sharedDialog setType:ASIStandardAuthenticationType];
}
[sharedDialog show];
} else {
[requestsNeedingAuthentication addObject:theRequest];
}
}
- (id)init
{
if ((self = [self initWithNibName:nil bundle:nil])) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
#endif
if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[self setDidEnableRotationNotifications:YES];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
}
#endif
}
return self;
}
- (void)dealloc
{
if ([self didEnableRotationNotifications]) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[request release];
[tableView release];
[presentingController.view removeFromSuperview];
[presentingController release];
[super dealloc];
}
#pragma mark keyboard notifications
- (void)keyboardWillShow:(NSNotification *)notification
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
#else
NSValue *keyboardBoundsValue = [[notification userInfo] objectForKey:UIKeyboardBoundsUserInfoKey];
#endif
CGRect keyboardBounds;
[keyboardBoundsValue getValue:&keyboardBounds];
UIEdgeInsets e = UIEdgeInsetsMake(0, 0, keyboardBounds.size.height, 0);
[[self tableView] setScrollIndicatorInsets:e];
[[self tableView] setContentInset:e];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
}
#endif
}
// Manually handles orientation changes on iPhone
- (void)orientationChanged:(NSNotification *)notification
{
[self showTitle];
UIInterfaceOrientation o = (UIInterfaceOrientation)[[UIApplication sharedApplication] statusBarOrientation];
CGFloat angle = 0;
switch (o) {
case UIDeviceOrientationLandscapeLeft: angle = 90; break;
case UIDeviceOrientationLandscapeRight: angle = -90; break;
case UIDeviceOrientationPortraitUpsideDown: angle = 180; break;
default: break;
}
CGRect f = [[UIScreen mainScreen] applicationFrame];
// Swap the frame height and width if necessary
if (UIInterfaceOrientationIsLandscape(o)) {
CGFloat t;
t = f.size.width;
f.size.width = f.size.height;
f.size.height = t;
}
CGAffineTransform previousTransform = self.view.layer.affineTransform;
CGAffineTransform newTransform = CGAffineTransformMakeRotation((CGFloat)(angle * M_PI / 180.0));
// Reset the transform so we can set the size
self.view.layer.affineTransform = CGAffineTransformIdentity;
self.view.frame = (CGRect){ { 0, 0 }, f.size};
// Revert to the previous transform for correct animation
self.view.layer.affineTransform = previousTransform;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
// Set the new transform
self.view.layer.affineTransform = newTransform;
// Fix the view origin
self.view.frame = (CGRect){ { f.origin.x, f.origin.y },self.view.frame.size};
[UIView commitAnimations];
}
#pragma mark utilities
- (UIViewController *)presentingController
{
if (!presentingController) {
presentingController = [[ASIAutorotatingViewController alloc] initWithNibName:nil bundle:nil];
// Attach to the window, but don't interfere.
UIWindow *window = [[[UIApplication sharedApplication] windows] objectAtIndex:0];
[window addSubview:[presentingController view]];
[[presentingController view] setFrame:CGRectZero];
[[presentingController view] setUserInteractionEnabled:NO];
}
return presentingController;
}
- (UITextField *)textFieldInRow:(NSUInteger)row section:(NSUInteger)section
{
return [[[[[self tableView] cellForRowAtIndexPath:
[NSIndexPath indexPathForRow:(NSInteger)row inSection:(NSInteger)section]]
contentView] subviews] objectAtIndex:0];
}
- (UITextField *)usernameField
{
return [self textFieldInRow:kUsernameRow section:kUsernameSection];
}
- (UITextField *)passwordField
{
return [self textFieldInRow:kPasswordRow section:kPasswordSection];
}
- (UITextField *)domainField
{
return [self textFieldInRow:kDomainRow section:kDomainSection];
}
#pragma mark show / dismiss
+ (void)dismiss
{
UIViewController* dismisser = nil;
if ([sharedDialog respondsToSelector:@selector(presentingViewController)]){
dismisser = [sharedDialog presentingViewController];
}else{
dismisser = [sharedDialog parentViewController];
}
if([dismisser respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]){
[dismisser dismissViewControllerAnimated:YES completion:nil];
}else{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[dismisser dismissModalViewControllerAnimated:YES];
#pragma clang diagnostic pop
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self retain];
[sharedDialog release];
sharedDialog = nil;
[self performSelector:@selector(presentNextDialog) withObject:nil afterDelay:0];
[self release];
}
- (void)dismiss
{
if (self == sharedDialog) {
[[self class] dismiss];
} else {
UIViewController* dismisser = nil;
if ([self respondsToSelector:@selector(presentingViewController)]){
dismisser = [self presentingViewController];
}else{
dismisser = [self parentViewController];
}
if([dismisser respondsToSelector:@selector(dismissViewControllerAnimated:completion:)]){
[dismisser dismissViewControllerAnimated:YES completion:nil];
}else{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[dismisser dismissModalViewControllerAnimated:YES];
#pragma clang diagnostic pop
}
}
}
- (void)showTitle
{
UINavigationBar *navigationBar = [[[self view] subviews] objectAtIndex:0];
UINavigationItem *navItem = [[navigationBar items] objectAtIndex:0];
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
// Setup the title
if ([self type] == ASIProxyAuthenticationType) {
[navItem setPrompt:@"Login to this secure proxy server."];
} else {
[navItem setPrompt:@"Login to this secure server."];
}
} else {
[navItem setPrompt:nil];
}
[navigationBar sizeToFit];
CGRect f = [[self view] bounds];
f.origin.y = [navigationBar frame].size.height;
f.size.height -= f.origin.y;
[[self tableView] setFrame:f];
}
- (void)show
{
// Remove all subviews
UIView *v;
while ((v = [[[self view] subviews] lastObject])) {
[v removeFromSuperview];
}
// Setup toolbar
UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease];
[bar setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
UINavigationItem *navItem = [[[UINavigationItem alloc] init] autorelease];
bar.items = [NSArray arrayWithObject:navItem];
[[self view] addSubview:bar];
[self showTitle];
// Setup toolbar buttons
if ([self type] == ASIProxyAuthenticationType) {
[navItem setTitle:[[self request] proxyHost]];
} else {
[navItem setTitle:[[[self request] url] host]];
}
[navItem setLeftBarButtonItem:[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(cancelAuthenticationFromDialog:)] autorelease]];
[navItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Login" style:UIBarButtonItemStyleDone target:self action:@selector(loginWithCredentialsFromDialog:)] autorelease]];
// We show the login form in a table view, similar to Safari's authentication dialog
[bar sizeToFit];
CGRect f = [[self view] bounds];
f.origin.y = [bar frame].size.height;
f.size.height -= f.origin.y;
[self setTableView:[[[UITableView alloc] initWithFrame:f style:UITableViewStyleGrouped] autorelease]];
[[self tableView] setDelegate:self];
[[self tableView] setDataSource:self];
[[self tableView] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[[self view] addSubview:[self tableView]];
// Force reload the table content, and focus the first field to show the keyboard
[[self tableView] reloadData];
[[[[[self tableView] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].contentView subviews] objectAtIndex:0] becomeFirstResponder];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self setModalPresentationStyle:UIModalPresentationFormSheet];
}
#endif
if([[self presentingController] respondsToSelector:@selector(presentViewController:animated:completion:)]){
[[self presentingController] presentViewController:self animated:YES completion:nil];
}else{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[self presentingController] presentModalViewController:self animated:YES];
#pragma clang diagnostic pop
}
}
#pragma mark button callbacks
- (void)cancelAuthenticationFromDialog:(id)sender
{
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
[theRequest cancelAuthentication];
[requestsNeedingAuthentication removeObject:theRequest];
}
[self dismiss];
}
- (NSArray *)requestsRequiringTheseCredentials
{
NSMutableArray *requestsRequiringTheseCredentials = [NSMutableArray array];
NSURL *requestURL = [[self request] url];
for (ASIHTTPRequest *otherRequest in requestsNeedingAuthentication) {
NSURL *theURL = [otherRequest url];
if (([otherRequest authenticationNeeded] == [[self request] authenticationNeeded]) && [[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]] && ((![otherRequest authenticationRealm] && ![[self request] authenticationRealm]) || ([otherRequest authenticationRealm] && [[self request] authenticationRealm] && [[[self request] authenticationRealm] isEqualToString:[otherRequest authenticationRealm]]))) {
[requestsRequiringTheseCredentials addObject:otherRequest];
}
}
[requestsRequiringTheseCredentials addObject:[self request]];
return requestsRequiringTheseCredentials;
}
- (void)presentNextDialog
{
if ([requestsNeedingAuthentication count]) {
ASIHTTPRequest *nextRequest = [requestsNeedingAuthentication objectAtIndex:0];
[requestsNeedingAuthentication removeObjectAtIndex:0];
[[self class] presentAuthenticationDialogForRequest:nextRequest];
}
}
- (void)loginWithCredentialsFromDialog:(id)sender
{
for (ASIHTTPRequest *theRequest in [self requestsRequiringTheseCredentials]) {
NSString *username = [[self usernameField] text];
NSString *password = [[self passwordField] text];
if (username == nil) { username = @""; }
if (password == nil) { password = @""; }
if ([self type] == ASIProxyAuthenticationType) {
[theRequest setProxyUsername:username];
[theRequest setProxyPassword:password];
} else {
[theRequest setUsername:username];
[theRequest setPassword:password];
}
// Handle NTLM domains
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
NSString *domain = [[self domainField] text];
if ([self type] == ASIProxyAuthenticationType) {
[theRequest setProxyDomain:domain];
} else {
[theRequest setDomain:domain];
}
}
[theRequest retryUsingSuppliedCredentials];
[requestsNeedingAuthentication removeObject:theRequest];
}
[self dismiss];
}
#pragma mark table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
NSString *scheme = ([self type] == ASIStandardAuthenticationType) ? [[self request] authenticationScheme] : [[self request] proxyAuthenticationScheme];
if ([scheme isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeNTLM]) {
return 2;
}
return 1;
}
- (CGFloat)tableView:(UITableView *)aTableView heightForFooterInSection:(NSInteger)section
{
if (section == [self numberOfSectionsInTableView:aTableView]-1) {
return 30;
}
return 0;
}
- (CGFloat)tableView:(UITableView *)aTableView heightForHeaderInSection:(NSInteger)section
{
if (section == 0) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_3_2
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
return 54;
}
#endif
return 30;
}
return 0;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (section == 0) {
return [[self request] authenticationRealm];
}
return nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_0
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
#else
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,0,0) reuseIdentifier:nil] autorelease];
#endif
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
CGRect f = CGRectInset([cell bounds], 10, 10);
UITextField *textField = [[[UITextField alloc] initWithFrame:f] autorelease];
[textField setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
[textField setAutocorrectionType:UITextAutocorrectionTypeNo];
NSInteger s = [indexPath section];
NSInteger r = [indexPath row];
if (s == kUsernameSection && r == kUsernameRow) {
[textField setPlaceholder:@"User"];
} else if (s == kPasswordSection && r == kPasswordRow) {
[textField setPlaceholder:@"Password"];
[textField setSecureTextEntry:YES];
} else if (s == kDomainSection && r == kDomainRow) {
[textField setPlaceholder:@"Domain"];
}
[cell.contentView addSubview:textField];
return cell;
}
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 2;
} else {
return 1;
}
}
- (NSString *)tableView:(UITableView *)aTableView titleForFooterInSection:(NSInteger)section
{
if (section == [self numberOfSectionsInTableView:aTableView]-1) {
// If we're using Basic authentication and the connection is not using SSL, we'll show the plain text message
if ([[[self request] authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && ![[[[self request] url] scheme] isEqualToString:@"https"]) {
return @"Password will be sent in the clear.";
// We are using Digest, NTLM, or any scheme over SSL
} else {
return @"Password will be sent securely.";
}
}
return nil;
}
#pragma mark -
@synthesize request;
@synthesize type;
@synthesize tableView;
@synthesize didEnableRotationNotifications;
@synthesize presentingController;
@end

View file

@ -1,103 +0,0 @@
//
// ASICacheDelegate.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 01/05/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
@class ASIHTTPRequest;
// Cache policies control the behaviour of a cache and how requests use the cache
// When setting a cache policy, you can use a combination of these values as a bitmask
// For example: [request setCachePolicy:ASIAskServerIfModifiedCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy|ASIDoNotWriteToCacheCachePolicy];
// Note that some of the behaviours below are mutally exclusive - you cannot combine ASIAskServerIfModifiedWhenStaleCachePolicy and ASIAskServerIfModifiedCachePolicy, for example.
typedef enum _ASICachePolicy {
// The default cache policy. When you set a request to use this, it will use the cache's defaultCachePolicy
// ASIDownloadCache's default cache policy is 'ASIAskServerIfModifiedWhenStaleCachePolicy'
ASIUseDefaultCachePolicy = 0,
// Tell the request not to read from the cache
ASIDoNotReadFromCacheCachePolicy = 1,
// The the request not to write to the cache
ASIDoNotWriteToCacheCachePolicy = 2,
// Ask the server if there is an updated version of this resource (using a conditional GET) ONLY when the cached data is stale
ASIAskServerIfModifiedWhenStaleCachePolicy = 4,
// Always ask the server if there is an updated version of this resource (using a conditional GET)
ASIAskServerIfModifiedCachePolicy = 8,
// If cached data exists, use it even if it is stale. This means requests will not talk to the server unless the resource they are requesting is not in the cache
ASIOnlyLoadIfNotCachedCachePolicy = 16,
// If cached data exists, use it even if it is stale. If cached data does not exist, stop (will not set an error on the request)
ASIDontLoadCachePolicy = 32,
// Specifies that cached data may be used if the request fails. If cached data is used, the request will succeed without error. Usually used in combination with other options above.
ASIFallbackToCacheIfLoadFailsCachePolicy = 64
} ASICachePolicy;
// Cache storage policies control whether cached data persists between application launches (ASICachePermanentlyCacheStoragePolicy) or not (ASICacheForSessionDurationCacheStoragePolicy)
// Calling [ASIHTTPRequest clearSession] will remove any data stored using ASICacheForSessionDurationCacheStoragePolicy
typedef enum _ASICacheStoragePolicy {
ASICacheForSessionDurationCacheStoragePolicy = 0,
ASICachePermanentlyCacheStoragePolicy = 1
} ASICacheStoragePolicy;
@protocol ASICacheDelegate <NSObject>
@required
// Should return the cache policy that will be used when requests have their cache policy set to ASIUseDefaultCachePolicy
- (ASICachePolicy)defaultCachePolicy;
// Returns the date a cached response should expire on. Pass a non-zero max age to specify a custom date.
- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
// Updates cached response headers with a new expiry date. Pass a non-zero max age to specify a custom date.
- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
// Looks at the request's cache policy and any cached headers to determine if the cache data is still valid
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request;
// Removes cached data for a particular request
- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request;
// Should return YES if the cache considers its cached response current for the request
// Should return NO is the data is not cached, or (for example) if the cached headers state the request should have expired
- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request;
// Should store the response for the passed request in the cache
// When a non-zero maxAge is passed, it should be used as the expiry time for the cached response
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge;
// Removes cached data for a particular url
- (void)removeCachedDataForURL:(NSURL *)url;
// Should return an NSDictionary of cached headers for the passed URL, if it is stored in the cache
- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url;
// Should return the cached body of a response for the passed URL, if it is stored in the cache
- (NSData *)cachedResponseDataForURL:(NSURL *)url;
// Returns a path to the cached response data, if it exists
- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url;
// Returns a path to the cached response headers, if they url
- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url;
// Returns the location to use to store cached response headers for a particular request
- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request;
// Returns the location to use to store a cached response body for a particular request
- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request;
// Clear cached data stored for the passed storage policy
- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)cachePolicy;
@end

View file

@ -1,42 +0,0 @@
//
// ASIDataCompressor.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 17/08/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
// This is a helper class used by ASIHTTPRequest to handle deflating (compressing) data in memory and on disk
// You may also find it helpful if you need to deflate data and files yourself - see the class methods below
// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
#import <Foundation/Foundation.h>
#import <zlib.h>
@interface ASIDataCompressor : NSObject {
BOOL streamReady;
z_stream zStream;
}
// Convenience constructor will call setupStream for you
+ (id)compressor;
// Compress the passed chunk of data
// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish;
// Convenience method - pass it some data, and you'll get deflated data back
+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err;
// Convenience method - pass it a file containing the data to compress in sourcePath, and it will write deflated data to destinationPath
+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'compressor'
- (NSError *)setupStream;
// Tells zlib to clean up. You need to call this if you need to cancel deflating part way through
// If deflating finishes or fails, this method will be called automatically
- (NSError *)closeStream;
@property (atomic, assign, readonly) BOOL streamReady;
@end

View file

@ -1,219 +0,0 @@
//
// ASIDataCompressor.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 17/08/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
#import "ASIDataCompressor.h"
#import "ASIHTTPRequest.h"
#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
#define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION
@interface ASIDataCompressor ()
+ (NSError *)deflateErrorWithCode:(int)code;
@end
@implementation ASIDataCompressor
+ (id)compressor
{
ASIDataCompressor *compressor = [[[self alloc] init] autorelease];
[compressor setupStream];
return compressor;
}
- (void)dealloc
{
if (streamReady) {
[self closeStream];
}
[super dealloc];
}
- (NSError *)setupStream
{
if (streamReady) {
return nil;
}
// Setup the inflate stream
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
zStream.avail_in = 0;
zStream.next_in = 0;
int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
if (status != Z_OK) {
return [[self class] deflateErrorWithCode:status];
}
streamReady = YES;
return nil;
}
- (NSError *)closeStream
{
if (!streamReady) {
return nil;
}
// Close the deflate stream
streamReady = NO;
int status = deflateEnd(&zStream);
if (status != Z_OK) {
return [[self class] deflateErrorWithCode:status];
}
return nil;
}
- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish
{
if (length == 0) return nil;
NSUInteger halfLength = length/2;
// We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below
NSMutableData *outputData = [NSMutableData dataWithLength:length/2];
int status;
zStream.next_in = bytes;
zStream.avail_in = (unsigned int)length;
zStream.avail_out = 0;
NSUInteger bytesProcessedAlready = zStream.total_out;
while (zStream.avail_out == 0) {
if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
[outputData increaseLengthBy:halfLength];
}
zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH);
if (status == Z_STREAM_END) {
break;
} else if (status != Z_OK) {
if (err) {
*err = [[self class] deflateErrorWithCode:status];
}
return nil;
}
}
// Set real length
[outputData setLength: zStream.total_out-bytesProcessedAlready];
return outputData;
}
+ (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err
{
NSError *theError = nil;
NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES];
if (theError) {
if (err) {
*err = theError;
}
return nil;
}
return outputData;
}
+ (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
{
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
// Create an empty file at the destination path
if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
}
return NO;
}
// Ensure the source file exists
if (![fileManager fileExistsAtPath:sourcePath]) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
}
return NO;
}
UInt8 inputData[DATA_CHUNK_SIZE];
NSData *outputData;
NSInteger readLength;
NSError *theError = nil;
ASIDataCompressor *compressor = [ASIDataCompressor compressor];
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
[inputStream open];
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
[outputStream open];
while ([compressor streamReady]) {
// Read some data from the file
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
// Make sure nothing went wrong
if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
}
[compressor closeStream];
return NO;
}
// Have we reached the end of the input data?
if (!readLength) {
break;
}
// Attempt to deflate the chunk of data
outputData = [compressor compressBytes:inputData length:(NSUInteger)readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE];
if (theError) {
if (err) {
*err = theError;
}
[compressor closeStream];
return NO;
}
// Write the deflated data out to the destination file
[outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]];
// Make sure nothing went wrong
if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
}
[compressor closeStream];
return NO;
}
}
[inputStream close];
[outputStream close];
NSError *error = [compressor closeStream];
if (error) {
if (err) {
*err = error;
}
return NO;
}
return YES;
}
+ (NSError *)deflateErrorWithCode:(int)code
{
return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %d",code],NSLocalizedDescriptionKey,nil]];
}
@synthesize streamReady;
@end

View file

@ -1,41 +0,0 @@
//
// ASIDataDecompressor.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 17/08/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
// This is a helper class used by ASIHTTPRequest to handle inflating (decompressing) data in memory and on disk
// You may also find it helpful if you need to inflate data and files yourself - see the class methods below
// Most of the zlib stuff is based on the sample code by Mark Adler available at http://zlib.net
#import <Foundation/Foundation.h>
#import <zlib.h>
@interface ASIDataDecompressor : NSObject {
BOOL streamReady;
z_stream zStream;
}
// Convenience constructor will call setupStream for you
+ (id)decompressor;
// Uncompress the passed chunk of data
- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err;
// Convenience method - pass it some deflated data, and you'll get inflated data back
+ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err;
// Convenience method - pass it a file containing deflated data in sourcePath, and it will write inflated data to destinationPath
+ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err;
// Sets up zlib to handle the inflating. You only need to call this yourself if you aren't using the convenience constructor 'decompressor'
- (NSError *)setupStream;
// Tells zlib to clean up. You need to call this if you need to cancel inflating part way through
// If inflating finishes or fails, this method will be called automatically
- (NSError *)closeStream;
@property (atomic, assign, readonly) BOOL streamReady;
@end

View file

@ -1,218 +0,0 @@
//
// ASIDataDecompressor.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 17/08/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
#import "ASIDataDecompressor.h"
#import "ASIHTTPRequest.h"
#define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks
@interface ASIDataDecompressor ()
+ (NSError *)inflateErrorWithCode:(int)code;
@end;
@implementation ASIDataDecompressor
+ (id)decompressor
{
ASIDataDecompressor *decompressor = [[[self alloc] init] autorelease];
[decompressor setupStream];
return decompressor;
}
- (void)dealloc
{
if (streamReady) {
[self closeStream];
}
[super dealloc];
}
- (NSError *)setupStream
{
if (streamReady) {
return nil;
}
// Setup the inflate stream
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
zStream.avail_in = 0;
zStream.next_in = 0;
int status = inflateInit2(&zStream, (15+32));
if (status != Z_OK) {
return [[self class] inflateErrorWithCode:status];
}
streamReady = YES;
return nil;
}
- (NSError *)closeStream
{
if (!streamReady) {
return nil;
}
// Close the inflate stream
streamReady = NO;
int status = inflateEnd(&zStream);
if (status != Z_OK) {
return [[self class] inflateErrorWithCode:status];
}
return nil;
}
- (NSData *)uncompressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err
{
if (length == 0) return nil;
NSUInteger halfLength = length/2;
NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength];
int status;
zStream.next_in = bytes;
zStream.avail_in = (unsigned int)length;
zStream.avail_out = 0;
NSUInteger bytesProcessedAlready = zStream.total_out;
while (zStream.avail_in != 0) {
if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
[outputData increaseLengthBy:halfLength];
}
zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
status = inflate(&zStream, Z_NO_FLUSH);
if (status == Z_STREAM_END) {
break;
} else if (status != Z_OK) {
if (err) {
*err = [[self class] inflateErrorWithCode:status];
}
return nil;
}
}
// Set real length
[outputData setLength: zStream.total_out-bytesProcessedAlready];
return outputData;
}
+ (NSData *)uncompressData:(NSData*)compressedData error:(NSError **)err
{
NSError *theError = nil;
NSData *outputData = [[ASIDataDecompressor decompressor] uncompressBytes:(Bytef *)[compressedData bytes] length:[compressedData length] error:&theError];
if (theError) {
if (err) {
*err = theError;
}
return nil;
}
return outputData;
}
+ (BOOL)uncompressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
{
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
// Create an empty file at the destination path
if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
}
return NO;
}
// Ensure the source file exists
if (![fileManager fileExistsAtPath:sourcePath]) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
}
return NO;
}
UInt8 inputData[DATA_CHUNK_SIZE];
NSData *outputData;
NSInteger readLength;
NSError *theError = nil;
ASIDataDecompressor *decompressor = [ASIDataDecompressor decompressor];
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
[inputStream open];
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
[outputStream open];
while ([decompressor streamReady]) {
// Read some data from the file
readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
// Make sure nothing went wrong
if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
}
[decompressor closeStream];
return NO;
}
// Have we reached the end of the input data?
if (!readLength) {
break;
}
// Attempt to inflate the chunk of data
outputData = [decompressor uncompressBytes:inputData length:(NSUInteger)readLength error:&theError];
if (theError) {
if (err) {
*err = theError;
}
[decompressor closeStream];
return NO;
}
// Write the inflated data out to the destination file
[outputStream write:(Bytef*)[outputData bytes] maxLength:[outputData length]];
// Make sure nothing went wrong
if ([inputStream streamStatus] == NSStreamStatusError) {
if (err) {
*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
}
[decompressor closeStream];
return NO;
}
}
[inputStream close];
[outputStream close];
NSError *error = [decompressor closeStream];
if (error) {
if (err) {
*err = error;
}
return NO;
}
return YES;
}
+ (NSError *)inflateErrorWithCode:(int)code
{
return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %d",code],NSLocalizedDescriptionKey,nil]];
}
@synthesize streamReady;
@end

View file

@ -1,46 +0,0 @@
//
// ASIDownloadCache.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 01/05/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASICacheDelegate.h"
@interface ASIDownloadCache : NSObject <ASICacheDelegate> {
// The default cache policy for this cache
// Requests that store data in the cache will use this cache policy if their cache policy is set to ASIUseDefaultCachePolicy
// Defaults to ASIAskServerIfModifiedWhenStaleCachePolicy
ASICachePolicy defaultCachePolicy;
// The directory in which cached data will be stored
// Defaults to a directory called 'ASIHTTPRequestCache' in the temporary directory
NSString *storagePath;
// Mediates access to the cache
NSRecursiveLock *accessLock;
// When YES, the cache will look for cache-control / pragma: no-cache headers, and won't reuse store responses if it finds them
BOOL shouldRespectCacheControlHeaders;
}
// Returns a static instance of an ASIDownloadCache
// In most circumstances, it will make sense to use this as a global cache, rather than creating your own cache
// To make ASIHTTPRequests use it automatically, use [ASIHTTPRequest setDefaultCache:[ASIDownloadCache sharedCache]];
+ (id)sharedCache;
// A helper function that determines if the server has requested data should not be cached by looking at the request's response headers
+ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request;
// A list of file extensions that we know won't be readable by a webview when accessed locally
// If we're asking for a path to cache a particular url and it has one of these extensions, we change it to '.html'
+ (NSArray *)fileExtensionsToHandleAsHTML;
@property (assign, nonatomic) ASICachePolicy defaultCachePolicy;
@property (retain, nonatomic) NSString *storagePath;
@property (atomic, retain) NSRecursiveLock *accessLock;
@property (atomic, assign) BOOL shouldRespectCacheControlHeaders;
@end

View file

@ -1,514 +0,0 @@
//
// ASIDownloadCache.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 01/05/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
#import "ASIDownloadCache.h"
#import "ASIHTTPRequest.h"
#import <CommonCrypto/CommonHMAC.h>
static ASIDownloadCache *sharedCache = nil;
static NSString *sessionCacheFolder = @"SessionStore";
static NSString *permanentCacheFolder = @"PermanentStore";
static NSArray *fileExtensionsToHandleAsHTML = nil;
@interface ASIDownloadCache ()
+ (NSString *)keyForURL:(NSURL *)url;
- (NSString *)pathToFile:(NSString *)file;
@end
@implementation ASIDownloadCache
+ (void)initialize
{
if (self == [ASIDownloadCache class]) {
// Obviously this is not an exhaustive list, but hopefully these are the most commonly used and this will 'just work' for the widest range of people
// I imagine many web developers probably use url rewriting anyway
fileExtensionsToHandleAsHTML = [[NSArray alloc] initWithObjects:@"asp",@"aspx",@"jsp",@"php",@"rb",@"py",@"pl",@"cgi", nil];
}
}
- (id)init
{
self = [super init];
[self setShouldRespectCacheControlHeaders:YES];
[self setDefaultCachePolicy:ASIUseDefaultCachePolicy];
[self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]];
return self;
}
+ (id)sharedCache
{
if (!sharedCache) {
@synchronized(self) {
if (!sharedCache) {
sharedCache = [[self alloc] init];
[sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]];
}
}
}
return sharedCache;
}
- (void)dealloc
{
[storagePath release];
[accessLock release];
[super dealloc];
}
- (NSString *)storagePath
{
[[self accessLock] lock];
NSString *p = [[storagePath retain] autorelease];
[[self accessLock] unlock];
return p;
}
- (void)setStoragePath:(NSString *)path
{
[[self accessLock] lock];
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
[storagePath release];
storagePath = [path retain];
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
BOOL isDirectory = NO;
NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil];
for (NSString *directory in directories) {
BOOL exists = [fileManager fileExistsAtPath:directory isDirectory:&isDirectory];
if (exists && !isDirectory) {
[[self accessLock] unlock];
[NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory];
} else if (!exists) {
[fileManager createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil];
if (![fileManager fileExistsAtPath:directory]) {
[[self accessLock] unlock];
[NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory];
}
}
}
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
[[self accessLock] unlock];
}
- (void)updateExpiryForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
{
NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
NSMutableDictionary *cachedHeaders = [NSMutableDictionary dictionaryWithContentsOfFile:headerPath];
if (!cachedHeaders) {
return;
}
NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
if (!expires) {
return;
}
[cachedHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
[cachedHeaders writeToFile:headerPath atomically:NO];
}
- (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
{
return [ASIHTTPRequest expiryDateForRequest:request maxAge:maxAge];
}
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
{
[[self accessLock] lock];
if ([request error] || ![request responseHeaders] || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) {
[[self accessLock] unlock];
return;
}
// We only cache 200/OK or redirect reponses (redirect responses are cached so the cache works better with no internet connection)
int responseCode = [request responseStatusCode];
if (responseCode != 200 && responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) {
[[self accessLock] unlock];
return;
}
if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) {
[[self accessLock] unlock];
return;
}
NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request];
NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
if ([request isResponseCompressed]) {
[responseHeaders removeObjectForKey:@"Content-Encoding"];
}
// Create a special 'X-ASIHTTPRequest-Expires' header
// This is what we use for deciding if cached data is current, rather than parsing the expires / max-age headers individually each time
// We store this as a timestamp to make reading it easier as NSDateFormatter is quite expensive
NSDate *expires = [self expiryDateForRequest:request maxAge:maxAge];
if (expires) {
[responseHeaders setObject:[NSNumber numberWithDouble:[expires timeIntervalSince1970]] forKey:@"X-ASIHTTPRequest-Expires"];
}
// Store the response code in a custom header so we can reuse it later
// We'll change 304/Not Modified to 200/OK because this is likely to be us updating the cached headers with a conditional GET
int statusCode = [request responseStatusCode];
if (statusCode == 304) {
statusCode = 200;
}
[responseHeaders setObject:[NSNumber numberWithInt:statusCode] forKey:@"X-ASIHTTPRequest-Response-Status-Code"];
[responseHeaders writeToFile:headerPath atomically:NO];
if ([request responseData]) {
[[request responseData] writeToFile:dataPath atomically:NO];
} else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) {
NSError *error = nil;
NSFileManager* manager = [[NSFileManager alloc] init];
if ([manager fileExistsAtPath:dataPath]) {
[manager removeItemAtPath:dataPath error:&error];
}
[manager copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error];
[manager release];
}
[[self accessLock] unlock];
}
- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url
{
NSString *path = [self pathToCachedResponseHeadersForURL:url];
if (path) {
return [NSDictionary dictionaryWithContentsOfFile:path];
}
return nil;
}
- (NSData *)cachedResponseDataForURL:(NSURL *)url
{
NSString *path = [self pathToCachedResponseDataForURL:url];
if (path) {
return [NSData dataWithContentsOfFile:path];
}
return nil;
}
- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url
{
// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
NSString *extension = [[url path] pathExtension];
// If the url doesn't have an extension, we'll add one so a webview can read it when locally cached
// If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason
if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) {
extension = @"html";
}
return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
}
+ (NSArray *)fileExtensionsToHandleAsHTML
{
return fileExtensionsToHandleAsHTML;
}
- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url
{
return [self pathToFile:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]];
}
- (NSString *)pathToFile:(NSString *)file
{
[[self accessLock] lock];
if (![self storagePath]) {
[[self accessLock] unlock];
return nil;
}
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
// Look in the session store
NSString *dataPath = [[[self storagePath] stringByAppendingPathComponent:sessionCacheFolder] stringByAppendingPathComponent:file];
if ([fileManager fileExistsAtPath:dataPath]) {
[[self accessLock] unlock];
return dataPath;
}
// Look in the permanent store
dataPath = [[[self storagePath] stringByAppendingPathComponent:permanentCacheFolder] stringByAppendingPathComponent:file];
if ([fileManager fileExistsAtPath:dataPath]) {
[[self accessLock] unlock];
return dataPath;
}
[[self accessLock] unlock];
return nil;
}
- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request
{
[[self accessLock] lock];
if (![self storagePath]) {
[[self accessLock] unlock];
return nil;
}
NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view
NSString *extension = [[[request url] path] pathExtension];
// If the url doesn't have an extension, we'll add one so a webview can read it when locally cached
// If the url has the extension of a common web scripting language, we'll change the extension on the cached path to html for the same reason
if (![extension length] || [[[self class] fileExtensionsToHandleAsHTML] containsObject:[extension lowercaseString]]) {
extension = @"html";
}
path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]];
[[self accessLock] unlock];
return path;
}
- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request
{
[[self accessLock] lock];
if (![self storagePath]) {
[[self accessLock] unlock];
return nil;
}
NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
path = [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"cachedheaders"]];
[[self accessLock] unlock];
return path;
}
- (void)removeCachedDataForURL:(NSURL *)url
{
[[self accessLock] lock];
if (![self storagePath]) {
[[self accessLock] unlock];
return;
}
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
NSString *path = [self pathToCachedResponseHeadersForURL:url];
if (path) {
[fileManager removeItemAtPath:path error:NULL];
}
path = [self pathToCachedResponseDataForURL:url];
if (path) {
[fileManager removeItemAtPath:path error:NULL];
}
[[self accessLock] unlock];
}
- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request
{
[self removeCachedDataForURL:[request url]];
}
- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request
{
[[self accessLock] lock];
if (![self storagePath]) {
[[self accessLock] unlock];
return NO;
}
NSDictionary *cachedHeaders = [self cachedResponseHeadersForURL:[request url]];
if (!cachedHeaders) {
[[self accessLock] unlock];
return NO;
}
NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
if (!dataPath) {
[[self accessLock] unlock];
return NO;
}
// New content is not different
if ([request responseStatusCode] == 304) {
[[self accessLock] unlock];
return YES;
}
// If we already have response headers for this request, check to see if the new content is different
// We check [request complete] so that we don't end up comparing response headers from a redirection with these
if ([request responseHeaders] && [request complete]) {
// If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again
NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil];
for (NSString *header in headersToCompare) {
if (![[[request responseHeaders] objectForKey:header] isEqualToString:[cachedHeaders objectForKey:header]]) {
[[self accessLock] unlock];
return NO;
}
}
}
if ([self shouldRespectCacheControlHeaders]) {
// Look for X-ASIHTTPRequest-Expires header to see if the content is out of date
NSNumber *expires = [cachedHeaders objectForKey:@"X-ASIHTTPRequest-Expires"];
if (expires) {
if ([[NSDate dateWithTimeIntervalSince1970:[expires doubleValue]] timeIntervalSinceNow] >= 0) {
[[self accessLock] unlock];
return YES;
}
}
// No explicit expiration time sent by the server
[[self accessLock] unlock];
return NO;
}
[[self accessLock] unlock];
return YES;
}
- (ASICachePolicy)defaultCachePolicy
{
[[self accessLock] lock];
ASICachePolicy cp = defaultCachePolicy;
[[self accessLock] unlock];
return cp;
}
- (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy
{
[[self accessLock] lock];
if (!cachePolicy) {
defaultCachePolicy = ASIAskServerIfModifiedWhenStaleCachePolicy;
} else {
defaultCachePolicy = cachePolicy;
}
[[self accessLock] unlock];
}
- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)storagePolicy
{
[[self accessLock] lock];
if (![self storagePath]) {
[[self accessLock] unlock];
return;
}
NSString *path = [[self storagePath] stringByAppendingPathComponent:(storagePolicy == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
BOOL isDirectory = NO;
BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
if (!exists || !isDirectory) {
[[self accessLock] unlock];
return;
}
NSError *error = nil;
NSArray *cacheFiles = [fileManager contentsOfDirectoryAtPath:path error:&error];
if (error) {
[[self accessLock] unlock];
[NSException raise:@"FailedToTraverseCacheDirectory" format:@"Listing cache directory failed at path '%@'",path];
}
for (NSString *file in cacheFiles) {
[fileManager removeItemAtPath:[path stringByAppendingPathComponent:file] error:&error];
if (error) {
[[self accessLock] unlock];
[NSException raise:@"FailedToRemoveCacheFile" format:@"Failed to remove cached data at path '%@'",path];
}
}
[[self accessLock] unlock];
}
+ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request
{
NSString *cacheControl = [[[request responseHeaders] objectForKey:@"Cache-Control"] lowercaseString];
if (cacheControl) {
if ([cacheControl isEqualToString:@"no-cache"] || [cacheControl isEqualToString:@"no-store"]) {
return NO;
}
}
NSString *pragma = [[[request responseHeaders] objectForKey:@"Pragma"] lowercaseString];
if (pragma) {
if ([pragma isEqualToString:@"no-cache"]) {
return NO;
}
}
return YES;
}
+ (NSString *)keyForURL:(NSURL *)url
{
NSString *urlString = [url absoluteString];
if ([urlString length] == 0) {
return nil;
}
// Strip trailing slashes so http://allseeing-i.com/ASIHTTPRequest/ is cached the same as http://allseeing-i.com/ASIHTTPRequest
if ([[urlString substringFromIndex:[urlString length]-1] isEqualToString:@"/"]) {
urlString = [urlString substringToIndex:[urlString length]-1];
}
// Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa
const char *cStr = [urlString UTF8String];
unsigned char result[16];
CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]];
}
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request
{
// Ensure the request is allowed to read from the cache
if ([request cachePolicy] & ASIDoNotReadFromCacheCachePolicy) {
return NO;
// If we don't want to load the request whatever happens, always pretend we have cached data even if we don't
} else if ([request cachePolicy] & ASIDontLoadCachePolicy) {
return YES;
}
NSDictionary *headers = [self cachedResponseHeadersForURL:[request url]];
if (!headers) {
return NO;
}
NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
if (!dataPath) {
return NO;
}
// If we get here, we have cached data
// If we have cached data, we can use it
if ([request cachePolicy] & ASIOnlyLoadIfNotCachedCachePolicy) {
return YES;
// If we want to fallback to the cache after an error
} else if ([request complete] && [request cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy) {
return YES;
// If we have cached data that is current, we can use it
} else if ([request cachePolicy] & ASIAskServerIfModifiedWhenStaleCachePolicy) {
if ([self isCachedDataCurrentForRequest:request]) {
return YES;
}
// If we've got headers from a conditional GET and the cached data is still current, we can use it
} else if ([request cachePolicy] & ASIAskServerIfModifiedCachePolicy) {
if (![request responseHeaders]) {
return NO;
} else if ([self isCachedDataCurrentForRequest:request]) {
return YES;
}
}
return NO;
}
@synthesize storagePath;
@synthesize defaultCachePolicy;
@synthesize accessLock;
@synthesize shouldRespectCacheControlHeaders;
@end

View file

@ -1,75 +0,0 @@
//
// ASIFormDataRequest.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASIHTTPRequest.h"
#import "ASIHTTPRequestConfig.h"
typedef enum _ASIPostFormat {
ASIMultipartFormDataPostFormat = 0,
ASIURLEncodedPostFormat = 1
} ASIPostFormat;
@interface ASIFormDataRequest : ASIHTTPRequest <NSCopying> {
// Parameters that will be POSTed to the url
NSMutableArray *postData;
// Files that will be POSTed to the url
NSMutableArray *fileData;
ASIPostFormat postFormat;
NSStringEncoding stringEncoding;
#if DEBUG_FORM_DATA_REQUEST
// Will store a string version of the request body that will be printed to the console when ASIHTTPREQUEST_DEBUG is set in GCC_PREPROCESSOR_DEFINITIONS
NSString *debugBodyString;
#endif
}
#pragma mark utilities
- (NSString*)encodeURL:(NSString *)string;
#pragma mark setup request
// Add a POST variable to the request
- (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key;
// Set a POST variable for this request, clearing any others with the same key
- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key;
// Add the contents of a local file to the request
- (void)addFile:(NSString *)filePath forKey:(NSString *)key;
// Same as above, but you can specify the content-type and file name
- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
// Add the contents of a local file to the request, clearing any others with the same key
- (void)setFile:(NSString *)filePath forKey:(NSString *)key;
// Same as above, but you can specify the content-type and file name
- (void)setFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
// Add the contents of an NSData object to the request
- (void)addData:(NSData *)data forKey:(NSString *)key;
// Same as above, but you can specify the content-type and file name
- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
// Add the contents of an NSData object to the request, clearing any others with the same key
- (void)setData:(NSData *)data forKey:(NSString *)key;
// Same as above, but you can specify the content-type and file name
- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key;
@property (atomic, assign) ASIPostFormat postFormat;
@property (atomic, assign) NSStringEncoding stringEncoding;
@end

View file

@ -1,362 +0,0 @@
//
// ASIFormDataRequest.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIFormDataRequest.h"
// Private stuff
@interface ASIFormDataRequest ()
- (void)buildMultipartFormDataPostBody;
- (void)buildURLEncodedPostBody;
- (void)appendPostString:(NSString *)string;
@property (atomic, retain) NSMutableArray *postData;
@property (atomic, retain) NSMutableArray *fileData;
#if DEBUG_FORM_DATA_REQUEST
- (void)addToDebugBody:(NSString *)string;
@property (retain, nonatomic) NSString *debugBodyString;
#endif
@end
@implementation ASIFormDataRequest
#pragma mark utilities
- (NSString*)encodeURL:(NSString *)string
{
NSString *newString = [NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding([self stringEncoding]))) autorelease];
if (newString) {
return newString;
}
return @"";
}
#pragma mark init / dealloc
+ (id)requestWithURL:(NSURL *)newURL
{
return [[[self alloc] initWithURL:newURL] autorelease];
}
- (id)initWithURL:(NSURL *)newURL
{
self = [super initWithURL:newURL];
[self setPostFormat:ASIURLEncodedPostFormat];
[self setStringEncoding:NSUTF8StringEncoding];
[self setRequestMethod:@"POST"];
return self;
}
- (void)dealloc
{
#if DEBUG_FORM_DATA_REQUEST
[debugBodyString release];
#endif
[postData release];
[fileData release];
[super dealloc];
}
#pragma mark setup request
- (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key
{
if (!key) {
return;
}
if (![self postData]) {
[self setPostData:[NSMutableArray array]];
}
NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2];
[keyValuePair setValue:key forKey:@"key"];
[keyValuePair setValue:[value description] forKey:@"value"];
[[self postData] addObject:keyValuePair];
}
- (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key
{
// Remove any existing value
NSUInteger i;
for (i=0; i<[[self postData] count]; i++) {
NSDictionary *val = [[self postData] objectAtIndex:i];
if ([[val objectForKey:@"key"] isEqualToString:key]) {
[[self postData] removeObjectAtIndex:i];
i--;
}
}
[self addPostValue:value forKey:key];
}
- (void)addFile:(NSString *)filePath forKey:(NSString *)key
{
[self addFile:filePath withFileName:nil andContentType:nil forKey:key];
}
- (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
{
BOOL isDirectory = NO;
BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory];
if (!fileExists || isDirectory) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],NSLocalizedDescriptionKey,nil]]];
}
// If the caller didn't specify a custom file name, we'll use the file name of the file we were passed
if (!fileName) {
fileName = [filePath lastPathComponent];
}
// If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension
if (!contentType) {
contentType = [ASIHTTPRequest mimeTypeForFileAtPath:filePath];
}
[self addData:filePath withFileName:fileName andContentType:contentType forKey:key];
}
- (void)setFile:(NSString *)filePath forKey:(NSString *)key
{
[self setFile:filePath withFileName:nil andContentType:nil forKey:key];
}
- (void)setFile:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
{
// Remove any existing value
NSUInteger i;
for (i=0; i<[[self fileData] count]; i++) {
NSDictionary *val = [[self fileData] objectAtIndex:i];
if ([[val objectForKey:@"key"] isEqualToString:key]) {
[[self fileData] removeObjectAtIndex:i];
i--;
}
}
[self addFile:data withFileName:fileName andContentType:contentType forKey:key];
}
- (void)addData:(NSData *)data forKey:(NSString *)key
{
[self addData:data withFileName:@"file" andContentType:nil forKey:key];
}
- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
{
if (![self fileData]) {
[self setFileData:[NSMutableArray array]];
}
if (!contentType) {
contentType = @"application/octet-stream";
}
NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4];
[fileInfo setValue:key forKey:@"key"];
[fileInfo setValue:fileName forKey:@"fileName"];
[fileInfo setValue:contentType forKey:@"contentType"];
[fileInfo setValue:data forKey:@"data"];
[[self fileData] addObject:fileInfo];
}
- (void)setData:(NSData *)data forKey:(NSString *)key
{
[self setData:data withFileName:@"file" andContentType:nil forKey:key];
}
- (void)setData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key
{
// Remove any existing value
NSUInteger i;
for (i=0; i<[[self fileData] count]; i++) {
NSDictionary *val = [[self fileData] objectAtIndex:i];
if ([[val objectForKey:@"key"] isEqualToString:key]) {
[[self fileData] removeObjectAtIndex:i];
i--;
}
}
[self addData:data withFileName:fileName andContentType:contentType forKey:key];
}
- (void)buildPostBody
{
if ([self haveBuiltPostBody]) {
return;
}
#if DEBUG_FORM_DATA_REQUEST
[self setDebugBodyString:@""];
#endif
if (![self postData] && ![self fileData]) {
[super buildPostBody];
return;
}
if ([[self fileData] count] > 0) {
[self setShouldStreamPostDataFromDisk:YES];
}
if ([self postFormat] == ASIURLEncodedPostFormat) {
[self buildURLEncodedPostBody];
} else {
[self buildMultipartFormDataPostBody];
}
[super buildPostBody];
#if DEBUG_FORM_DATA_REQUEST
ASI_DEBUG_LOG(@"%@",[self debugBodyString]);
[self setDebugBodyString:nil];
#endif
}
- (void)buildMultipartFormDataPostBody
{
#if DEBUG_FORM_DATA_REQUEST
[self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"];
#endif
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
// We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does.
CFUUIDRef uuid = CFUUIDCreate(nil);
NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease];
CFRelease(uuid);
NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString];
[self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]];
[self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]];
// Adds post data
NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary];
NSUInteger i=0;
for (NSDictionary *val in [self postData]) {
[self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]];
[self appendPostString:[val objectForKey:@"value"]];
i++;
if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body
[self appendPostString:endItemBoundary];
}
}
// Adds files to upload
i=0;
for (NSDictionary *val in [self fileData]) {
[self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]];
[self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]];
id data = [val objectForKey:@"data"];
if ([data isKindOfClass:[NSString class]]) {
[self appendPostDataFromFile:data];
} else {
[self appendPostData:data];
}
i++;
// Only add the boundary if this is not the last item in the post body
if (i != [[self fileData] count]) {
[self appendPostString:endItemBoundary];
}
}
[self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]];
#if DEBUG_FORM_DATA_REQUEST
[self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"];
#endif
}
- (void)buildURLEncodedPostBody
{
// We can't post binary data using application/x-www-form-urlencoded
if ([[self fileData] count] > 0) {
[self setPostFormat:ASIMultipartFormDataPostFormat];
[self buildMultipartFormDataPostBody];
return;
}
#if DEBUG_FORM_DATA_REQUEST
[self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"];
#endif
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding]));
[self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]];
NSUInteger i=0;
NSUInteger count = [[self postData] count]-1;
for (NSDictionary *val in [self postData]) {
NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")];
[self appendPostString:data];
i++;
}
#if DEBUG_FORM_DATA_REQUEST
[self addToDebugBody:@"\r\n==== End of application/x-www-form-urlencoded body ====\r\n"];
#endif
}
- (void)appendPostString:(NSString *)string
{
#if DEBUG_FORM_DATA_REQUEST
[self addToDebugBody:string];
#endif
[super appendPostData:[string dataUsingEncoding:[self stringEncoding]]];
}
#if DEBUG_FORM_DATA_REQUEST
- (void)appendPostData:(NSData *)data
{
[self addToDebugBody:[NSString stringWithFormat:@"[%lu bytes of data]",(unsigned long)[data length]]];
[super appendPostData:data];
}
- (void)appendPostDataFromFile:(NSString *)file
{
NSError *err = nil;
unsigned long long fileSize = [[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:file error:&err] objectForKey:NSFileSize] unsignedLongLongValue];
if (err) {
[self addToDebugBody:[NSString stringWithFormat:@"[Error: Failed to obtain the size of the file at '%@']",file]];
} else {
[self addToDebugBody:[NSString stringWithFormat:@"[%llu bytes of data from file '%@']",fileSize,file]];
}
[super appendPostDataFromFile:file];
}
- (void)addToDebugBody:(NSString *)string
{
if (string) {
[self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]];
}
}
#endif
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone
{
ASIFormDataRequest *newRequest = [super copyWithZone:zone];
[newRequest setPostData:[[[self postData] mutableCopyWithZone:zone] autorelease]];
[newRequest setFileData:[[[self fileData] mutableCopyWithZone:zone] autorelease]];
[newRequest setPostFormat:[self postFormat]];
[newRequest setStringEncoding:[self stringEncoding]];
[newRequest setRequestMethod:[self requestMethod]];
return newRequest;
}
@synthesize postData;
@synthesize fileData;
@synthesize postFormat;
@synthesize stringEncoding;
#if DEBUG_FORM_DATA_REQUEST
@synthesize debugBodyString;
#endif
@end

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,43 +0,0 @@
//
// ASIHTTPRequestConfig.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 14/12/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
// ======
// Debug output configuration options
// ======
// If defined will use the specified function for debug logging
// Otherwise use NSLog
#ifndef ASI_DEBUG_LOG
#define ASI_DEBUG_LOG NSLog
#endif
// When set to 1 ASIHTTPRequests will print information about what a request is doing
#ifndef DEBUG_REQUEST_STATUS
#define DEBUG_REQUEST_STATUS 0
#endif
// When set to 1, ASIFormDataRequests will print information about the request body to the console
#ifndef DEBUG_FORM_DATA_REQUEST
#define DEBUG_FORM_DATA_REQUEST 0
#endif
// When set to 1, ASIHTTPRequests will print information about bandwidth throttling to the console
#ifndef DEBUG_THROTTLING
#define DEBUG_THROTTLING 0
#endif
// When set to 1, ASIHTTPRequests will print information about persistent connections to the console
#ifndef DEBUG_PERSISTENT_CONNECTIONS
#define DEBUG_PERSISTENT_CONNECTIONS 0
#endif
// When set to 1, ASIHTTPRequests will print information about HTTP authentication (Basic, Digest or NTLM) to the console
#ifndef DEBUG_HTTP_AUTHENTICATION
#define DEBUG_HTTP_AUTHENTICATION 0
#endif

View file

@ -1,35 +0,0 @@
//
// ASIHTTPRequestDelegate.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 13/04/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
@class ASIHTTPRequest;
@protocol ASIHTTPRequestDelegate <NSObject>
@optional
// These are the default delegate methods for request status
// You can use different ones by setting didStartSelector / didFinishSelector / didFailSelector
- (void)requestStarted:(ASIHTTPRequest *)request;
- (void)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders;
- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL;
- (void)requestFinished:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)requestRedirected:(ASIHTTPRequest *)request;
// When a delegate implements this method, it is expected to process all incoming data itself
// This means that responseData / responseString / downloadDestinationPath etc are ignored
// You can have the request call a different method by setting didReceiveDataSelector
- (void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data;
// If a delegate implements one of these, it will be asked to supply credentials when none are available
// The delegate can then either restart the request ([request retryUsingSuppliedCredentials]) once credentials have been set
// or cancel it ([request cancelAuthentication])
- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request;
- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request;
@end

View file

@ -1,26 +0,0 @@
//
// ASIInputStream.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 10/08/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
@class ASIHTTPRequest;
// This is a wrapper for NSInputStream that pretends to be an NSInputStream itself
// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead.
// It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading
@interface ASIInputStream : NSObject {
NSInputStream *stream;
ASIHTTPRequest *request;
}
+ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request;
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request;
@property (retain, nonatomic) NSInputStream *stream;
@property (assign, nonatomic) ASIHTTPRequest *request;
@end

View file

@ -1,138 +0,0 @@
//
// ASIInputStream.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 10/08/2009.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
#import "ASIInputStream.h"
#import "ASIHTTPRequest.h"
// Used to ensure only one request can read data at once
static NSLock *readLock = nil;
@implementation ASIInputStream
+ (void)initialize
{
if (self == [ASIInputStream class]) {
readLock = [[NSLock alloc] init];
}
}
+ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)theRequest
{
ASIInputStream *theStream = [[[self alloc] init] autorelease];
[theStream setRequest:theRequest];
[theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
return theStream;
}
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)theRequest
{
ASIInputStream *theStream = [[[self alloc] init] autorelease];
[theStream setRequest:theRequest];
[theStream setStream:[NSInputStream inputStreamWithData:data]];
return theStream;
}
- (void)dealloc
{
[stream release];
[super dealloc];
}
// Called when CFNetwork wants to read more of our request body
// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
{
[readLock lock];
unsigned long toRead = len;
if ([ASIHTTPRequest isBandwidthThrottled]) {
toRead = [ASIHTTPRequest maxUploadReadLength];
if (toRead > len) {
toRead = len;
} else if (toRead == 0) {
toRead = 1;
}
[request performThrottling];
}
[readLock unlock];
NSInteger rv = [stream read:buffer maxLength:toRead];
if (rv > 0)
[ASIHTTPRequest incrementBandwidthUsedInLastSecond:(NSUInteger)rv];
return rv;
}
/*
* Implement NSInputStream mandatory methods to make sure they are implemented
* (necessary for MacRuby for example) and avoid the overhead of method
* forwarding for these common methods.
*/
- (void)open
{
[stream open];
}
- (void)close
{
[stream close];
}
- (id)delegate
{
return [stream delegate];
}
- (void)setDelegate:(id)delegate
{
[stream setDelegate:delegate];
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
{
[stream scheduleInRunLoop:aRunLoop forMode:mode];
}
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
{
[stream removeFromRunLoop:aRunLoop forMode:mode];
}
- (id)propertyForKey:(NSString *)key
{
return [stream propertyForKey:key];
}
- (BOOL)setProperty:(id)property forKey:(NSString *)key
{
return [stream setProperty:property forKey:key];
}
- (NSStreamStatus)streamStatus
{
return [stream streamStatus];
}
- (NSError *)streamError
{
return [stream streamError];
}
// If we get asked to perform a method we don't have (probably internal ones),
// we'll just forward the message to our stream
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [stream methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation invokeWithTarget:stream];
}
@synthesize stream;
@synthesize request;
@end

View file

@ -1,108 +0,0 @@
//
// ASINetworkQueue.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ASIHTTPRequestDelegate.h"
#import "ASIProgressDelegate.h"
@interface ASINetworkQueue : NSOperationQueue <ASIProgressDelegate, ASIHTTPRequestDelegate, NSCopying> {
// Delegate will get didFail + didFinish messages (if set)
id delegate;
// Will be called when a request starts with the request as the argument
SEL requestDidStartSelector;
// Will be called when a request receives response headers
// Should take the form request:didRecieveResponseHeaders:, where the first argument is the request, and the second the headers dictionary
SEL requestDidReceiveResponseHeadersSelector;
// Will be called when a request is about to redirect
// Should take the form request:willRedirectToURL:, where the first argument is the request, and the second the new url
SEL requestWillRedirectSelector;
// Will be called when a request completes with the request as the argument
SEL requestDidFinishSelector;
// Will be called when a request fails with the request as the argument
SEL requestDidFailSelector;
// Will be called when the queue finishes with the queue as the argument
SEL queueDidFinishSelector;
// Upload progress indicator, probably an NSProgressIndicator or UIProgressView
id uploadProgressDelegate;
// Total amount uploaded so far for all requests in this queue
unsigned long long bytesUploadedSoFar;
// Total amount to be uploaded for all requests in this queue - requests add to this figure as they work out how much data they have to transmit
unsigned long long totalBytesToUpload;
// Download progress indicator, probably an NSProgressIndicator or UIProgressView
id downloadProgressDelegate;
// Total amount downloaded so far for all requests in this queue
unsigned long long bytesDownloadedSoFar;
// Total amount to be downloaded for all requests in this queue - requests add to this figure as they receive Content-Length headers
unsigned long long totalBytesToDownload;
// When YES, the queue will cancel all requests when a request fails. Default is YES
BOOL shouldCancelAllRequestsOnFailure;
//Number of real requests (excludes HEAD requests created to manage showAccurateProgress)
int requestsCount;
// When NO, this request will only update the progress indicator when it completes
// When YES, this request will update the progress indicator according to how much data it has received so far
// When YES, the queue will first perform HEAD requests for all GET requests in the queue, so it can calculate the total download size before it starts
// NO means better performance, because it skips this step for GET requests, and it won't waste time updating the progress indicator until a request completes
// Set to YES if the size of a requests in the queue varies greatly for much more accurate results
// Default for requests in the queue is NO
BOOL showAccurateProgress;
// Storage container for additional queue information.
NSDictionary *userInfo;
}
// Convenience constructor
+ (id)queue;
// Call this to reset a queue - it will cancel all operations, clear delegates, and suspend operation
- (void)reset;
// Used internally to manage HEAD requests when showAccurateProgress is YES, do not use!
- (void)addHEADOperation:(NSOperation *)operation;
// All ASINetworkQueues are paused when created so that total size can be calculated before the queue starts
// This method will start the queue
- (void)go;
@property (assign, nonatomic, setter=setUploadProgressDelegate:) id uploadProgressDelegate;
@property (assign, nonatomic, setter=setDownloadProgressDelegate:) id downloadProgressDelegate;
@property (assign, atomic) SEL requestDidStartSelector;
@property (assign, atomic) SEL requestDidReceiveResponseHeadersSelector;
@property (assign, atomic) SEL requestWillRedirectSelector;
@property (assign, atomic) SEL requestDidFinishSelector;
@property (assign, atomic) SEL requestDidFailSelector;
@property (assign, atomic) SEL queueDidFinishSelector;
@property (assign, atomic) BOOL shouldCancelAllRequestsOnFailure;
@property (assign, atomic) id delegate;
@property (assign, atomic) BOOL showAccurateProgress;
@property (assign, atomic, readonly) int requestsCount;
@property (retain, atomic) NSDictionary *userInfo;
@property (assign, atomic) unsigned long long bytesUploadedSoFar;
@property (assign, atomic) unsigned long long totalBytesToUpload;
@property (assign, atomic) unsigned long long bytesDownloadedSoFar;
@property (assign, atomic) unsigned long long totalBytesToDownload;
@end

View file

@ -1,343 +0,0 @@
//
// ASINetworkQueue.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 07/11/2008.
// Copyright 2008-2009 All-Seeing Interactive. All rights reserved.
//
#import "ASINetworkQueue.h"
#import "ASIHTTPRequest.h"
// Private stuff
@interface ASINetworkQueue ()
- (void)resetProgressDelegate:(id *)progressDelegate;
@property (assign) int requestsCount;
@end
@implementation ASINetworkQueue
- (id)init
{
self = [super init];
[self setShouldCancelAllRequestsOnFailure:YES];
[self setMaxConcurrentOperationCount:4];
[self setSuspended:YES];
return self;
}
+ (id)queue
{
return [[[self alloc] init] autorelease];
}
- (void)dealloc
{
//We need to clear the queue on any requests that haven't got around to cleaning up yet, as otherwise they'll try to let us know if something goes wrong, and we'll be long gone by then
for (ASIHTTPRequest *request in [self operations]) {
[request setQueue:nil];
}
[userInfo release];
[super dealloc];
}
- (void)setSuspended:(BOOL)suspend
{
[super setSuspended:suspend];
}
- (void)reset
{
[self cancelAllOperations];
[self setDelegate:nil];
[self setDownloadProgressDelegate:nil];
[self setUploadProgressDelegate:nil];
[self setRequestDidStartSelector:NULL];
[self setRequestDidReceiveResponseHeadersSelector:NULL];
[self setRequestDidFailSelector:NULL];
[self setRequestDidFinishSelector:NULL];
[self setQueueDidFinishSelector:NULL];
[self setSuspended:YES];
}
- (void)go
{
[self setSuspended:NO];
}
- (void)cancelAllOperations
{
[self setBytesUploadedSoFar:0];
[self setTotalBytesToUpload:0];
[self setBytesDownloadedSoFar:0];
[self setTotalBytesToDownload:0];
[super cancelAllOperations];
}
- (void)setUploadProgressDelegate:(id)newDelegate
{
uploadProgressDelegate = newDelegate;
[self resetProgressDelegate:&uploadProgressDelegate];
}
- (void)setDownloadProgressDelegate:(id)newDelegate
{
downloadProgressDelegate = newDelegate;
[self resetProgressDelegate:&downloadProgressDelegate];
}
- (void)resetProgressDelegate:(id *)progressDelegate
{
#if !TARGET_OS_IPHONE
// If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can treat it similarly to UIProgressViews
SEL selector = @selector(setMaxValue:);
if ([*progressDelegate respondsToSelector:selector]) {
double max = 1.0;
[ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&max callerToRetain:nil];
}
selector = @selector(setDoubleValue:);
if ([*progressDelegate respondsToSelector:selector]) {
double value = 0.0;
[ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil];
}
#else
SEL selector = @selector(setProgress:);
if ([*progressDelegate respondsToSelector:selector]) {
float value = 0.0f;
[ASIHTTPRequest performSelector:selector onTarget:progressDelegate withObject:nil amount:&value callerToRetain:nil];
}
#endif
}
- (void)addHEADOperation:(NSOperation *)operation
{
if ([operation isKindOfClass:[ASIHTTPRequest class]]) {
ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
[request setRequestMethod:@"HEAD"];
[request setQueuePriority:10];
[request setShowAccurateProgress:YES];
[request setQueue:self];
// Important - we are calling NSOperation's add method - we don't want to add this as a normal request!
[super addOperation:request];
}
}
// Only add ASIHTTPRequests to this queue!!
- (void)addOperation:(NSOperation *)operation
{
if (![operation isKindOfClass:[ASIHTTPRequest class]]) {
[NSException raise:@"AttemptToAddInvalidRequest" format:@"Attempted to add an object that was not an ASIHTTPRequest to an ASINetworkQueue"];
}
[self setRequestsCount:[self requestsCount]+1];
ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
if ([self showAccurateProgress]) {
// Force the request to build its body (this may change requestMethod)
[request buildPostBody];
// If this is a GET request and we want accurate progress, perform a HEAD request first to get the content-length
// We'll only do this before the queue is started
// If requests are added after the queue is started they will probably move the overall progress backwards anyway, so there's no value performing the HEAD requests first
// Instead, they'll update the total progress if and when they receive a content-length header
if ([[request requestMethod] isEqualToString:@"GET"]) {
if ([self isSuspended]) {
ASIHTTPRequest *HEADRequest = [request HEADRequest];
[self addHEADOperation:HEADRequest];
[request addDependency:HEADRequest];
if ([request shouldResetDownloadProgress]) {
[self resetProgressDelegate:&downloadProgressDelegate];
[request setShouldResetDownloadProgress:NO];
}
}
}
[request buildPostBody];
[self request:nil incrementUploadSizeBy:(long long)[request postLength]];
} else {
[self request:nil incrementDownloadSizeBy:1];
[self request:nil incrementUploadSizeBy:1];
}
// Tell the request not to increment the upload size when it starts, as we've already added its length
if ([request shouldResetUploadProgress]) {
[self resetProgressDelegate:&uploadProgressDelegate];
[request setShouldResetUploadProgress:NO];
}
[request setShowAccurateProgress:[self showAccurateProgress]];
[request setQueue:self];
[super addOperation:request];
}
- (void)requestStarted:(ASIHTTPRequest *)request
{
if ([self requestDidStartSelector]) {
[[self delegate] performSelector:[self requestDidStartSelector] withObject:request];
}
}
- (void)request:(ASIHTTPRequest *)request didReceiveResponseHeaders:(NSDictionary *)responseHeaders
{
if ([self requestDidReceiveResponseHeadersSelector]) {
[[self delegate] performSelector:[self requestDidReceiveResponseHeadersSelector] withObject:request withObject:responseHeaders];
}
}
- (void)request:(ASIHTTPRequest *)request willRedirectToURL:(NSURL *)newURL
{
if ([self requestWillRedirectSelector]) {
[[self delegate] performSelector:[self requestWillRedirectSelector] withObject:request withObject:newURL];
}
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
[self setRequestsCount:[self requestsCount]-1];
if ([self requestDidFinishSelector]) {
[[self delegate] performSelector:[self requestDidFinishSelector] withObject:request];
}
if ([self requestsCount] == 0) {
if ([self queueDidFinishSelector]) {
[[self delegate] performSelector:[self queueDidFinishSelector] withObject:self];
}
}
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
[self setRequestsCount:[self requestsCount]-1];
if ([self requestDidFailSelector]) {
[[self delegate] performSelector:[self requestDidFailSelector] withObject:request];
}
if ([self requestsCount] == 0) {
if ([self queueDidFinishSelector]) {
[[self delegate] performSelector:[self queueDidFinishSelector] withObject:self];
}
}
if ([self shouldCancelAllRequestsOnFailure] && [self requestsCount] > 0) {
[self cancelAllOperations];
}
}
- (void)request:(ASIHTTPRequest *)request didReceiveBytes:(long long)bytes
{
[self setBytesDownloadedSoFar:[self bytesDownloadedSoFar]+(unsigned long long)bytes];
if ([self downloadProgressDelegate]) {
[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self bytesDownloadedSoFar] ofTotal:[self totalBytesToDownload]];
}
}
- (void)request:(ASIHTTPRequest *)request didSendBytes:(long long)bytes
{
[self setBytesUploadedSoFar:[self bytesUploadedSoFar]+(unsigned long long)bytes];
if ([self uploadProgressDelegate]) {
[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self bytesUploadedSoFar] ofTotal:[self totalBytesToUpload]];
}
}
- (void)request:(ASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength
{
[self setTotalBytesToDownload:[self totalBytesToDownload]+(unsigned long long)newLength];
}
- (void)request:(ASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength
{
[self setTotalBytesToUpload:[self totalBytesToUpload]+(unsigned long long)newLength];
}
// Since this queue takes over as the delegate for all requests it contains, it should forward authorisation requests to its own delegate
- (void)authenticationNeededForRequest:(ASIHTTPRequest *)request
{
if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
[[self delegate] performSelector:@selector(authenticationNeededForRequest:) withObject:request];
}
}
- (void)proxyAuthenticationNeededForRequest:(ASIHTTPRequest *)request
{
if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
[[self delegate] performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:request];
}
}
- (BOOL)respondsToSelector:(SEL)selector
{
// We handle certain methods differently because whether our delegate implements them or not can affect how the request should behave
// If the delegate implements this, the request will stop to wait for credentials
if (selector == @selector(authenticationNeededForRequest:)) {
if ([[self delegate] respondsToSelector:@selector(authenticationNeededForRequest:)]) {
return YES;
}
return NO;
// If the delegate implements this, the request will to wait for credentials
} else if (selector == @selector(proxyAuthenticationNeededForRequest:)) {
if ([[self delegate] respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
return YES;
}
return NO;
// If the delegate implements requestWillRedirectSelector, the request will stop to allow the delegate to change the url
} else if (selector == @selector(request:willRedirectToURL:)) {
if ([self requestWillRedirectSelector] && [[self delegate] respondsToSelector:[self requestWillRedirectSelector]]) {
return YES;
}
return NO;
}
return [super respondsToSelector:selector];
}
#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone
{
ASINetworkQueue *newQueue = [[[self class] alloc] init];
[newQueue setDelegate:[self delegate]];
[newQueue setRequestDidStartSelector:[self requestDidStartSelector]];
[newQueue setRequestWillRedirectSelector:[self requestWillRedirectSelector]];
[newQueue setRequestDidReceiveResponseHeadersSelector:[self requestDidReceiveResponseHeadersSelector]];
[newQueue setRequestDidFinishSelector:[self requestDidFinishSelector]];
[newQueue setRequestDidFailSelector:[self requestDidFailSelector]];
[newQueue setQueueDidFinishSelector:[self queueDidFinishSelector]];
[newQueue setUploadProgressDelegate:[self uploadProgressDelegate]];
[newQueue setDownloadProgressDelegate:[self downloadProgressDelegate]];
[newQueue setShouldCancelAllRequestsOnFailure:[self shouldCancelAllRequestsOnFailure]];
[newQueue setShowAccurateProgress:[self showAccurateProgress]];
[newQueue setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]];
return newQueue;
}
@synthesize requestsCount;
@synthesize bytesUploadedSoFar;
@synthesize totalBytesToUpload;
@synthesize bytesDownloadedSoFar;
@synthesize totalBytesToDownload;
@synthesize shouldCancelAllRequestsOnFailure;
@synthesize uploadProgressDelegate;
@synthesize downloadProgressDelegate;
@synthesize requestDidStartSelector;
@synthesize requestDidReceiveResponseHeadersSelector;
@synthesize requestWillRedirectSelector;
@synthesize requestDidFinishSelector;
@synthesize requestDidFailSelector;
@synthesize queueDidFinishSelector;
@synthesize delegate;
@synthesize showAccurateProgress;
@synthesize userInfo;
@end

View file

@ -1,38 +0,0 @@
//
// ASIProgressDelegate.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Ben Copsey on 13/04/2010.
// Copyright 2010 All-Seeing Interactive. All rights reserved.
//
@class ASIHTTPRequest;
@protocol ASIProgressDelegate <NSObject>
@optional
// These methods are used to update UIProgressViews (iPhone OS) or NSProgressIndicators (Mac OS X)
// If you are using a custom progress delegate, you may find it easier to implement didReceiveBytes / didSendBytes instead
#if TARGET_OS_IPHONE
- (void)setProgress:(float)newProgress;
#else
- (void)setDoubleValue:(double)newProgress;
- (void)setMaxValue:(double)newMax;
#endif
// Called when the request receives some data - bytes is the length of that data
- (void)request:(ASIHTTPRequest *)request didReceiveBytes:(long long)bytes;
// Called when the request sends some data
// The first 32KB (128KB on older platforms) of data sent is not included in this amount because of limitations with the CFNetwork API
// bytes may be less than zero if a request needs to remove upload progress (probably because the request needs to run again)
- (void)request:(ASIHTTPRequest *)request didSendBytes:(long long)bytes;
// Called when a request needs to change the length of the content to download
- (void)request:(ASIHTTPRequest *)request incrementDownloadSizeBy:(long long)newLength;
// Called when a request needs to change the length of the content to upload
// newLength may be less than zero when a request needs to remove the size of the internal buffer from progress tracking
- (void)request:(ASIHTTPRequest *)request incrementUploadSizeBy:(long long)newLength;
@end

View file

@ -9,14 +9,12 @@
#import <UIKit/UIKit.h>
@class NewsBlurAppDelegate;
@class ASIHTTPRequest;
@interface ActivityModule : UIView
<UITableViewDelegate,
UITableViewDataSource> {
NewsBlurAppDelegate *appDelegate;
UITableView *activitiesTable;
UIPopoverController *popoverController;
BOOL pageFetching;
BOOL pageFinished;
@ -25,7 +23,6 @@
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
@property (nonatomic, strong) UITableView *activitiesTable;
@property (nonatomic, strong) UIPopoverController *popoverController;
@property (nonatomic, readwrite) BOOL pageFetching;
@property (nonatomic, readwrite) BOOL pageFinished;
@ -34,8 +31,6 @@
- (void)refreshWithActivities:(NSArray *)activities;
- (void)fetchActivitiesDetail:(int)page;
- (void)finishLoadActivities:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)checkScroll;

View file

@ -11,7 +11,6 @@
#import "NewsBlurAppDelegate.h"
#import "UserProfileViewController.h"
#import <QuartzCore/QuartzCore.h>
#import "ASIHTTPRequest.h"
#import "ActivityCell.h"
#import "SmallActivityCell.h"
@ -19,7 +18,6 @@
@synthesize appDelegate;
@synthesize activitiesTable;
@synthesize popoverController;
@synthesize pageFetching;
@synthesize pageFinished;
@synthesize activitiesPage;
@ -31,7 +29,7 @@
{
self = [super initWithFrame:frame];
if (self) {
// initialize code here
self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
}
return self;
}
@ -49,7 +47,6 @@
}
- (void)refreshWithActivities:(NSArray *)activities {
self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.userActivitiesArray = activities;
[self.activitiesTable reloadData];
@ -102,26 +99,17 @@
[appDelegate.dictSocialProfile objectForKey:@"user_id"],
page];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDidFinishSelector:@selector(finishLoadActivities:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishLoadActivities:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[appDelegate informError:error];
}];
}
}
- (void)finishLoadActivities:(ASIHTTPRequest *)request {
- (void)finishLoadActivities:(NSDictionary *)results {
self.pageFetching = NO;
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// check for last page
if (![[results objectForKey:@"has_next_page"] intValue]) {
self.pageFinished = YES;
@ -152,12 +140,6 @@
[self refreshWithActivities:appDelegate.userActivitiesArray];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
#pragma mark -
#pragma mark Table View - Interactions List

View file

@ -8,16 +8,16 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
@class NewsBlurAppDelegate;
@interface AddSiteViewController : UIViewController
<UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource, ASIHTTPRequestDelegate>
@interface AddSiteViewController : BaseViewController
<UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource> {
NewsBlurAppDelegate *appDelegate;
}
- (void)reload;
- (IBAction)addSite;
- (void)autocompleteSite:(ASIHTTPRequest *)request;
- (IBAction)doCancelButton;
- (IBAction)doAddButton;
- (NSString *)extractParentFolder;
@ -26,7 +26,6 @@
- (IBAction)toggleAddFolder:(id)sender;
- (NSArray *)folders;
@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate;
@property (nonatomic) IBOutlet UITextField *inFolderInput;
@property (nonatomic) IBOutlet UITextField *addFolderInput;
@property (nonatomic) IBOutlet UITextField *siteAddressInput;

View file

@ -10,8 +10,6 @@
#import "AddSiteAutocompleteCell.h"
#import "NewsBlurAppDelegate.h"
#import "NewsBlurViewController.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import "NBContainerViewController.h"
#import "MenuViewController.h"
#import "SBJson4.h"
@ -29,11 +27,14 @@
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
}
return self;
}
- (void)viewDidLoad {
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(doCancelButton)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Add Site" style:UIBarButtonItemStyleDone target:self action:@selector(addSite)];
@ -45,12 +46,12 @@
[self.inFolderInput setLeftViewMode:UITextFieldViewModeAlways];
// If you want to show a disclosure arrow; don't really need it, though.
// UIImageView *disclosureImage = [[UIImageView alloc]
// initWithImage:[UIImage imageNamed:@"accessory_disclosure.png"]];
// disclosureImage.frame = CGRectMake(0, 0, 24, 20);
// [disclosureImage setContentMode:UIViewContentModeLeft];
// [inFolderInput setRightView:disclosureImage];
// [inFolderInput setRightViewMode:UITextFieldViewModeAlways];
// UIImageView *disclosureImage = [[UIImageView alloc]
// initWithImage:[UIImage imageNamed:@"accessory_disclosure.png"]];
// disclosureImage.frame = CGRectMake(0, 0, 24, 20);
// [disclosureImage setContentMode:UIViewContentModeLeft];
// [inFolderInput setRightView:disclosureImage];
// [inFolderInput setRightViewMode:UITextFieldViewModeAlways];
UIImageView *folderImage2 = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"g_icn_folder_rss_sm.png"]];
@ -82,7 +83,7 @@
self.siteTable.backgroundColor = UIColorFromRGB(NEWSBLUR_WHITE_COLOR);
// eliminate extra separators at bottom of site table (e.g., while animating)
self.siteTable.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
[super viewWillAppear:animated];
}
@ -122,9 +123,9 @@
- (IBAction)doCancelButton {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.appDelegate hidePopover];
[appDelegate hidePopover];
} else {
[self.appDelegate hidePopoverAnimated:YES];
[appDelegate hidePopoverAnimated:YES];
}
}
@ -160,10 +161,10 @@
if (self.siteAddressInput.returnKeyType == UIReturnKeySearch) {
[self checkSiteAddress];
} else {
[self addSite];
[self addSite];
}
}
return YES;
return YES;
}
- (IBAction)checkSiteAddress {
@ -196,47 +197,31 @@
[self.siteActivityIndicator startAnimating];
NSString *urlString = [NSString stringWithFormat:@"%@/rss_feeds/feed_autocomplete?term=%@&v=2",
self.appDelegate.url, [phrase stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:@selector(autocompleteSite:)];
[request startAsynchronous];
}
- (void)autocompleteSite:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSString *query = [NSString stringWithFormat:@"%@", [results objectForKey:@"term"]];
NSString *phrase = self.siteAddressInput.text;
// cache the results
[self.searchResults_ setValue:[results objectForKey:@"feeds"] forKey:query];
if ([phrase isEqualToString:query]) {
self.autocompleteResults = [results objectForKey:@"feeds"];
[self reloadSearchResults];
}
// NSRange range = [query rangeOfString : activeTerm_];
// BOOL found = (range.location != NSNotFound);
appDelegate.url, [phrase stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSString *query = [NSString stringWithFormat:@"%@", [responseObject objectForKey:@"term"]];
NSString *phrase = self.siteAddressInput.text;
// cache the results
[self.searchResults_ setValue:[responseObject objectForKey:@"feeds"] forKey:query];
if ([phrase isEqualToString:query]) {
self.autocompleteResults = [responseObject objectForKey:@"feeds"];
[self reloadSearchResults];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self.siteActivityIndicator stopAnimating];
}];
}
- (void)reloadSearchResults {
if ([self.siteAddressInput.text length] > 0 && [self.autocompleteResults count] > 0) {
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self.siteScrollView setAlpha:1];
} completion:nil];
} else {
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self.siteScrollView setAlpha:0];
} completion:nil];
@ -257,47 +242,45 @@
[self.errorLabel setHidden:YES];
[self.activityIndicator startAnimating];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/add_url",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
appDelegate.url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *parent_folder = [self extractParentFolder];
[request setPostValue:parent_folder forKey:@"folder"];
[request setPostValue:[self.siteAddressInput text] forKey:@"url"];
[params setObject:parent_folder forKey:@"folder"];
[params setObject:[self.siteAddressInput text] forKey:@"url"];
if (self.addFolderButton.selected && [self.addFolderInput.text length]) {
[request setPostValue:[self.addFolderInput text] forKey:@"new_folder"];
}
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setTimeOutSeconds:30];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request {
[self.addingLabel setHidden:YES];
[self.activityIndicator stopAnimating];
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
[self.errorLabel setText:[results valueForKey:@"message"]];
[self.errorLabel setHidden:NO];
} else {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.appDelegate hidePopover];
} else {
[self.appDelegate hidePopoverAnimated:YES];
}
[self.appDelegate reloadFeedsView:NO];
[params setObject:[self.addFolderInput text] forKey:@"new_folder"];
}
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.addingLabel setHidden:YES];
[self.activityIndicator stopAnimating];
int code = [[responseObject valueForKey:@"code"] intValue];
if (code == -1) {
[self.errorLabel setText:[responseObject valueForKey:@"message"]];
[self.errorLabel setHidden:NO];
} else {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate hidePopover];
} else {
[appDelegate hidePopoverAnimated:YES];
}
[appDelegate reloadFeedsView:NO];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self.addingLabel setHidden:YES];
[self.errorLabel setHidden:NO];
[self.activityIndicator stopAnimating];
NSLog(@"Error: %@", error);
[self.errorLabel setText:error.localizedDescription];
self.siteTable.hidden = YES;
[self preferredContentSize];
}];
}
- (NSString *)extractParentFolder {
@ -327,7 +310,7 @@
self.view.frame.size.width,
self.siteScrollView.frame.size.height);
} completion:nil];
} else {
self.addFolderButton.selected = NO;
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionAllowUserInteraction
@ -343,19 +326,8 @@
[self preferredContentSize];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
[self.addingLabel setHidden:YES];
[self.errorLabel setHidden:NO];
[self.activityIndicator stopAnimating];
NSError *error = [request error];
NSLog(@"Error: %@", error);
[self.errorLabel setText:error.localizedDescription];
self.siteTable.hidden = YES;
[self preferredContentSize];
}
- (NSArray *)folders {
return _.without([self.appDelegate dictFoldersArray],
return _.without([appDelegate dictFoldersArray],
@[@"saved_stories",
@"read_stories",
@"river_blurblogs",
@ -398,7 +370,7 @@
viewController.checkedRow = [folders indexOfObject:self.inFolderInput.text] + 1;
}
[self.appDelegate.addSiteNavigationController pushViewController:viewController animated:YES];
[appDelegate.addSiteNavigationController pushViewController:viewController animated:YES];
}
#pragma mark -
@ -409,13 +381,13 @@
return [self.autocompleteResults count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *AddSiteAutocompleteCellIdentifier = @"AddSiteAutocompleteCellIdentifier";
AddSiteAutocompleteCell *cell = (AddSiteAutocompleteCell *)[tableView dequeueReusableCellWithIdentifier:AddSiteAutocompleteCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"AddSiteAutocompleteCell"
AddSiteAutocompleteCell *cell = (AddSiteAutocompleteCell *)[tableView dequeueReusableCellWithIdentifier:AddSiteAutocompleteCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"AddSiteAutocompleteCell"
owner:self
options:nil];
for (id oneObject in nib) {
@ -427,13 +399,13 @@
if (cell == nil) {
cell = [AddSiteAutocompleteCell new];
}
}
}
NSDictionary *result = [self.autocompleteResults objectAtIndex:indexPath.row];
int subs = [[result objectForKey:@"num_subscribers"] intValue];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setPositiveFormat:@"#,###"];
NSNumber *theScore = [NSNumber numberWithInt:subs];
[numberFormatter setPositiveFormat:@"#,###"];
NSNumber *theScore = [NSNumber numberWithInt:subs];
NSString *favicon = [result objectForKey:@"favicon"];
UIImage *faviconImage;
if ((NSNull *)favicon != [NSNull null] && [favicon length] > 0) {
@ -442,13 +414,13 @@
} else {
faviconImage = [UIImage imageNamed:@"world.png"];
}
cell.feedTitle.text = [result objectForKey:@"label"];
cell.feedTitle.textColor = UIColorFromRGB(NEWSBLUR_BLACK_COLOR);
cell.feedUrl.text = [result objectForKey:@"value"];
cell.feedUrl.textColor = UIColorFromFixedRGB(NEWSBLUR_LINK_COLOR);
cell.feedSubs.text = [[NSString stringWithFormat:@"%@ subscriber%@",
[NSString stringWithFormat:@"%@", [numberFormatter stringFromNumber:theScore]], subs == 1 ? @"" : @"s"] uppercaseString];
[NSString stringWithFormat:@"%@", [numberFormatter stringFromNumber:theScore]], subs == 1 ? @"" : @"s"] uppercaseString];
cell.feedSubs.textColor = UIColorFromRGB(0x808080);
cell.feedFavicon.image = faviconImage;
cell.backgroundColor = UIColorFromRGB(NEWSBLUR_WHITE_COLOR);
@ -456,7 +428,7 @@
return cell;
}
- (void)tableView:(UITableView *)tableView
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *result = [self.autocompleteResults objectAtIndex:indexPath.row];
[self.siteAddressInput setText:[result objectForKey:@"value"]];

View file

@ -100,13 +100,7 @@
}];
} else {
[self.navigationController popViewControllerAnimated:YES];
if ([type isEqualToString:@"google"]) {
if (error.length) {
[appDelegate.firstTimeUserAddSitesViewController importFromGoogleReaderFailed:error];
} else {
[appDelegate.firstTimeUserAddSitesViewController importFromGoogleReader];
}
} else if ([type isEqualToString:@"facebook"]) {
if ([type isEqualToString:@"facebook"]) {
if (error.length) {
[self showError:error];
} else {

View file

@ -1,20 +1,9 @@
#import <UIKit/UIKit.h>
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import "MBProgressHUD.h"
@interface BaseViewController : UIViewController {
NSMutableArray* requests;
}
- (ASIHTTPRequest*) requestWithURL:(NSString*) s;
- (ASIFormDataRequest*) formRequestWithURL:(NSString*) s;
- (void) addRequest:(ASIHTTPRequest*)request;
- (void) clearFinishedRequests;
- (void) cancelRequests;
- (void)informError:(id)error;
- (void)informError:(id)error details:(NSString *)details;
- (void)informMessage:(NSString *)message;

View file

@ -6,48 +6,12 @@
#pragma mark -
#pragma mark HTTP requests
- (ASIHTTPRequest*) requestWithURL:(NSString*) s {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:s]];
[request setValidatesSecureCertificate:NO];
[self addRequest:request];
return request;
}
- (instancetype)init {
if (self = [super init]) {
- (ASIFormDataRequest*) formRequestWithURL:(NSString*) s {
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:s]];
[self addRequest:request];
return request;
}
- (void) addRequest:(ASIHTTPRequest*)request {
[request setDelegate:self];
if (!requests) {
requests = [[NSMutableArray alloc] initWithCapacity:3];
} else {
[self clearFinishedRequests];
}
[requests addObject:request];
}
- (void) clearFinishedRequests {
NSMutableArray* toremove = [[NSMutableArray alloc] initWithCapacity:[requests count]];
for (ASIHTTPRequest* r in requests) {
if ([r isFinished]) {
[toremove addObject:r];
}
}
for (ASIHTTPRequest* r in toremove) {
[requests removeObject:r];
}
}
- (void) cancelRequests {
for (ASIHTTPRequest* r in requests) {
r.delegate = nil;
[r cancel];
}
[requests removeAllObjects];
}
return self;
}
#pragma mark -
@ -146,13 +110,4 @@
}
}
#pragma mark -
#pragma mark Memory management
- (void)dealloc {
[self cancelRequests];
}
@end

View file

@ -12,7 +12,7 @@
#import "InteractionsModule.h"
#import "FeedDetailViewController.h"
#import "UserProfileViewController.h"
#import "TMCache.h"
#import "PINCache.h"
#import "StoriesCollection.h"
#import "UISearchBar+Field.h"
@ -170,7 +170,7 @@
#pragma mark - Stories
- (void)refreshStories {
[appDelegate.cachedStoryImages removeAllObjects:^(TMCache *cache) {
[appDelegate.cachedStoryImages removeAllObjects:^(PINCache * _Nonnull cache) {
dispatch_async(dispatch_get_main_queue(), ^{
[appDelegate loadRiverFeedDetailView:self.storiesModule withFolder:@"everything"];
appDelegate.inFeedDetail = NO;
@ -213,4 +213,4 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
return NO;
}
}
@end
@end

View file

@ -7,6 +7,7 @@
//
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
typedef NS_ENUM(NSUInteger, FeedChooserOperation)
{
@ -15,7 +16,9 @@ typedef NS_ENUM(NSUInteger, FeedChooserOperation)
};
@interface FeedChooserViewController : UIViewController
@interface FeedChooserViewController : BaseViewController {
NewsBlurAppDelegate *appDelegate;
}
@property (weak) IBOutlet UITableView *tableView;

View file

@ -42,6 +42,8 @@ static const CGFloat kFolderTitleHeight = 36.0;
- (void)viewDidLoad {
[super viewDidLoad];
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done)];
self.optionsItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"nav_icn_settings.png"] style:UIBarButtonItemStylePlain target:self action:@selector(showOptionsMenu)];
@ -96,21 +98,14 @@ static const CGFloat kFolderTitleHeight = 36.0;
}
NSString *urlString = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=false&include_inactive=true", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
request.delegate = self;
request.didFinishSelector = @selector(finishLoadingInactiveFeeds:);
request.didFailSelector = @selector(finishedWithError:);
request.timeOutSeconds = 30;
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishLoadingInactiveFeeds:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self finishedWithError:error];
}];
}
- (void)finishLoadingInactiveFeeds:(ASIHTTPRequest *)request {
NSString *responseString = request.responseString;
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *results = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
- (void)finishLoadingInactiveFeeds:(NSDictionary *)results {
self.dictFolders = results[@"flat_folders_with_inactive"];
self.inactiveFeeds = results[@"inactive_feeds"];
@ -126,8 +121,7 @@ static const CGFloat kFolderTitleHeight = 36.0;
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
- (void)finishedWithError:(ASIHTTPRequest *)request {
NSError *error = request.error;
- (void)finishedWithError:(NSError *)error {
NSLog(@"informError: %@", error);
NSString *errorMessage = [error localizedDescription];
@ -138,11 +132,11 @@ static const CGFloat kFolderTitleHeight = 36.0;
[HUD setMode:MBProgressHUDModeCustomView];
HUD.labelText = errorMessage;
[HUD hide:YES afterDelay:1];
[self rebuildItemsAnimated:YES];
}
- (void)rebuildItemsAnimated:(BOOL)animated {
NewsBlurAppDelegate *appDelegate = self.appDelegate;
FeedChooserItem *section = nil;
NSMutableArray *sections = [NSMutableArray array];
@ -464,17 +458,16 @@ static const CGFloat kFolderTitleHeight = 36.0;
}
NSString *urlString = [NSString stringWithFormat:@"%@/reader/move_feeds_by_folder_to_folder", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:feedsByFolder.JSONRepresentation forKey:@"feeds_by_folder"];
[request setPostValue:toFolder.identifier forKey:@"to_folder"];
[request setCompletionBlock:^(void) {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:feedsByFolder.JSONRepresentation forKey:@"feeds_by_folder"];
[params setObject:toFolder.identifier forKey:@"to_folder"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
HUD.labelText = @"Reloading...";
[self.appDelegate reloadFeedsView:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self finishedWithError:error];
}];
request.didFailSelector = @selector(finishedWithError:);
request.timeOutSeconds = 30;
[request startAsynchronous];
}
- (void)showMoveMenu {
@ -519,16 +512,14 @@ static const CGFloat kFolderTitleHeight = 36.0;
}
NSString *urlString = [NSString stringWithFormat:@"%@/reader/delete_feeds_by_folder", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:feedsByFolder.JSONRepresentation forKey:@"feeds_by_folder"];
[request setCompletionBlock:^(void) {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:feedsByFolder.JSONRepresentation forKey:@"feeds_by_folder"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
HUD.labelText = @"Reloading...";
[self.appDelegate reloadFeedsView:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self finishedWithError:error];
}];
request.didFailSelector = @selector(finishedWithError:);
request.timeOutSeconds = 30;
[request startAsynchronous];
}
- (void)deleteFeeds {
@ -568,23 +559,23 @@ static const CGFloat kFolderTitleHeight = 36.0;
HUD.labelText = @"Updating...";
NSString *urlString = [NSString stringWithFormat:@"%@/reader/save_feed_chooser", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSArray *mutedIndexPaths = self.tableView.indexPathsForSelectedRows;
NSMutableArray *feeds = [NSMutableArray array];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[self enumerateAllRowsUsingBlock:^(NSIndexPath *indexPath, FeedChooserItem *item) {
if (![mutedIndexPaths containsObject:indexPath]) {
[request addPostValue:item.identifier forKey:@"approved_feeds"];
[feeds addObject:item.identifier];
}
}];
[request setCompletionBlock:^(void) {
[params setObject:feeds forKey:@"approved_feeds"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:YES completion:nil];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self finishedWithError:error];
}];
request.didFailSelector = @selector(finishedWithError:);
request.timeOutSeconds = 30;
[request startAsynchronous];
}
- (BOOL)didChangeActiveFeeds {

View file

@ -15,7 +15,7 @@
#import "UIImageView+AFNetworking.h"
#import "Utilities.h"
#import "MCSwipeTableViewCell.h"
#import "TMCache.h"
#import "PINCache.h"
static UIFont *textFont = nil;
static UIFont *indicatorFont = nil;

View file

@ -8,7 +8,6 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
#import "BaseViewController.h"
#import "Utilities.h"
#import "NBNotifier.h"
@ -19,8 +18,8 @@
@class MCSwipeTableViewCell;
@interface FeedDetailViewController : BaseViewController
<UITableViewDelegate, UITableViewDataSource, UIAlertViewDelegate,
UIPopoverControllerDelegate, ASIHTTPRequestDelegate,
<UITableViewDelegate, UITableViewDataSource,
UIPopoverControllerDelegate,
MCSwipeTableViewCellDelegate,
UIGestureRecognizerDelegate, UISearchBarDelegate> {
NewsBlurAppDelegate *appDelegate;
@ -72,7 +71,6 @@
- (void)loadOfflineStories;
- (void)fetchRiver;
- (void)fetchRiverPage:(int)page withCallback:(void(^)())callback;
- (void)finishedLoadingFeed:(ASIHTTPRequest *)request;
- (void)testForTryFeed;
- (void)cacheStoryImages:(NSArray *)storyImageUrls;
- (void)showStoryImage:(NSString *)imageUrl;
@ -106,12 +104,10 @@
- (void)changeActiveStoryTitleCellLayout;
- (void)loadFaviconsFromActiveFeed;
- (void)markFeedsReadFromTimestamp:(NSInteger)cutoffTimestamp andOlder:(BOOL)older;
- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request;
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsUnread:(ASIFormDataRequest *)request;
- (void)finishMarkAsSaved:(NSDictionary *)params;
- (void)failedMarkAsSaved:(NSDictionary *)params;
- (void)finishMarkAsUnsaved:(NSDictionary *)params;
- (void)failedMarkAsUnsaved:(NSDictionary *)params;
- (void)failedMarkAsUnread:(NSDictionary *)params;
@end

View file

@ -12,7 +12,6 @@
#import "NBContainerViewController.h"
#import "NewsBlurViewController.h"
#import "FeedDetailTableCell.h"
#import "ASIFormDataRequest.h"
#import "UserProfileViewController.h"
#import "StoryDetailViewController.h"
#import "StoryPageControl.h"
@ -31,8 +30,7 @@
#import "FMDatabase.h"
#import "NBBarButtonItem.h"
#import "UIImage+Resize.h"
#import "TMCache.h"
#import "AFHTTPRequestOperation.h"
#import "PINCache.h"
#import "DashboardViewController.h"
#import "StoriesCollection.h"
#import "NSNull+JSON.h"
@ -522,7 +520,7 @@
storiesCollection.searchQuery = nil;
[self.searchBar setText:@""];
[self.notifier hideIn:0];
[self cancelRequests];
// [self cancelRequests];
[self beginOfflineTimer];
[appDelegate.cacheImagesOperationQueue cancelAllOperations];
}
@ -565,37 +563,35 @@
- (void)cacheStoryImages:(NSArray *)storyImageUrls {
NSBlockOperation *cacheImagesOperation = [NSBlockOperation blockOperationWithBlock:^{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager.requestSerializer setTimeoutInterval:5];
manager.responseSerializer = [AFImageResponseSerializer serializer];
for (NSString *storyImageUrl in storyImageUrls) {
// NSLog(@"Fetching image: %@", storyImageUrl);
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:[NSURL URLWithString:storyImageUrl]];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[request setTimeoutInterval:5.0];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[requestOperation setResponseSerializer:[AFImageResponseSerializer serializer]];
[requestOperation start];
[requestOperation waitUntilFinished];
UIImage *image = (UIImage *)requestOperation.responseObject;
if (!image || image.size.height < 50 || image.size.width < 50) {
[appDelegate.cachedStoryImages setObject:[NSNull null]
[manager GET:storyImageUrl parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
UIImage *image = (UIImage *)responseObject;
if (!image || image.size.height < 50 || image.size.width < 50) {
[appDelegate.cachedStoryImages setObject:[NSNull null]
forKey:storyImageUrl];
return;
}
CGSize maxImageSize = CGSizeMake(300, 300);
image = [image imageByScalingAndCroppingForSize:maxImageSize];
[appDelegate.cachedStoryImages setObject:image
forKey:storyImageUrl];
continue;
}
CGSize maxImageSize = CGSizeMake(300, 300);
image = [image imageByScalingAndCroppingForSize:maxImageSize];
[appDelegate.cachedStoryImages setObject:image
forKey:storyImageUrl];
if (self.isDashboardModule) {
[appDelegate.dashboardViewController.storiesModule
showStoryImage:storyImageUrl];
} else {
[appDelegate.feedDetailViewController
showStoryImage:storyImageUrl];
}
if (self.isDashboardModule) {
[appDelegate.dashboardViewController.storiesModule
showStoryImage:storyImageUrl];
} else {
[appDelegate.feedDetailViewController
showStoryImage:storyImageUrl];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
}];
[cacheImagesOperation setQualityOfService:NSQualityOfServiceBackground];
@ -704,39 +700,34 @@
if (storiesCollection.inSearch && storiesCollection.searchQuery) {
theFeedDetailURL = [NSString stringWithFormat:@"%@&query=%@",
theFeedDetailURL,
[storiesCollection.searchQuery stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[storiesCollection.searchQuery stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]];
}
[self cancelRequests];
__weak ASIHTTPRequest *request = [self requestWithURL:theFeedDetailURL];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setValidatesSecureCertificate:NO];
[request setUserInfo:@{@"feedPage": [NSNumber numberWithInt:storiesCollection.feedPage]}];
[request setFailedBlock:^(void) {
NSLog(@"in failed block %@", request);
if (request.isCancelled) {
NSLog(@"Cancelled");
return;
} else {
self.isOnline = NO;
storiesCollection.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
}
[self.storyTitlesTable reloadData];
}];
[request setCompletionBlock:^(void) {
// [self cancelRequests];
NSString *feedId = [NSString stringWithFormat:@"%@", [[storiesCollection activeFeed] objectForKey:@"id"]];
NSInteger feedPage = storiesCollection.feedPage;
[appDelegate.networkManager GET:theFeedDetailURL parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
if (!storiesCollection.activeFeed) return;
[self finishedLoadingFeed:request];
[self finishedLoadingFeed:responseObject feedPage:feedPage feedId:feedId];
if (callback) {
callback();
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)operation.response;
NSLog(@"in failed block %@", operation);
self.isOnline = NO;
self.isShowingFetching = NO;
// storiesCollection.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
if (httpResponse.statusCode == 503) {
[self informError:@"In maintenance mode"];
self.pageFinished = YES;
} else if (httpResponse.statusCode >= 500) {
[self informError:@"The server barfed."];
}
[self.storyTitlesTable reloadData];
}];
[request setTimeOutSeconds:30];
[request setTag:[[[storiesCollection activeFeed] objectForKey:@"id"] intValue]];
[request startAsynchronous];
[requests addObject:request];
}
- (void)loadOfflineStories {
@ -921,72 +912,41 @@
if (storiesCollection.inSearch && storiesCollection.searchQuery) {
theFeedDetailURL = [NSString stringWithFormat:@"%@&query=%@",
theFeedDetailURL,
[storiesCollection.searchQuery stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[storiesCollection.searchQuery stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]];
}
[self cancelRequests];
__weak ASIHTTPRequest *request = [self requestWithURL:theFeedDetailURL];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setUserInfo:@{@"feedPage": [NSNumber numberWithInt:storiesCollection.feedPage]}];
[request setFailedBlock:^(void) {
if (request.isCancelled) {
NSLog(@"Cancelled");
return;
} else {
self.isOnline = NO;
self.isShowingFetching = NO;
// storiesCollection.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
}
}];
[request setCompletionBlock:^(void) {
[self finishedLoadingFeed:request];
// [self cancelRequests];
[appDelegate.networkManager GET:theFeedDetailURL parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
[self finishedLoadingFeed:responseObject feedPage:storiesCollection.feedPage feedId:nil];
if (callback) {
callback();
}
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)operation.response;
self.isOnline = NO;
self.isShowingFetching = NO;
// storiesCollection.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
if (httpResponse.statusCode == 503) {
[self informError:@"In maintenance mode"];
self.pageFinished = YES;
} else if (httpResponse.statusCode >= 500) {
[self informError:@"The server barfed."];
}
}];
[request setTimeOutSeconds:30];
[request startAsynchronous];
}
#pragma mark -
#pragma mark Processing Stories
- (void)finishedLoadingFeed:(ASIHTTPRequest *)request {
if (request.isCancelled) {
NSLog(@"Cancelled");
return;
} else if ([request responseStatusCode] >= 500 || [request responseStatusCode] == 404) {
self.isOnline = NO;
self.isShowingFetching = NO;
// storiesCollection.feedPage = 1;
[self loadOfflineStories];
[self showOfflineNotifier];
if ([request responseStatusCode] == 503) {
[self informError:@"In maintenance mode"];
self.pageFinished = YES;
} else {
[self informError:@"The server barfed."];
}
[self.storyTitlesTable reloadData];
return;
}
- (void)finishedLoadingFeed:(NSDictionary *)results feedPage:(NSInteger)feedPage feedId:(NSString *)sentFeedId {
appDelegate.hasLoadedFeedDetail = YES;
self.isOnline = YES;
self.isShowingFetching = NO;
storiesCollection.feedPage = [[request.userInfo objectForKey:@"feedPage"] intValue];
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// storiesCollection.feedPage = [[request.userInfo objectForKey:@"feedPage"] intValue];
// if (storiesCollection.isSavedView &&
// ![[results objectForKey:@"stories"] count] &&
@ -994,15 +954,14 @@
// [results objectForKey:@"message"]) {
// [self informError:nil details:[results objectForKey:@"message"]];
// }
id feedId = [results objectForKey:@"feed_id"];
NSString *feedIdStr = [NSString stringWithFormat:@"%@",feedId];
NSString *receivedFeedId = [NSString stringWithFormat:@"%@", [results objectForKey:@"feed_id"]];
if (!(storiesCollection.isRiverView ||
storiesCollection.isSavedView ||
storiesCollection.isReadView ||
storiesCollection.isSocialView ||
storiesCollection.isSocialRiverView)
&& request.tag != [feedId intValue]) {
&& ![receivedFeedId isEqualToString:sentFeedId]) {
return;
}
if (storiesCollection.isSocialView ||
@ -1028,7 +987,7 @@
[storiesCollection.activeClassifiers setObject:[newClassifiers objectForKey:key] forKey:key];
}
} else if (newClassifiers) {
[storiesCollection.activeClassifiers setObject:newClassifiers forKey:feedIdStr];
[storiesCollection.activeClassifiers setObject:newClassifiers forKey:receivedFeedId];
}
storiesCollection.activePopularAuthors = [results objectForKey:@"feed_authors"];
storiesCollection.activePopularTags = [results objectForKey:@"feed_tags"];
@ -1161,7 +1120,8 @@
NSMutableArray *storyImageUrls = [NSMutableArray array];
for (NSDictionary *story in newStories) {
if ([story objectForKey:@"image_urls"] && [[story objectForKey:@"image_urls"] count]) {
[storyImageUrls addObject:[[story objectForKey:@"image_urls"] objectAtIndex:0]];
[storyImageUrls addObject:[[[story objectForKey:@"image_urls"] objectAtIndex:0]
stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]];
}
}
[self performSelector:@selector(cacheStoryImages:) withObject:storyImageUrls afterDelay:0.2];
@ -1791,9 +1751,8 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
- (void)markFeedsReadFromTimestamp:(NSInteger)cutoffTimestamp andOlder:(BOOL)older {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_as_read",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableArray *feedIds = [NSMutableArray array];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (storiesCollection.isRiverView) {
if ([storiesCollection.activeFolder isEqual:@"everything"]) {
@ -1813,34 +1772,18 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
[feedIds addObject:[storiesCollection.activeFeed objectForKey:@"id"]];
}
for (id feedId in feedIds) {
[request addPostValue:feedId forKey:@"feed_id"];
}
[params setObject:feedIds forKey:@"feed_id"];
[params setObject:@(cutoffTimestamp) forKey:@"cutoff_timestamp"];
NSString *direction = older ? @"older" : @"newest";
[request setPostValue:@(cutoffTimestamp) forKey:@"cutoff_timestamp"];
[request setPostValue:direction forKey:@"direction"];
[request setDidFinishSelector:@selector(finishMarkOlderNewerAsRead:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setUserInfo:@{@"feeds" : feedIds, @"cutoffTimestamp" : @(cutoffTimestamp), @"older" : @(older)}];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)finishMarkOlderNewerAsRead:(ASIFormDataRequest *)request {
if (request.responseStatusCode != 200) {
[self requestFailed:request];
return;
}
[params setObject:direction forKey:@"direction"];
if ([request.userInfo objectForKey:@"feeds"]) {
[appDelegate markFeedReadInCache:request.userInfo[@"feeds"] cutoffTimestamp:[request.userInfo[@"cutoffTimestamp"] integerValue] older:[request.userInfo[@"older"] boolValue]];
}
// is there a better way to refresh the detail view?
[self reloadStories];
// [appDelegate reloadFeedsView:YES];
// [appDelegate loadFeedDetailView];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionTask *task, id responseObject) {
[appDelegate markFeedReadInCache:feedIds cutoffTimestamp:cutoffTimestamp older:older];
// is there a better way to refresh the detail view?
[self reloadStories];
} failure:^(NSURLSessionTask *operation, NSError *error) {
[self requestFailed:error];
}];
}
- (IBAction)doOpenMarkReadMenu:(id)sender {
@ -1925,16 +1868,6 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
[self.appDelegate.feedDetailMenuNavigationController pushViewController:viewController animated:YES];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView.tag == 1) {
// Rename
if (buttonIndex != alertView.cancelButtonIndex) {
NSString *newTitle = [[alertView textFieldAtIndex:0] text];
[self renameTo:newTitle];
}
}
}
- (void)renameTo:(NSString *)newTitle {
[MBProgressHUD hideHUDForView:self.view animated:YES];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
@ -1944,20 +1877,18 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
if (storiesCollection.isRiverView) {
urlString = [NSString stringWithFormat:@"%@/reader/rename_folder", self.appDelegate.url];
}
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setDelegate:self];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (storiesCollection.isRiverView) {
[request addPostValue:[appDelegate extractFolderName:storiesCollection.activeFolder] forKey:@"folder_name"];
[request addPostValue:[appDelegate extractParentFolderName:storiesCollection.activeFolder] forKey:@"in_folder"];
[request addPostValue:newTitle forKey:@"new_folder_name"];
[params setObject:[appDelegate extractFolderName:storiesCollection.activeFolder] forKey:@"folder_name"];
[params setObject:[appDelegate extractParentFolderName:storiesCollection.activeFolder] forKey:@"in_folder"];
[params setObject:newTitle forKey:@"new_folder_name"];
} else {
[request addPostValue:[storiesCollection.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[request addPostValue:newTitle forKey:@"feed_title"];
[params setObject:[storiesCollection.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[params setObject:newTitle forKey:@"feed_title"];
}
[request setDidFailSelector:@selector(requestFailed:)];
[request setCompletionBlock:^(void) {
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[appDelegate reloadFeedsView:YES];
if (storiesCollection.isRiverView) {
[appDelegate renameFolder:newTitle];
@ -1972,11 +1903,9 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
}
[self.navigationController.view setNeedsDisplay];
[MBProgressHUD hideHUDForView:self.view animated:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
[request setTimeOutSeconds:30];
[request setTag:[[storiesCollection.activeFeed objectForKey:@"id"] intValue]];
[request startAsynchronous];
}
- (void)deleteSite {
@ -1984,26 +1913,22 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = @"Deleting...";
NSString *theFeedDetailURL = [NSString stringWithFormat:@"%@/reader/delete_feed",
NSString *urlString = [NSString stringWithFormat:@"%@/reader/delete_feed",
self.appDelegate.url];
NSURL *urlFeedDetail = [NSURL URLWithString:theFeedDetailURL];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:urlFeedDetail];
[request setDelegate:self];
[request addPostValue:[storiesCollection.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[request addPostValue:[appDelegate extractFolderName:storiesCollection.activeFolder] forKey:@"in_folder"];
[request setDidFailSelector:@selector(requestFailed:)];
[request setCompletionBlock:^(void) {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[storiesCollection.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[params setObject:[appDelegate extractFolderName:storiesCollection.activeFolder] forKey:@"in_folder"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[appDelegate reloadFeedsView:YES];
[appDelegate.navigationController
popToViewController:[appDelegate.navigationController.viewControllers
objectAtIndex:0]
[appDelegate.navigationController
popToViewController:[appDelegate.navigationController.viewControllers
objectAtIndex:0]
animated:YES];
[MBProgressHUD hideHUDForView:self.view animated:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
[request setTimeOutSeconds:30];
[request setTag:[[storiesCollection.activeFeed objectForKey:@"id"] intValue]];
[request startAsynchronous];
}
- (void)deleteFolder {
@ -2011,28 +1936,25 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = @"Deleting...";
NSString *theFeedDetailURL = [NSString stringWithFormat:@"%@/reader/delete_folder",
NSString *urlString = [NSString stringWithFormat:@"%@/reader/delete_folder",
self.appDelegate.url];
NSURL *urlFeedDetail = [NSURL URLWithString:theFeedDetailURL];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[appDelegate extractFolderName:storiesCollection.activeFolder]
forKey:@"folder_to_delete"];
[params setObject:[appDelegate extractFolderName:[appDelegate
extractParentFolderName:storiesCollection.activeFolder]]
forKey:@"in_folder"];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:urlFeedDetail];
[request setDelegate:self];
[request addPostValue:[appDelegate extractFolderName:storiesCollection.activeFolder]
forKey:@"folder_to_delete"];
[request addPostValue:[appDelegate extractFolderName:[appDelegate
extractParentFolderName:storiesCollection.activeFolder]]
forKey:@"in_folder"];
[request setDidFailSelector:@selector(requestFailed:)];
[request setCompletionBlock:^(void) {
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[appDelegate reloadFeedsView:YES];
[appDelegate.navigationController
popToViewController:[appDelegate.navigationController.viewControllers
objectAtIndex:0]
[appDelegate.navigationController
popToViewController:[appDelegate.navigationController.viewControllers
objectAtIndex:0]
animated:YES];
[MBProgressHUD hideHUDForView:self.view animated:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
[request setTimeOutSeconds:30];
[request startAsynchronous];
}
- (void)muteSite {
@ -2044,80 +1966,59 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
NSString *thisIdentifier = [NSString stringWithFormat:@"%@", storiesCollection.activeFeed[@"id"]];
[activeIdentifiers removeObject:thisIdentifier];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/save_feed_chooser", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
for (id identifier in activeIdentifiers) {
[request addPostValue:identifier forKey:@"approved_feeds"];
}
[request setCompletionBlock:^(void) {
[params setObject:activeIdentifiers forKey:@"approved_feeds"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.appDelegate reloadFeedsView:YES];
[self.appDelegate.navigationController popToViewController:[appDelegate.navigationController.viewControllers objectAtIndex:0]
animated:YES];
animated:YES];
[MBProgressHUD hideHUDForView:self.view animated:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
request.didFailSelector = @selector(requestFailed:);
request.timeOutSeconds = 30;
[request startAsynchronous];
}
- (void)performMoveToFolder:(id)toFolder {
[MBProgressHUD hideHUDForView:self.view animated:YES];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
ASIFormDataRequest *request = nil;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *urlString;
if (self.appDelegate.storiesCollection.isRiverView) {
HUD.labelText = @"Moving folder...";
NSString *urlString = [NSString stringWithFormat:@"%@/reader/move_folder_to_folder", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
request = [ASIFormDataRequest requestWithURL:url];
urlString = [NSString stringWithFormat:@"%@/reader/move_folder_to_folder", self.appDelegate.url];
NSString *activeFolder = self.appDelegate.storiesCollection.activeFolder;
NSString *parentFolderName = [self.appDelegate extractParentFolderName:activeFolder];
NSString *fromFolder = [self.appDelegate extractFolderName:parentFolderName];
NSString *toFolderIdentifier = [self.appDelegate extractFolderName:toFolder];
NSString *folderName = [self.appDelegate extractFolderName:activeFolder];
[request setPostValue:fromFolder forKey:@"in_folder"];
[request setPostValue:toFolderIdentifier forKey:@"to_folder"];
[request setPostValue:folderName forKey:@"folder_name"];
[params setObject:fromFolder forKey:@"in_folder"];
[params setObject:toFolderIdentifier forKey:@"to_folder"];
[params setObject:folderName forKey:@"folder_name"];
} else {
HUD.labelText = @"Moving site...";
NSString *urlString = [NSString stringWithFormat:@"%@/reader/move_feed_to_folder", self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
request = [ASIFormDataRequest requestWithURL:url];
urlString = [NSString stringWithFormat:@"%@/reader/move_feed_to_folder", self.appDelegate.url];
NSString *fromFolder = [self.appDelegate extractFolderName:self.appDelegate.storiesCollection.activeFolder];
NSString *toFolderIdentifier = [self.appDelegate extractFolderName:toFolder];
NSString *feedIdentifier = [self.appDelegate.storiesCollection.activeFeed objectForKey:@"id"];
[request setPostValue:fromFolder forKey:@"in_folder"];
[request setPostValue:toFolderIdentifier forKey:@"to_folder"];
[request setPostValue:feedIdentifier forKey:@"feed_id"];
[params setObject:fromFolder forKey:@"in_folder"];
[params setObject:toFolderIdentifier forKey:@"to_folder"];
[params setObject:feedIdentifier forKey:@"feed_id"];
}
[request setDelegate:self];
[request setDidFinishSelector:@selector(moveToFolderFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setUserInfo:@{@"toFolder" : toFolder}];
[request startAsynchronous];
}
- (void)moveToFolderFinished:(ASIHTTPRequest *)request {
if ([request responseStatusCode] >= 500) {
return [self requestFailed:request];
}
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
int code = [[results valueForKey:@"code"] intValue];
if (code != -1) {
self.appDelegate.storiesCollection.activeFolder = request.userInfo[@"toFolder"];
[self.appDelegate reloadFeedsView:NO];
}
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
int code = [[responseObject valueForKey:@"code"] intValue];
if (code != -1) {
self.appDelegate.storiesCollection.activeFolder = toFolder;
[self.appDelegate reloadFeedsView:NO];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)openMoveView {
@ -2168,17 +2069,29 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
[appDelegate extractFolderName:appDelegate.storiesCollection.activeFolder] : [appDelegate.storiesCollection.activeFeed objectForKey:@"feed_title"]];
NSString *subtitle = (appDelegate.storiesCollection.isRiverView ?
nil : [appDelegate.storiesCollection.activeFeed objectForKey:@"feed_address"]);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
message:subtitle
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Rename", nil];
[alertView setTag:1];
[alertView setAlertViewStyle:UIAlertViewStylePlainTextInput];
[[alertView textFieldAtIndex:0] setText:appDelegate.storiesCollection.isRiverView ?
[appDelegate extractFolderName:appDelegate.storiesCollection.activeFolder] :
[appDelegate.storiesCollection.activeFeed objectForKey:@"feed_title"]];
[alertView show];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:subtitle preferredStyle:UIAlertControllerStyleAlert];
[alert setModalPresentationStyle:UIModalPresentationPopover];
UIAlertAction *rename = [UIAlertAction actionWithTitle:@"Rename" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSString *newTitle = alert.textFields[0].text;
[self renameTo:newTitle];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = appDelegate.storiesCollection.isRiverView ?
[appDelegate extractFolderName:appDelegate.storiesCollection.activeFolder] :
[appDelegate.storiesCollection.activeFeed objectForKey:@"feed_title"];
}];
[alert addAction:rename];
[alert addAction:cancel];
if (self.presentedViewController) {
[self.presentedViewController dismissViewControllerAnimated:NO completion:^{
[self presentViewController:alert animated:YES completion:nil];
}];
}
// [self.appDelegate showAlert:alert withViewController:self];
}
- (void)showUserProfile {
@ -2248,27 +2161,27 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
#pragma mark -
#pragma mark Story Actions - save
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request {
- (void)finishMarkAsSaved:(NSDictionary *)params {
}
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request {
- (void)failedMarkAsSaved:(NSDictionary *)params {
[self informError:@"Failed to save story"];
[self.storyTitlesTable reloadData];
}
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request {
- (void)finishMarkAsUnsaved:(NSDictionary *)params {
}
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request {
- (void)failedMarkAsUnsaved:(NSDictionary *)params {
[self informError:@"Failed to unsave story"];
[self.storyTitlesTable reloadData];
}
- (void)failedMarkAsUnread:(ASIFormDataRequest *)request {
- (void)failedMarkAsUnread:(NSDictionary *)params {
[self informError:@"Failed to unread story"];
[self.storyTitlesTable reloadData];
@ -2284,16 +2197,14 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
stringWithFormat:@"%@/reader/refresh_feed/%@",
self.appDelegate.url,
[storiesCollection.activeFeed objectForKey:@"id"]];
[self cancelRequests];
ASIHTTPRequest *request = [self requestWithURL:urlString];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setDidFinishSelector:@selector(finishedRefreshingFeed:)];
[request setDidFailSelector:@selector(failRefreshingFeed:)];
[request setTimeOutSeconds:60];
[request startAsynchronous];
// [self cancelRequests];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
[self renderStories:[responseObject objectForKey:@"stories"]];
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(@"Fail: %@", error);
[self informError:[operation error]];
[self fetchFeedDetail:1 withCallback:nil];
}];
[storiesCollection setStories:nil];
storiesCollection.feedPage = 1;
@ -2302,24 +2213,6 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
[storyTitlesTable scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
}
- (void)finishedRefreshingFeed:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
[self renderStories:[results objectForKey:@"stories"]];
}
- (void)failRefreshingFeed:(ASIHTTPRequest *)request {
NSLog(@"Fail: %@", request);
[self informError:[request error]];
[self fetchFeedDetail:1 withCallback:nil];
}
#pragma mark -
#pragma mark loadSocial Feeds
@ -2336,25 +2229,15 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
NSString *urlString = [NSString stringWithFormat:@"%@/reader/favicons%@",
self.appDelegate.url,
feedIdsQuery];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDidFinishSelector:@selector(saveAndDrawFavicons:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self saveAndDrawFavicons:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)saveAndDrawFavicons:(NSDictionary *)results {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0ul);
dispatch_async(queue, ^{
for (id feed_id in results) {
@ -2377,9 +2260,8 @@ didEndSwipingSwipingWithState:(MCSwipeTableViewCellState)state
}
- (void)requestFailed:(ASIHTTPRequest *)request {
- (void)requestFailed:(NSError *)error {
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}

View file

@ -7,19 +7,18 @@
//
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
@class NewsBlurAppDelegate;
@interface FeedsMenuViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource, UIAlertViewDelegate> {
@interface FeedsMenuViewController : BaseViewController
<UITableViewDelegate, UITableViewDataSource> {
NewsBlurAppDelegate *appDelegate;
UIAlertView *loginAsAlert;
}
@property (nonatomic, strong) NSArray *menuOptions;
@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate;
@property (nonatomic) IBOutlet UITableView *menuTableView;
@property (nonatomic) IBOutlet UIAlertView *loginAsAlert;
@property (nonatomic) IBOutlet UISegmentedControl *themeSegmentedControl;
- (IBAction)changeTheme:(id)sender;

View file

@ -20,7 +20,7 @@
@synthesize appDelegate;
@synthesize menuOptions;
@synthesize menuTableView;
@synthesize loginAsAlert;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
@ -170,6 +170,12 @@
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.appDelegate hidePopover];
} else {
[self.appDelegate hidePopoverAnimated:YES];
}
switch (indexPath.row) {
case 0:
[appDelegate showPreferences];
@ -203,11 +209,6 @@
break;
}
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.appDelegate hidePopover];
} else {
[self.appDelegate hidePopoverAnimated:YES];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@ -216,46 +217,29 @@
#pragma mark Menu Options
- (void)showLoginAsDialog {
loginAsAlert = [[UIAlertView alloc] initWithTitle:@"Login as..." message:nil delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Login", nil];
loginAsAlert.alertViewStyle = UIAlertViewStylePlainTextInput;
UITextField * alertTextField = [loginAsAlert textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeAlphabet;
alertTextField.placeholder = @"Username";
[loginAsAlert show];
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Login as..." message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:nil];
[alertController addAction:[UIAlertAction actionWithTitle: @"Login" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[alertController dismissViewControllerAnimated:YES completion:nil];
NSString *username = alertController.textFields[0].text;
NSString *urlString = [NSString stringWithFormat:@"%@/reader/login_as?user=%@",
self.appDelegate.url, username];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
UITextField * alertTextField = [loginAsAlert textFieldAtIndex:0];
if ([alertTextField.text length] <= 0 || buttonIndex == 0){
return;
}
if (buttonIndex == 1) {
NSString *urlS = [NSString stringWithFormat:@"%@/reader/login_as?user=%@",
self.appDelegate.url, alertTextField.text];
NSURL *url = [NSURL URLWithString:urlS];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setFailedBlock:^(void) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}];
[request setCompletionBlock:^(void) {
NSLog(@"Login as %@ successful", alertTextField.text);
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"Login as %@ successful", username);
[MBProgressHUD hideHUDForView:self.view animated:YES];
[appDelegate reloadFeedsView:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
[request setTimeOutSeconds:30];
[request startAsynchronous];
[ASIHTTPRequest setSessionCookies:nil];
[MBProgressHUD hideHUDForView:self.view animated:YES];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:appDelegate.feedsViewController.view animated:YES];
HUD.labelText = [NSString stringWithFormat:@"Login: %@", alertTextField.text];
}
HUD.labelText = [NSString stringWithFormat:@"Login: %@", username];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel handler:nil]];
[appDelegate.feedsViewController presentViewController:alertController animated:YES completion:nil];
}
#pragma mark - Theme Options

View file

@ -10,9 +10,7 @@
#import "NewsBlurAppDelegate.h"
@class ASIHTTPRequest;
@interface FirstTimeUserAddFriendsViewController : UIViewController <ASIHTTPRequestDelegate, UIWebViewDelegate> {
@interface FirstTimeUserAddFriendsViewController : BaseViewController <UIWebViewDelegate> {
NewsBlurAppDelegate *appDelegate;
}
@ -32,14 +30,10 @@
- (IBAction)toggleAutoFollowFriends:(id)sender;
- (void)connectToSocial;
- (void)finishConnectFromSocial:(ASIHTTPRequest *)request;
- (void)finishTwitterConnect;
- (void)finishFacebookConnect;
- (void)finishedWithError:(ASIHTTPRequest *)request;
- (void)finishToggleAutoFollowFriends:(ASIHTTPRequest *)request;
- (void)changeMessaging:(NSString *)msg;
@end
@end

View file

@ -9,7 +9,6 @@
#import "FirstTimeUserAddFriendsViewController.h"
#import "FirstTimeUserAddNewsBlurViewController.h"
#import "AuthorizeServicesViewController.h"
#import "ASIHTTPRequest.h"
@interface FirstTimeUserAddFriendsViewController ()
@ -116,30 +115,20 @@
- (void)connectToSocial {
NSString *urlString = [NSString stringWithFormat:@"%@/social/load_user_friends",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishConnectFromSocial:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishConnectFromSocial:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
- (void)requestFailed:(NSError *)error {
NSLog(@"Error: %@", error);
[appDelegate informError:error];
[self informError:error];
}
- (void)finishConnectFromSocial:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSLog(@"results are %@", results);
- (void)finishConnectFromSocial:(NSDictionary *)results {
NSLog(@"Connect to social results: %@", results);
BOOL facebookSync = [[[[results objectForKey:@"services"] objectForKey:@"facebook"] objectForKey:@"syncing"] boolValue];
BOOL twitterSync = [[[[results objectForKey:@"services"] objectForKey:@"twitter"] objectForKey:@"syncing"] boolValue];
@ -197,49 +186,23 @@
- (IBAction)toggleAutoFollowFriends:(id)sender {
UISwitch *button = (UISwitch *)sender;
NSURL *preferenceURL = [NSURL URLWithString:
[NSString stringWithFormat:@"%@/profile/set_preference",
self.appDelegate.url]];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:preferenceURL];
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
NSString *urlString = [NSString stringWithFormat:@"%@/profile/set_preference", self.appDelegate.url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (button.on) {
[request setPostValue:@"false" forKey:@"autofollow_friends"];
[params setObject:@"false" forKey:@"autofollow_friends"];
} else {
[request setPostValue:@"true" forKey:@"autofollow_friends"];
[params setObject:@"true" forKey:@"autofollow_friends"];
}
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setDidFinishSelector:@selector(finishToggleAutoFollowFriends:)];
[request setDidFailSelector:@selector(finishedWithError:)];
[request setTimeOutSeconds:30];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishToggleAutoFollowFriends:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)finishedWithError:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSLog(@"results are %@", results);
}
- (void)finishToggleAutoFollowFriends:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)finishToggleAutoFollowFriends:(NSDictionary *)results {
NSLog(@"results are %@", results);
}

View file

@ -7,10 +7,9 @@
//
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
@interface FirstTimeUserAddNewsBlurViewController : UIViewController <ASIHTTPRequestDelegate> {
@interface FirstTimeUserAddNewsBlurViewController : BaseViewController {
NewsBlurAppDelegate *appDelegate;
}
@ -22,8 +21,6 @@
- (IBAction)tapNewsBlurButton:(id)sender;
- (IBAction)tapPopularButton:(id)sender;
- (void)finishAddSite:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)addSite:(NSString *)siteUrl;
- (void)addPopular;
@end
@end

View file

@ -112,45 +112,34 @@
- (void)addPopular {
NSString *urlString = [NSString stringWithFormat:@"%@/social/follow/",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[request setPostValue:@"social:popular" forKey:@"user_id"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishAddSite:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[params setObject:@"social:popular" forKey:@"user_id"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishAddSite:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
- (void)addSite:(NSString *)siteUrl {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/add_url/",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:siteUrl forKey:@"url"];
[request setPostValue:@"true" forKey:@"auto_active"];
[request setPostValue:@"true" forKey:@"skip_fetch"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishAddSite:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:siteUrl forKey:@"url"];
[params setObject:@"true" forKey:@"auto_active"];
[params setObject:@"true" forKey:@"skip_fetch"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishAddSite:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
- (void)finishAddSite:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)finishAddSite:(NSDictionary *)results {
NSLog(@"results are %@", results);
}

View file

@ -9,7 +9,8 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
@interface FirstTimeUserAddSitesViewController : UIViewController <ASIHTTPRequestDelegate, UITableViewDataSource, UITableViewDelegate> {
@interface FirstTimeUserAddSitesViewController : BaseViewController
<UITableViewDataSource, UITableViewDelegate> {
NewsBlurAppDelegate *appDelegate;
}
@ -25,9 +26,7 @@
- (void)tapGoogleReaderButton;
- (void)addCategory:(id)sender;
- (void)importFromGoogleReader;
- (void)importFromGoogleReaderFailed:(NSString *)error;
- (void)updateSites;
- (CGFloat)tableViewHeight;
@end
@end

View file

@ -114,31 +114,22 @@
if (self.selectedCategories_.count) {
NSString *urlString = [NSString stringWithFormat:@"%@/categories/subscribe",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSMutableArray *categories = [NSMutableArray array];
for(NSObject *category in self.selectedCategories_) {
[request addPostValue:category forKey:@"category"];
for (NSObject *category in self.selectedCategories_) {
[categories addObject:category];
}
[params setObject:categories forKey:@"category"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishAddingCategories:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
}
- (void)finishAddingCategories:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSLog(@"results are %@", results);
}
#pragma mark -
#pragma mark Import Google Reader
@ -163,29 +154,22 @@
[self.activityIndicator startAnimating];
NSString *urlString = [NSString stringWithFormat:@"%@/import/import_from_google_reader/",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:@"true" forKey:@"auto_active"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishImportFromGoogleReader:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:@"true" forKey:@"auto_active"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishImportFromGoogleReader:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self importFromGoogleReaderFailed:error];
}];
}
- (void)importFromGoogleReaderFailed:(NSString *)error {
- (void)importFromGoogleReaderFailed:(NSError *)error {
[self.googleReaderButton setTitle:@"Retry Google Reader" forState:UIControlStateNormal];
self.instructionLabel.textColor = [UIColor redColor];
self.instructionLabel.text = error;
self.instructionLabel.text = error.localizedDescription;
}
- (void)finishImportFromGoogleReader:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)finishImportFromGoogleReader:(NSDictionary *)results {
NSLog(@"results are %@", results);
self.importedFeedCount_ = [[results objectForKey:@"feed_count"] intValue];
@ -242,31 +226,20 @@
[self.categoriesTable reloadData];
}
- (void)finishAddFolder:(ASIHTTPRequest *)request {
NSLog(@"Successfully added.");
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
#pragma mark -
#pragma mark Add Site
- (void)addSite:(NSString *)siteUrl {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/add_url",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:siteUrl forKey:@"url"];
[request setPostValue:siteUrl forKey:@"url"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishAddFolder:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
#pragma mark -

View file

@ -8,7 +8,6 @@
#import "FirstTimeUserViewController.h"
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
#import "FirstTimeUserAddSitesViewController.h"
#import <QuartzCore/QuartzCore.h>

View file

@ -47,7 +47,7 @@
}
NSString *collapseKey = [NSString stringWithFormat:@"folderCollapsed:%@", folderName];
bool isFolderCollapsed = [userPreferences boolForKey:collapseKey];
int countWidth = 0;
NSInteger countWidth = 0;
NSString *accessibilityCount = @"";
if ([folderName isEqual:@"saved_stories"]) {

View file

@ -66,7 +66,7 @@
NSString *fontStyle = [[NSUserDefaults standardUserDefaults] stringForKey:@"fontStyle"];
if (!fontStyle) {
fontStyle = @"NB-helvetica";
fontStyle = @"GothamNarrow-Book";
}
if ([font[@"style"] isEqualToString:fontStyle]) {

View file

@ -276,7 +276,7 @@
} else if (indexPath.row == 5) {
NSString *fontStyle = [[NSUserDefaults standardUserDefaults] stringForKey:@"fontStyle"];
if (!fontStyle) {
fontStyle = @"NB-helvetica";
fontStyle = @"GothamNarrow-Book";
}
NSUInteger idx = [self.fonts indexOfObjectPassingTest:^BOOL(NSDictionary *obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [obj[@"style"] isEqualToString:fontStyle];

View file

@ -7,11 +7,11 @@
//
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
@class NewsBlurAppDelegate;
@class ASIHTTPRequest;
@interface FriendsListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate> {
@interface FriendsListViewController : BaseViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate> {
NewsBlurAppDelegate *appDelegate;
UISearchBar *friendSearchBar;
UITableView *friendsTable;
@ -29,9 +29,6 @@
- (void)doCancelButton;
- (void)loadFriendsList:(NSString *)query;
- (void)requestFinished:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)loadSuggestedFriendsList;
- (void)loadSuggestedFriendsListFinished:(ASIHTTPRequest *)request;
- (void)hideUserProfileModal;
@end

View file

@ -9,7 +9,6 @@
#import "FriendsListViewController.h"
#import "NewsBlurAppDelegate.h"
#import "UserProfileViewController.h"
#import "ASIHTTPRequest.h"
#import "ProfileBadge.h"
#import "MBProgressHUD.h"
#import "UISearchBar+Field.h"
@ -119,57 +118,36 @@
NSString *urlString = [NSString stringWithFormat:@"%@/social/find_friends?query=%@&limit=10",
self.appDelegate.url,
query];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self requestFinished:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
- (void)loadSuggestedFriendsList {
NSString *urlString = [NSString stringWithFormat:@"%@/social/load_user_friends",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setDidFinishSelector:@selector(loadSuggestedFriendsListFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self loadSuggestedFriendsListFinished:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
- (void)loadSuggestedFriendsListFinished:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData= [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
- (void)loadSuggestedFriendsListFinished:(NSDictionary *)results {
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
return;
}
self.suggestedUserProfiles = [results objectForKey:@"recommended_users"];
[self.friendsTable reloadData];
}
- (void)requestFinished:(ASIHTTPRequest *)request {
- (void)requestFinished:(NSDictionary *)results {
if (self.inSearch_) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSString *responseString = [request responseString];
NSData *responseData= [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
return;
@ -177,18 +155,11 @@
self.userProfiles = [results objectForKey:@"profiles"];
[self.friendsTable reloadData];
}
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
- (BOOL)disablesAutomaticKeyboardDismissal {
return NO;
}

View file

@ -7,15 +7,14 @@
//
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
@class NewsBlurAppDelegate;
@class ASIHTTPRequest;
@interface InteractionsModule : UIView <UITableViewDelegate, UITableViewDataSource> {
NewsBlurAppDelegate *appDelegate;
UITableView *interactionsTable;
NSMutableArray *interactionsArray;
UIPopoverController *popoverController;
BOOL pageFetching;
BOOL pageFinished;
@ -25,7 +24,6 @@
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
@property (nonatomic, strong) UITableView *interactionsTable;
@property (nonatomic) NSArray *interactionsArray;
@property (nonatomic, strong) UIPopoverController *popoverController;
@property (nonatomic, readwrite) BOOL pageFetching;
@property (nonatomic, readwrite) BOOL pageFinished;
@ -34,9 +32,7 @@
- (void)refreshWithInteractions:(NSArray *)interactions;
- (void)fetchInteractionsDetail:(int)page;
- (void)finishLoadInteractions:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)checkScroll;
@end
@end

View file

@ -11,7 +11,6 @@
#import "InteractionCell.h"
#import "SmallInteractionCell.h"
#import <QuartzCore/QuartzCore.h>
#import "ASIHTTPRequest.h"
#import "UserProfileViewController.h"
#import "DashboardViewController.h"
@ -23,7 +22,6 @@
@synthesize appDelegate;
@synthesize interactionsTable;
@synthesize interactionsArray;
@synthesize popoverController;
@synthesize pageFetching;
@synthesize pageFinished;
@synthesize interactionsPage;
@ -32,7 +30,7 @@
{
self = [super initWithFrame:frame];
if (self) {
appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
}
return self;
}
@ -75,8 +73,6 @@
#pragma mark Get Interactions
- (void)fetchInteractionsDetail:(int)page {
self.appDelegate = (NewsBlurAppDelegate *)[[UIApplication sharedApplication] delegate];
// if there is no social profile, we are DONE
// if ([[appDelegate.dictSocialProfile allKeys] count] == 0) {
// self.pageFinished = YES;
@ -105,26 +101,16 @@
[appDelegate.dictSocialProfile objectForKey:@"user_id"],
page];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDidFinishSelector:@selector(finishLoadInteractions:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishLoadInteractions:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[appDelegate informError:error];
}];
}
}
- (void)finishLoadInteractions:(ASIHTTPRequest *)request {
- (void)finishLoadInteractions:(NSDictionary *)results {
self.pageFetching = NO;
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *newInteractions = [results objectForKey:@"interactions"];
@ -158,12 +144,6 @@
[self refreshWithInteractions:appDelegate.userInteractionsArray];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
#pragma mark -
#pragma mark Table View - Interactions List
@ -333,4 +313,4 @@
[self checkScroll];
}
@end
@end

View file

@ -8,12 +8,10 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
#define LANDSCAPE_MARGIN 128
@interface LoginViewController : UIViewController
<ASIHTTPRequestDelegate> {
@interface LoginViewController : BaseViewController {
NewsBlurAppDelegate *appDelegate;
BOOL isOnSignUpScreen;

View file

@ -7,7 +7,6 @@
//
#import "LoginViewController.h"
#import "ASIFormDataRequest.h"
#import "../Other Sources/OnePasswordExtension/OnePasswordExtension.h"
//#import <QuartzCore/QuartzCore.h>
@ -199,46 +198,35 @@
NSString *urlString = [NSString stringWithFormat:@"%@/api/login",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[usernameInput text] forKey:@"username"];
[request setPostValue:[passwordInput text] forKey:@"password"];
[request setPostValue:@"login" forKey:@"submit"];
[request setPostValue:@"1" forKey:@"api"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request {
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
NSDictionary *errors = [results valueForKey:@"errors"];
if ([errors valueForKey:@"username"]) {
[self showError:[[errors valueForKey:@"username"] firstObject]];
} else if ([errors valueForKey:@"__all__"]) {
[self showError:[[errors valueForKey:@"__all__"] firstObject]];
}
} else {
[self.passwordInput setText:@""];
[self.signUpPasswordInput setText:@""];
[appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[usernameInput text] forKey:@"username"];
[params setObject:[passwordInput text] forKey:@"password"];
[params setObject:@"login" forKey:@"submit"];
[params setObject:@"1" forKey:@"api"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
int code = [[responseObject valueForKey:@"code"] intValue];
if (code == -1) {
NSDictionary *errors = [responseObject valueForKey:@"errors"];
if ([errors valueForKey:@"username"]) {
[self showError:[[errors valueForKey:@"username"] firstObject]];
} else if ([errors valueForKey:@"__all__"]) {
[self showError:[[errors valueForKey:@"__all__"] firstObject]];
}
} else {
[self.passwordInput setText:@""];
[self.signUpPasswordInput setText:@""];
[appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
@ -249,59 +237,48 @@
[self showError:nil];
NSString *urlString = [NSString stringWithFormat:@"%@/api/signup",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[request setPostValue:[signUpUsernameInput text] forKey:@"username"];
[request setPostValue:[signUpPasswordInput text] forKey:@"password"];
[params setObject:[signUpUsernameInput text] forKey:@"username"];
[params setObject:[signUpPasswordInput text] forKey:@"password"];
} else {
[request setPostValue:[usernameInput text] forKey:@"username"];
[request setPostValue:[passwordInput text] forKey:@"password"];
[params setObject:[usernameInput text] forKey:@"username"];
[params setObject:[passwordInput text] forKey:@"password"];
}
[request setPostValue:[emailInput text] forKey:@"email"];
[request setPostValue:@"login" forKey:@"submit"];
[request setPostValue:@"1" forKey:@"api"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishRegistering:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
}
- (void)finishRegistering:(ASIHTTPRequest *)request {
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
[params setObject:[emailInput text] forKey:@"email"];
[params setObject:@"login" forKey:@"submit"];
[params setObject:@"1" forKey:@"api"];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
NSDictionary *errors = [results valueForKey:@"errors"];
if ([errors valueForKey:@"email"]) {
[self showError:[[errors valueForKey:@"email"] objectAtIndex:0]];
} else if ([errors valueForKey:@"username"]) {
[self showError:[[errors valueForKey:@"username"] objectAtIndex:0]];
} else if ([errors valueForKey:@"__all__"]) {
[self showError:[[errors valueForKey:@"__all__"] objectAtIndex:0]];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
int code = [[responseObject valueForKey:@"code"] intValue];
if (code == -1) {
NSDictionary *errors = [responseObject valueForKey:@"errors"];
if ([errors valueForKey:@"email"]) {
[self showError:[[errors valueForKey:@"email"] objectAtIndex:0]];
} else if ([errors valueForKey:@"username"]) {
[self showError:[[errors valueForKey:@"username"] objectAtIndex:0]];
} else if ([errors valueForKey:@"__all__"]) {
[self showError:[[errors valueForKey:@"__all__"] objectAtIndex:0]];
}
} else {
[self.passwordInput setText:@""];
[self.signUpPasswordInput setText:@""];
// [appDelegate showFirstTimeUser];
[appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
} else {
[self.passwordInput setText:@""];
[self.signUpPasswordInput setText:@""];
// [appDelegate showFirstTimeUser];
[appDelegate reloadFeedsView:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
- (void)requestFailed:(NSError *)error {
NSLog(@"Error: %@", error);
[appDelegate informError:error];

View file

@ -8,7 +8,6 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
@class NewsBlurAppDelegate;
@ -16,8 +15,8 @@
@end
@interface MoveSiteViewController : UIViewController
<UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource, ASIHTTPRequestDelegate> {
@interface MoveSiteViewController : BaseViewController
<UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource> {
NewsBlurAppDelegate *appDelegate;
}

View file

@ -8,8 +8,6 @@
#import "MoveSiteViewController.h"
#import "NewsBlurAppDelegate.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import "StringHelper.h"
#import "StoriesCollection.h"
@ -141,34 +139,23 @@
[self.activityIndicator startAnimating];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/move_feed_to_folder",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *fromFolder = [appDelegate extractFolderName:[fromFolderInput text]];
NSString *toFolder = [appDelegate extractFolderName:[toFolderInput text]];
[request setPostValue:fromFolder forKey:@"in_folder"];
[request setPostValue:toFolder forKey:@"to_folder"];
[request setPostValue:[appDelegate.storiesCollection.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[params setObject:fromFolder forKey:@"in_folder"];
[params setObject:toFolder forKey:@"to_folder"];
[params setObject:[appDelegate.storiesCollection.activeFeed objectForKey:@"id"] forKey:@"feed_id"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self requestFinished:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)requestFinished:(ASIHTTPRequest *)request {
if ([request responseStatusCode] >= 500) {
return [self requestFailed:request];
}
- (void)requestFinished:(NSDictionary *)results {
[self.movingLabel setHidden:YES];
[self.activityIndicator stopAnimating];
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
[self.errorLabel setText:[results valueForKey:@"message"]];
@ -190,31 +177,24 @@
[self.activityIndicator startAnimating];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/move_folder_to_folder",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSString *folderName = [appDelegate extractFolderName:appDelegate.storiesCollection.activeFolder];
NSString *fromFolder = [appDelegate extractFolderName:[fromFolderInput text]];
NSString *toFolder = [appDelegate extractFolderName:[toFolderInput text]];
[request setPostValue:fromFolder forKey:@"in_folder"];
[request setPostValue:toFolder forKey:@"to_folder"];
[request setPostValue:folderName forKey:@"folder_name"];
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishMoveFolder:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[params setObject:fromFolder forKey:@"in_folder"];
[params setObject:toFolder forKey:@"to_folder"];
[params setObject:folderName forKey:@"folder_name"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMoveFolder:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)finishMoveFolder:(ASIHTTPRequest *)request {
- (void)finishMoveFolder:(NSDictionary *)results {
[self.movingLabel setHidden:YES];
[self.activityIndicator stopAnimating];
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
[self.errorLabel setText:[results valueForKey:@"message"]];
@ -226,13 +206,11 @@
}
- (void)requestFailed:(ASIHTTPRequest *)request {
- (void)requestFailed:(NSError *)error {
[self.movingLabel setHidden:YES];
[self.errorLabel setHidden:NO];
[self.activityIndicator stopAnimating];
NSError *error = [request error];
NSLog(@"Error: %@", error);
NSLog(@"Error: %@", [request responseString]);
[self.errorLabel setText:error.localizedDescription];
}
@ -311,4 +289,4 @@ numberOfRowsInComponent:(NSInteger)component {
return CGRectInset(bounds, 24, 0);
}
@end
@end

View file

@ -8,6 +8,7 @@
#import <UIKit/UIKit.h>
#import <SafariServices/SafariServices.h>
#import "NewsBlurAppDelegate.h"
@class NewsBlurAppDelegate;

View file

@ -361,8 +361,7 @@
FeedDetailTableCell *cell = (FeedDetailTableCell *)sender;
[self.appDelegate showPopoverWithViewController:self.appDelegate.notificationsViewController contentSize:CGSizeMake(420, 382) sourceView:cell sourceRect:cell.bounds];
} else {
CGRect frame = [sender CGRectValue];
[self.appDelegate showPopoverWithViewController:self.appDelegate.notificationsViewController contentSize:CGSizeMake(420, 382) sourceView:self.storyPageControl.view sourceRect:frame];
[self.appDelegate showPopoverWithViewController:self.appDelegate.notificationsViewController contentSize:CGSizeMake(420, 382) barButtonItem:appDelegate.feedsViewController.settingsBarButton];
}
}

View file

@ -11,6 +11,7 @@
#import "BaseViewController.h"
#import "FMDatabaseQueue.h"
#import "EventWindow.h"
#import "AFNetworking.h"
#define FEED_DETAIL_VIEW_TAG 1000001
#define STORY_DETAIL_VIEW_TAG 1000002
@ -49,10 +50,10 @@
@class IASKAppSettingsViewController;
@class UnreadCounts;
@class StoriesCollection;
@class TMCache;
@class PINCache;
@interface NewsBlurAppDelegate : BaseViewController
<UIApplicationDelegate, UIAlertViewDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate,
<UIApplicationDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate,
SFSafariViewControllerDelegate> {
EventWindow *window;
UINavigationController *ftuxNavigationController;
@ -91,6 +92,8 @@ SFSafariViewControllerDelegate> {
UserProfileViewController *userProfileViewController;
IASKAppSettingsViewController *preferencesViewController;
AFHTTPSessionManager *networkManager;
NSString * activeUsername;
NSString * activeUserProfileId;
NSString * activeUserProfileName;
@ -109,16 +112,16 @@ SFSafariViewControllerDelegate> {
NSURL * activeOriginalStoryURL;
NSString * activeShareType;
NSDictionary * activeComment;
int feedDetailPortraitYCoordinate;
int originalStoryCount;
NSInteger feedDetailPortraitYCoordinate;
NSInteger originalStoryCount;
NSInteger selectedIntelligence;
int savedStoriesCount;
int totalUnfetchedStoryCount;
int remainingUnfetchedStoryCount;
int latestFetchedStoryDate;
int latestCachedImageDate;
int totalUncachedImagesCount;
int remainingUncachedImagesCount;
NSInteger savedStoriesCount;
NSInteger totalUnfetchedStoryCount;
NSInteger remainingUnfetchedStoryCount;
NSInteger latestFetchedStoryDate;
NSInteger latestCachedImageDate;
NSInteger totalUncachedImagesCount;
NSInteger remainingUncachedImagesCount;
NSMutableDictionary * recentlyReadStories;
NSMutableSet * recentlyReadFeeds;
NSMutableArray * readStories;
@ -149,8 +152,8 @@ SFSafariViewControllerDelegate> {
UIImageView *splashView;
NSMutableDictionary *activeCachedImages;
TMCache *cachedFavicons;
TMCache *cachedStoryImages;
PINCache *cachedFavicons;
PINCache *cachedStoryImages;
}
@property (nonatomic) IBOutlet EventWindow *window;
@ -192,9 +195,10 @@ SFSafariViewControllerDelegate> {
@property (nonatomic) IBOutlet FirstTimeUserAddFriendsViewController *firstTimeUserAddFriendsViewController;
@property (nonatomic) IBOutlet FirstTimeUserAddNewsBlurViewController *firstTimeUserAddNewsBlurViewController;
@property (nonatomic) AFHTTPSessionManager *networkManager;
@property (nonatomic, readwrite) StoriesCollection *storiesCollection;
@property (nonatomic, readwrite) TMCache *cachedFavicons;
@property (nonatomic, readwrite) TMCache *cachedStoryImages;
@property (nonatomic, readwrite) PINCache *cachedFavicons;
@property (nonatomic, readwrite) PINCache *cachedStoryImages;
@property (nonatomic, readonly) NSString *url;
@property (nonatomic, readonly) NSString *host;
@ -217,15 +221,15 @@ SFSafariViewControllerDelegate> {
@property (readwrite) NSURL * activeOriginalStoryURL;
@property (readwrite) NSDictionary * activeComment;
@property (readwrite) NSString * activeShareType;
@property (readwrite) int feedDetailPortraitYCoordinate;
@property (readwrite) int originalStoryCount;
@property (readwrite) int savedStoriesCount;
@property (readwrite) int totalUnfetchedStoryCount;
@property (readwrite) int remainingUnfetchedStoryCount;
@property (readwrite) int totalUncachedImagesCount;
@property (readwrite) int remainingUncachedImagesCount;
@property (readwrite) int latestFetchedStoryDate;
@property (readwrite) int latestCachedImageDate;
@property (readwrite) NSInteger feedDetailPortraitYCoordinate;
@property (readwrite) NSInteger originalStoryCount;
@property (readwrite) NSInteger savedStoriesCount;
@property (readwrite) NSInteger totalUnfetchedStoryCount;
@property (readwrite) NSInteger remainingUnfetchedStoryCount;
@property (readwrite) NSInteger totalUncachedImagesCount;
@property (readwrite) NSInteger remainingUncachedImagesCount;
@property (readwrite) NSInteger latestFetchedStoryDate;
@property (readwrite) NSInteger latestCachedImageDate;
@property (readwrite) NSInteger selectedIntelligence;
@property (readwrite) NSMutableDictionary * recentlyReadStories;
@property (readwrite) NSMutableSet * recentlyReadFeeds;
@ -297,6 +301,8 @@ SFSafariViewControllerDelegate> {
- (void)openTrainSite;
- (void)openNotificationsWithFeed:(NSString *)feedId;
- (void)openNotificationsWithFeed:(NSString *)feedId sender:(id)sender;
- (void)updateNotifications:(NSDictionary *)params feed:(NSString *)feedId;
- (void)checkForFeedNotifications;
- (void)openTrainSiteWithFeedLoaded:(BOOL)feedLoaded from:(id)sender;
- (void)openTrainStory:(id)sender;
- (void)openUserTagsStory:(id)sender;
@ -313,6 +319,7 @@ SFSafariViewControllerDelegate> {
- (void)adjustStoryDetailWebView;
- (void)calibrateStoryTitles;
- (void)recalculateIntelligenceScores:(id)feedId;
- (void)cancelRequests;
- (void)reloadFeedsView:(BOOL)showLoader;
- (void)setTitle:(NSString *)title;
- (void)showOriginalStory:(NSURL *)url;
@ -331,6 +338,7 @@ SFSafariViewControllerDelegate> {
- (BOOL)isPortrait;
- (void)confirmLogout;
- (void)showConnectToService:(NSString *)serviceName;
- (void)showAlert:(UIAlertController *)alert withViewController:(UIViewController *)vc;
- (void)refreshUserProfile:(void(^)())callback;
- (void)refreshFeedCount:(id)feedId;
@ -354,15 +362,13 @@ SFSafariViewControllerDelegate> {
- (void)markFeedReadInCache:(NSArray *)feedIds cutoffTimestamp:(NSInteger)cutoff;
- (void)markFeedReadInCache:(NSArray *)feedIds cutoffTimestamp:(NSInteger)cutoff older:(BOOL)older;
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds cutoffTimestamp:(NSInteger)cutoff;
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request;
- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request;
- (void)finishMarkAsRead:(NSDictionary *)story;
- (void)finishMarkAsUnread:(NSDictionary *)story;
- (void)failedMarkAsUnread:(ASIFormDataRequest *)request;
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request;
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsUnread:(NSDictionary *)params;
- (void)finishMarkAsSaved:(NSDictionary *)params;
- (void)failedMarkAsSaved:(NSDictionary *)params;
- (void)finishMarkAsUnsaved:(NSDictionary *)params;
- (void)failedMarkAsUnsaved:(NSDictionary *)params;
- (NSInteger)adjustSavedStoryCount:(NSString *)tagName direction:(NSInteger)direction;
- (NSArray *)updateStarredStoryCounts:(NSDictionary *)results;
- (void)renameFeed:(NSString *)newTitle;
@ -400,11 +406,10 @@ SFSafariViewControllerDelegate> {
- (void)toggleTagClassifier:(NSString *)tag feedId:(NSString *)feedId;
- (void)toggleTitleClassifier:(NSString *)title feedId:(NSString *)feedId score:(NSInteger)score;
- (void)toggleFeedClassifier:(NSString *)feedId;
- (void)requestClassifierResponse:(ASIHTTPRequest *)request withFeed:(NSString *)feedId;
- (NSInteger)databaseSchemaVersion:(FMDatabase *)db;
- (void)createDatabaseConnection;
- (void)setupDatabase:(FMDatabase *)db;
- (void)setupDatabase:(FMDatabase *)db force:(BOOL)force;
- (void)cancelOfflineQueue;
- (void)startOfflineQueue;
- (void)startOfflineFetchStories;

View file

@ -29,8 +29,6 @@
#import "FontSettingsViewController.h"
#import "FeedChooserViewController.h"
#import "UserProfileViewController.h"
#import "AFHTTPRequestOperation.h"
#import "ASINetworkQueue.h"
#import "InteractionsModule.h"
#import "ActivityModule.h"
#import "FirstTimeUserViewController.h"
@ -56,7 +54,7 @@
#import "OfflineFetchImages.h"
#import "OfflineCleanImages.h"
#import "NBBarButtonItem.h"
#import "TMCache.h"
#import "PINCache.h"
#import "StoriesCollection.h"
#import "NSString+HTML.h"
#import "UIView+ViewController.h"
@ -65,6 +63,7 @@
#import "NSNull+JSON.h"
#import "UISearchBar+Field.h"
#import "UIViewController+HidePopover.h"
#import "PINCache.h"
#import <float.h>
#import <UserNotifications/UserNotifications.h>
@ -116,6 +115,7 @@
@synthesize firstTimeUserAddFriendsViewController;
@synthesize firstTimeUserAddNewsBlurViewController;
@synthesize networkManager;
@synthesize feedDetailPortraitYCoordinate;
@synthesize cachedFavicons;
@synthesize cachedStoryImages;
@ -189,8 +189,6 @@
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *currentiPhoneVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
[self registerDefaultsFromSettingsBundle];
self.navigationController.delegate = self;
@ -198,24 +196,21 @@
self.storiesCollection = [StoriesCollection new];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[ASIHTTPRequest setDefaultUserAgentString:[NSString stringWithFormat:@"NewsBlur iPad App v%@",
currentiPhoneVersion]];
[window addSubview:self.masterContainerViewController.view];
self.window.rootViewController = self.masterContainerViewController;
} else {
[ASIHTTPRequest setDefaultUserAgentString:[NSString stringWithFormat:@"NewsBlur iPhone App v%@",
currentiPhoneVersion]];
[window addSubview:self.navigationController.view];
self.window.rootViewController = self.navigationController;
}
[self clearNetworkManager];
[window makeKeyAndVisible];
[[ThemeManager themeManager] prepareForWindow:self.window];
[self createDatabaseConnection];
[self.cachedStoryImages removeAllObjects:^(TMCache *cache) {}];
[self.cachedStoryImages removeAllObjects:nil];
[feedsViewController loadOfflineFeeds:NO];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
@ -230,8 +225,8 @@
// [self showFirstTimeUser];
cachedFavicons = [[TMCache alloc] initWithName:@"NBFavicons"];
cachedStoryImages = [[TMCache alloc] initWithName:@"NBStoryImages"];
cachedFavicons = [[PINCache alloc] initWithName:@"NBFavicons"];
cachedStoryImages = [[PINCache alloc] initWithName:@"NBStoryImages"];
NBURLCache *urlCache = [[NBURLCache alloc] init];
[NSURLCache setSharedURLCache:urlCache];
@ -314,8 +309,8 @@
- (void)finishBackground {
if (!backgroundCompletionHandler) return;
NSLog(@"Background fetch complete. Found data: %d/%d = %d",
self.totalUnfetchedStoryCount, self.totalUncachedImagesCount,
NSLog(@"Background fetch complete. Found data: %ld/%ld = %d",
(long)self.totalUnfetchedStoryCount, (long)self.totalUncachedImagesCount,
self.totalUnfetchedStoryCount || self.totalUncachedImagesCount);
if (self.totalUnfetchedStoryCount || self.totalUncachedImagesCount) {
backgroundCompletionHandler(UIBackgroundFetchResultNewData);
@ -430,31 +425,14 @@
}
NSLog(@" -> APNS token: %@", token);
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/notifications/apns_token/",
self.url]];
ASIFormDataRequest *_request = [ASIFormDataRequest requestWithURL:url];
__weak ASIFormDataRequest *request = _request;
[request setValidatesSecureCertificate:NO];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setPostValue:token
forKey:@"apns_token"];
[request setFailedBlock:^(void) {
NSString *url = [NSString stringWithFormat:@"%@/notifications/apns_token/", self.url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:token forKey:@"apns_token"];
[networkManager POST:url parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@" -> APNS: %@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed to set APNS token");
}];
[request setCompletionBlock:^(void) {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSLog(@" -> APNS: %@/%@", results, error);
}];
[request setTimeOutSeconds:30];
[request startAsynchronous];
}
- (BOOL)application:(UIApplication *)application
@ -682,6 +660,7 @@
}
- (void)showMuteSites {
[self showFeedChooserForOperation:FeedChooserOperationMuteSites];
}
@ -1016,8 +995,6 @@
UINavigationController *navController = self.navigationController;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// trainerViewController.modalPresentationStyle=UIModalPresentationFormSheet;
// [navController presentViewController:trainerViewController animated:YES completion:nil];
[self.masterContainerViewController showNotificationsPopoverWithFeed:feedId sender:sender];
} else {
if (self.notificationsNavigationController == nil) {
@ -1030,6 +1007,40 @@
}
}
- (void)updateNotifications:(NSDictionary *)params feed:(NSString *)feedId {
NSMutableDictionary *feed = [[self.dictFeeds objectForKey:feedId] mutableCopy];
[feed setObject:params[@"notification_types"] forKey:@"notification_types"];
[feed setObject:params[@"notification_filter"] forKey:@"notification_filter"];
[self.dictFeeds setObject:feed forKey:feedId];
}
- (void)checkForFeedNotifications {
NSMutableArray *foundNotificationFeedIds = [NSMutableArray array];
for (NSDictionary *feed in self.dictFeeds.allValues) {
NSArray *types = [feed objectForKey:@"notification_types"];
if (types) {
for (NSString *notificationType in types) {
if ([notificationType isEqualToString:@"ios"]) {
[self registerForRemoteNotifications];
}
}
if ([types count]) {
[foundNotificationFeedIds addObject:[feed objectForKey:@"id"]];
}
}
}
self.notificationFeedIds = [foundNotificationFeedIds sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSString *feed1Title = [[[self.dictFeeds objectForKey:[NSString stringWithFormat:@"%@", obj1]] objectForKey:@"feed_title"] lowercaseString];
NSString *feed2Title = [[[self.dictFeeds objectForKey:[NSString stringWithFormat:@"%@", obj2]] objectForKey:@"feed_title"] lowercaseString];
return [feed1Title compare:feed2Title];
}];
}
- (void)openUserTagsStory:(id)sender {
if (!self.userTagsViewController) {
self.userTagsViewController = [[UserTagsViewController alloc] init];
@ -1050,6 +1061,30 @@
[self.navigationController.topViewController becomeFirstResponder];
}
#pragma mark - Network
- (void)cancelRequests {
[self clearNetworkManager];
}
- (void)clearNetworkManager {
[networkManager invalidateSessionCancelingTasks:YES];
networkManager = [AFHTTPSessionManager manager];
networkManager.responseSerializer = [AFJSONResponseSerializer serializer];
[networkManager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
NSString *currentiPhoneVersion = [[[NSBundle mainBundle] infoDictionary]
objectForKey:@"CFBundleVersion"];
NSString *UA;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UA = [NSString stringWithFormat:@"NewsBlur iPad App v%@", currentiPhoneVersion];
} else {
UA = [NSString stringWithFormat:@"NewsBlur iPhone App v%@", currentiPhoneVersion];
}
[networkManager.requestSerializer setValue:UA forHTTPHeaderField:@"User-Agent"];
}
#pragma mark -
- (void)reloadFeedsView:(BOOL)showLoader {
@ -1264,13 +1299,26 @@
}
- (void)confirmLogout {
UIAlertView *logoutConfirm = [[UIAlertView alloc] initWithTitle:@"Positive?"
message:nil
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Logout", nil];
[logoutConfirm show];
[logoutConfirm setTag:1];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Positive?" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle: @"Logout" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
[alertController dismissViewControllerAnimated:YES completion:nil];
NSLog(@"Logging out...");
NSString *urlString = [NSString stringWithFormat:@"%@/reader/logout?api=1",
self.url];
[networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self showLogin];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}];
[MBProgressHUD hideHUDForView:self.view animated:YES];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = @"Logging out...";
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel handler:nil]];
[self.feedsViewController presentViewController:alertController animated:YES completion:nil];
}
- (void)showConnectToService:(NSString *)serviceName {
@ -1292,74 +1340,31 @@
}
}
- (void)showAlert:(UIAlertController *)alert withViewController:(UIViewController *)vc {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.masterContainerViewController presentViewController:alert animated:YES completion:nil];
} else {
[vc presentViewController:alert animated:YES completion:nil];
}
}
- (void)refreshUserProfile:(void(^)())callback {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/social/load_user_profile",
self.url]];
ASIHTTPRequest *_request = [ASIHTTPRequest requestWithURL:url];
__weak ASIHTTPRequest *request = _request;
[request setValidatesSecureCertificate:NO];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setFailedBlock:^(void) {
NSString *urlString = [NSString stringWithFormat:@"%@/social/load_user_profile",
self.url];
[networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
self.dictUserProfile = [responseObject objectForKey:@"user_profile"];
self.dictSocialServices = [responseObject objectForKey:@"services"];
callback();
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed user profile");
callback();
}];
[request setCompletionBlock:^(void) {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
self.dictUserProfile = [results objectForKey:@"user_profile"];
self.dictSocialServices = [results objectForKey:@"services"];
callback();
}];
[request setTimeOutSeconds:30];
[request startAsynchronous];
}
- (void)refreshFeedCount:(id)feedId {
[feedsViewController fadeFeed:feedId];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView.tag == 1) { // this is logout
if (buttonIndex == 0) {
return;
} else {
NSLog(@"Logging out...");
NSString *urlS = [NSString stringWithFormat:@"%@/reader/logout?api=1",
self.url];
NSURL *url = [NSURL URLWithString:urlS];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setFailedBlock:^(void) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}];
[request setCompletionBlock:^(void) {
NSLog(@"Logout successful");
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self showLogin];
}];
[request setTimeOutSeconds:30];
[request startAsynchronous];
[ASIHTTPRequest setSessionCookies:nil];
[MBProgressHUD hideHUDForView:self.view animated:YES];
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = @"Logging out...";
}
}
}
- (void)loadRiverFeedDetailView:(FeedDetailViewController *)feedDetailView withFolder:(NSString *)folder {
self.readStories = [NSMutableArray array];
NSMutableArray *feeds = [NSMutableArray array];
@ -1635,14 +1640,15 @@
NSString *storyBrowser = [preferences stringForKey:@"story_browser"];
if ([storyBrowser isEqualToString:@"safari"]) {
[[UIApplication sharedApplication] openURL:url];
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
// [[UIApplication sharedApplication] openURL:url];
return;
} else if ([storyBrowser isEqualToString:@"chrome"] &&
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"googlechrome-x-callback://"]]) {
NSString *openingURL = [url.absoluteString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *openingURL = [url.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSURL *callbackURL = [NSURL URLWithString:@"newsblur://"];
NSString *callback = [callbackURL.absoluteString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *sourceName = [[[NSBundle mainBundle]objectForInfoDictionaryKey:@"CFBundleName"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *callback = [callbackURL.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSString *sourceName = [[[NSBundle mainBundle]objectForInfoDictionaryKey:@"CFBundleName"] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSURL *activityURL = [NSURL URLWithString:
[NSString stringWithFormat:@"googlechrome-x-callback://x-callback-url/open/?url=%@&x-success=%@&x-source=%@",
@ -1650,7 +1656,7 @@
callback,
sourceName]];
[[UIApplication sharedApplication] openURL:activityURL];
[[UIApplication sharedApplication] openURL:activityURL options:@{} completionHandler:nil];
return;
} else if ([storyBrowser isEqualToString:@"opera_mini"] &&
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"opera-http://"]]) {
@ -1664,12 +1670,12 @@
withString: @"opera-http"];
}
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:operaURL]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:operaURL] options:@{} completionHandler:nil];
return;
} else if ([storyBrowser isEqualToString:@"firefox"]) {
NSString *encodedURL = [url.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]];
NSString *firefoxURL = [NSString stringWithFormat:@"%@%@", @"firefox://?url=", encodedURL];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:firefoxURL]];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:firefoxURL] options:@{} completionHandler:nil];
} else if ([storyBrowser isEqualToString:@"inappsafari"]) {
self.safariViewController = [[SFSafariViewController alloc] initWithURL:url
entersReaderIfAvailable:NO];
@ -1783,7 +1789,9 @@
}
- (BOOL)isFeedInTextView:(id)feedId {
return [self.dictTextFeeds objectForKey:feedId];
id text = [self.dictTextFeeds objectForKey:feedId];
if (text != nil) return YES;
return NO;
}
- (void)toggleFeedTextView:(id)feedId {
@ -2129,42 +2137,34 @@
- (void)markStoryAsRead:(NSString *)storyHash inFeed:(NSString *)feed withCallback:(void(^)())callback {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_hashes_as_read",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:storyHash
forKey:@"story_hash"];
[request setCompletionBlock:^{
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:storyHash forKey:@"story_hash"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"Marked as read: %@", storyHash);
callback();
}];
[request setFailedBlock:^{
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed marked as read, queueing: %@", storyHash);
NSMutableDictionary *stories = [NSMutableDictionary dictionary];
[stories setObject:@[storyHash] forKey:feed];
[self queueReadStories:stories];
callback();
}];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)markStoryAsStarred:(NSString *)storyHash withCallback:(void(^)())callback {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_hash_as_starred",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:storyHash
forKey:@"story_hash"];
[request setCompletionBlock:^{
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:storyHash forKey:@"story_hash"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"Marked as starred: %@", storyHash);
callback();
}];
[request setFailedBlock:^{
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed marked as starred: %@", storyHash);
callback();
}];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)markStoriesRead:(NSDictionary *)stories inFeeds:(NSArray *)feeds cutoffTimestamp:(NSInteger)cutoff {
@ -2213,20 +2213,6 @@
}
}
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request {
// [self informError:@"Failed to mark story as read"];
NSArray *feedIds = [request.userInfo objectForKey:@"feeds"];
NSDictionary *stories = [request.userInfo objectForKey:@"stories"];
[self markStoriesRead:stories inFeeds:feedIds cutoffTimestamp:0];
}
- (void)finishMarkAllAsRead:(ASIFormDataRequest *)request {
if (request.responseStatusCode != 200) {
[self requestFailedMarkStoryRead:request];
}
}
- (void)finishMarkAsRead:(NSDictionary *)story {
if (!storyPageControl.previousPage || !storyPageControl.currentPage || !storyPageControl.nextPage) return;
for (StoryDetailViewController *page in @[storyPageControl.previousPage,
@ -2255,38 +2241,38 @@
originalStoryCount += 1;
}
- (void)failedMarkAsUnread:(ASIFormDataRequest *)request {
if (![storyPageControl failedMarkAsUnread:request]) {
[feedDetailViewController failedMarkAsUnread:request];
[dashboardViewController.storiesModule failedMarkAsUnread:request];
- (void)failedMarkAsUnread:(NSDictionary *)params {
if (![storyPageControl failedMarkAsUnread:params]) {
[feedDetailViewController failedMarkAsUnread:params];
[dashboardViewController.storiesModule failedMarkAsUnread:params];
}
[feedDetailViewController reloadData];
[dashboardViewController.storiesModule reloadData];
}
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request {
[storyPageControl finishMarkAsSaved:request];
[feedDetailViewController finishMarkAsSaved:request];
- (void)finishMarkAsSaved:(NSDictionary *)params {
[storyPageControl finishMarkAsSaved:params];
[feedDetailViewController finishMarkAsSaved:params];
}
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request {
if (![storyPageControl failedMarkAsSaved:request]) {
[feedDetailViewController failedMarkAsSaved:request];
[dashboardViewController.storiesModule failedMarkAsSaved:request];
- (void)failedMarkAsSaved:(NSDictionary *)params {
if (![storyPageControl failedMarkAsSaved:params]) {
[feedDetailViewController failedMarkAsSaved:params];
[dashboardViewController.storiesModule failedMarkAsSaved:params];
}
[feedDetailViewController reloadData];
[dashboardViewController.storiesModule reloadData];
}
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request {
[storyPageControl finishMarkAsUnsaved:request];
[feedDetailViewController finishMarkAsUnsaved:request];
- (void)finishMarkAsUnsaved:(NSDictionary *)params {
[storyPageControl finishMarkAsUnsaved:params];
[feedDetailViewController finishMarkAsUnsaved:params];
}
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request {
if (![storyPageControl failedMarkAsUnsaved:request]) {
[feedDetailViewController failedMarkAsUnsaved:request];
[dashboardViewController.storiesModule failedMarkAsUnsaved:request];
- (void)failedMarkAsUnsaved:(NSDictionary *)params {
if (![storyPageControl failedMarkAsUnsaved:params]) {
[feedDetailViewController failedMarkAsUnsaved:params];
[dashboardViewController.storiesModule failedMarkAsUnsaved:params];
}
[feedDetailViewController reloadData];
[dashboardViewController.storiesModule reloadData];
@ -2839,6 +2825,22 @@
#pragma mark -
#pragma mark Classifiers
- (void)failedClassifierSave:(NSURLSessionDataTask *)task {
BaseViewController *view;
if (self.trainerViewController.isViewLoaded && self.trainerViewController.view.window) {
view = self.trainerViewController;
} else {
view = self.storyPageControl.currentPage;
}
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
if (response.statusCode == 503) {
[view informError:@"In maintenance mode"];
} else {
[view informError:@"The server barfed!"];
}
}
- (void)toggleAuthorClassifier:(NSString *)author feedId:(NSString *)feedId {
int authorScore = [[[[storiesCollection.activeClassifiers objectForKey:feedId]
objectForKey:@"authors"]
@ -2863,23 +2865,19 @@
NSString *urlString = [NSString stringWithFormat:@"%@/classifier/save",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
__weak ASIFormDataRequest *_request = request;
[request setPostValue:author
forKey:authorScore >= 1 ? @"like_author" :
authorScore <= -1 ? @"dislike_author" :
@"remove_like_author"];
[request setPostValue:feedId forKey:@"feed_id"];
[request setCompletionBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setFailedBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setDelegate:self];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:author
forKey:authorScore >= 1 ? @"like_author" :
authorScore <= -1 ? @"dislike_author" :
@"remove_like_author"];
[params setObject:feedId forKey:@"feed_id"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.feedsViewController refreshFeedList:feedId];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedClassifierSave:task];
}];
[self recalculateIntelligenceScores:feedId];
[self.feedDetailViewController.storyTitlesTable reloadData];
}
@ -2911,22 +2909,18 @@
NSString *urlString = [NSString stringWithFormat:@"%@/classifier/save",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
__weak ASIFormDataRequest *_request = request;
[request setPostValue:tag
forKey:tagScore >= 1 ? @"like_tag" :
tagScore <= -1 ? @"dislike_tag" :
@"remove_like_tag"];
[request setPostValue:feedId forKey:@"feed_id"];
[request setCompletionBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:tag
forKey:tagScore >= 1 ? @"like_tag" :
tagScore <= -1 ? @"dislike_tag" :
@"remove_like_tag"];
[params setObject:feedId forKey:@"feed_id"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.feedsViewController refreshFeedList:feedId];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedClassifierSave:task];
}];
[request setFailedBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setDelegate:self];
[request startAsynchronous];
[self recalculateIntelligenceScores:feedId];
[self.feedDetailViewController.storyTitlesTable reloadData];
@ -2963,23 +2957,19 @@
NSString *urlString = [NSString stringWithFormat:@"%@/classifier/save",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
__weak ASIFormDataRequest *_request = request;
[request setPostValue:title
forKey:titleScore >= 1 ? @"like_title" :
titleScore <= -1 ? @"dislike_title" :
@"remove_like_title"];
[request setPostValue:feedId forKey:@"feed_id"];
[request setCompletionBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setFailedBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setDelegate:self];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:title
forKey:titleScore >= 1 ? @"like_title" :
titleScore <= -1 ? @"dislike_title" :
@"remove_like_title"];
[params setObject:feedId forKey:@"feed_id"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.feedsViewController refreshFeedList:feedId];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedClassifierSave:task];
}];
[self recalculateIntelligenceScores:feedId];
[self.feedDetailViewController.storyTitlesTable reloadData];
}
@ -3009,47 +2999,36 @@
NSString *urlString = [NSString stringWithFormat:@"%@/classifier/save",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
__weak ASIFormDataRequest *_request = request;
[request setPostValue:feedId
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:feedId
forKey:feedScore >= 1 ? @"like_feed" :
feedScore <= -1 ? @"dislike_feed" :
@"remove_like_feed"];
[request setPostValue:feedId forKey:@"feed_id"];
[request setCompletionBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setFailedBlock:^{
[self requestClassifierResponse:_request withFeed:feedId];
}];
[request setDelegate:self];
[request startAsynchronous];
[params setObject:feedId forKey:@"feed_id"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self.feedsViewController refreshFeedList:feedId];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedRequest:task.response];
}];
[self recalculateIntelligenceScores:feedId];
[self.feedDetailViewController.storyTitlesTable reloadData];
}
- (void)requestClassifierResponse:(ASIHTTPRequest *)request withFeed:(NSString *)feedId {
- (void)failedRequest:(NSURLResponse *)response {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
BaseViewController *view;
if (self.trainerViewController.isViewLoaded && self.trainerViewController.view.window) {
view = self.trainerViewController;
} else {
view = self.storyPageControl.currentPage;
}
if ([request responseStatusCode] == 503) {
if (httpResponse.statusCode == 503) {
return [view informError:@"In maintenance mode"];
} else if ([request responseStatusCode] != 200) {
} else if (httpResponse.statusCode != 200) {
return [view informError:@"The server barfed!"];
}
[self.feedsViewController refreshFeedList:feedId];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
NSLog(@"Error: %@", error);
[self informError:error];
}
#pragma mark -
@ -3106,12 +3085,12 @@
database = [FMDatabaseQueue databaseQueueWithPath:path];
[database inDatabase:^(FMDatabase *db) {
// db.traceExecution = YES;
[self setupDatabase:db];
[self setupDatabase:db force:NO];
}];
}
- (void)setupDatabase:(FMDatabase *)db {
if ([self databaseSchemaVersion:db] < CURRENT_DB_VERSION) {
- (void)setupDatabase:(FMDatabase *)db force:(BOOL)force {
if ([self databaseSchemaVersion:db] < CURRENT_DB_VERSION || force) {
// FMDB cannot execute this query because FMDB tries to use prepared statements
[db closeOpenResultSets];
[db executeUpdate:@"drop table if exists `stories`"];
@ -3412,38 +3391,27 @@
- (void)syncQueuedReadStories:(FMDatabase *)db withStories:(NSDictionary *)hashes withCallback:(void(^)())callback {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_stories_as_read",
self.url];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableArray *completedHashes = [NSMutableArray array];
for (NSArray *storyHashes in [hashes allValues]) {
[completedHashes addObjectsFromArray:storyHashes];
}
NSLog(@"Marking %lu queued read stories as read...", (unsigned long)[completedHashes count]);
NSString *completedHashesStr = [completedHashes componentsJoinedByString:@"\",\""];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
__weak ASIHTTPRequest *_request = request;
[request setPostValue:[hashes JSONRepresentation] forKey:@"feeds_stories"];
[request setDelegate:self];
[request setValidatesSecureCertificate:NO];
[request setCompletionBlock:^{
if ([_request responseStatusCode] == 200) {
NSLog(@"Completed clearing %@ hashes", completedHashesStr);
[db executeUpdate:[NSString stringWithFormat:@"DELETE FROM queued_read_hashes "
"WHERE story_hash in (\"%@\")", completedHashesStr]];
[self pruneQueuedReadHashes];
} else {
NSLog(@"Failed mark read queued.");
self.hasQueuedReadStories = YES;
[self pruneQueuedReadHashes];
}
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[hashes JSONRepresentation] forKey:@"feeds_stories"];
[networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"Completed clearing %@ hashes", completedHashesStr);
[db executeUpdate:[NSString stringWithFormat:@"DELETE FROM queued_read_hashes "
"WHERE story_hash in (\"%@\")", completedHashesStr]];
[self pruneQueuedReadHashes];
if (callback) callback();
}];
[request setFailedBlock:^{
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed mark read queued.");
self.hasQueuedReadStories = YES;
[self pruneQueuedReadHashes];
if (callback) callback();
}];
[request startAsynchronous];
}
- (void)pruneQueuedReadHashes {
@ -3502,6 +3470,32 @@
}
- (void)deleteAllCachedImages {
NSUInteger memorySize = 1024 * 1024 * 64;
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:memorySize diskCapacity:memorySize diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
NSLog(@"cap: %ld", (unsigned long)[[NSURLCache sharedURLCache] diskCapacity]);
NSInteger sizeInteger = [[NSURLCache sharedURLCache] currentDiskUsage];
float sizeInMB = sizeInteger / (1024.0f * 1024.0f);
NSLog(@"size: %ld, %f", (long)sizeInteger, sizeInMB);
[[NSURLCache sharedURLCache] removeAllCachedResponses];
sizeInteger = [[NSURLCache sharedURLCache] currentDiskUsage];
sizeInMB = sizeInteger / (1024.0f * 1024.0f);
NSLog(@"size: %ld, %f", (long)sizeInteger, sizeInMB);
[[NSURLCache sharedURLCache] removeAllCachedResponses];
sizeInteger = [[NSURLCache sharedURLCache] currentDiskUsage];
sizeInMB = sizeInteger / (1024.0f * 1024.0f);
NSLog(@"size: %ld, %f", (long)sizeInteger, sizeInMB);
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[[PINCache sharedCache] removeAllObjects];
[self.cachedStoryImages removeAllObjects];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *error = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
@ -3521,8 +3515,9 @@
}
NSLog(@"Deleted %d images.", removed);
}
@end
#pragma mark -

View file

@ -9,7 +9,6 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
#import "FolderTitleView.h"
#import "ASIHTTPRequest.h"
#import "BaseViewController.h"
#import "NBNotifier.h"
#import "IASKAppSettingsViewController.h"
@ -19,8 +18,7 @@
@interface NewsBlurViewController : BaseViewController
<UITableViewDelegate, UITableViewDataSource,
UIAlertViewDelegate,
ASIHTTPRequestDelegate, NSCacheDelegate,
NSCacheDelegate,
UIPopoverControllerDelegate,
IASKSettingsDelegate,
MCSwipeTableViewCellDelegate,
@ -84,10 +82,7 @@ UIGestureRecognizerDelegate> {
- (void)layoutForInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
- (void)returnToApp;
- (void)fetchFeedList:(BOOL)showLoader;
- (void)finishedWithError:(ASIHTTPRequest *)request;
- (void)finishLoadingFeedList:(ASIHTTPRequest *)request;
- (void)finishLoadingFeedListWithDict:(NSDictionary *)results finished:(BOOL)finished;
- (void)finishRefreshingFeedList:(ASIHTTPRequest *)request;
- (void)didSelectSectionHeader:(UIButton *)button;
- (void)didSelectSectionHeaderWithTag:(NSInteger)tag;
- (IBAction)selectIntelligence;
@ -95,8 +90,6 @@ UIGestureRecognizerDelegate> {
- (void)markFeedsRead:(NSArray *)feedIds cutoffDays:(NSInteger)days;
- (void)markEverythingReadWithDays:(NSInteger)days;
- (void)markVisibleStoriesRead;
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request;
- (void)finishMarkAllAsRead:(ASIHTTPRequest *)request;
- (void)didCollapseFolder:(UIButton *)button;
- (BOOL)isFeedVisible:(id)feedId;
- (void)changeToAllMode;
@ -109,8 +102,6 @@ UIGestureRecognizerDelegate> {
+ (int)computeMaxScoreForFeed:(NSDictionary *)feed;
- (void)loadFavicons;
- (void)loadAvatars;
- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)refreshFeedList;
- (void)refreshFeedList:(id)feedId;
- (void)loadOfflineFeeds:(BOOL)failed;
@ -131,8 +122,8 @@ UIGestureRecognizerDelegate> {
- (void)showRefreshNotifier;
- (void)showCountingNotifier;
- (void)showSyncingNotifier;
- (void)showSyncingNotifier:(float)progress hoursBack:(int)days;
- (void)showCachingNotifier:(float)progress hoursBack:(int)hours;
- (void)showSyncingNotifier:(float)progress hoursBack:(NSInteger)days;
- (void)showCachingNotifier:(float)progress hoursBack:(NSInteger)hours;
- (void)showOfflineNotifier;
- (void)showDoneNotifier;
- (void)hideNotifier;

View file

@ -20,8 +20,6 @@
#import "UserProfileViewController.h"
#import "StoryDetailViewController.h"
#import "StoryPageControl.h"
#import "ASIHTTPRequest.h"
#import "AFHTTPRequestOperation.h"
#import "MBProgressHUD.h"
#import "Base64.h"
#import "SBJson4.h"
@ -414,65 +412,44 @@ static UIFont *userLabelFont;
}
-(void)fetchFeedList:(BOOL)showLoader {
NSURL *urlFeedList;
NSString *urlFeedList;
NSLog(@"Fetching feed list");
[appDelegate cancelOfflineQueue];
if (self.inPullToRefresh_) {
urlFeedList = [NSURL URLWithString:
[NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=true",
self.appDelegate.url]];
urlFeedList = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=true",
self.appDelegate.url];
} else {
urlFeedList = [NSURL URLWithString:
[NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=false",
self.appDelegate.url]];
urlFeedList = [NSString stringWithFormat:@"%@/reader/feeds?flat=true&update_counts=false",
self.appDelegate.url];
}
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:urlFeedList];
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
[request setDidFinishSelector:@selector(finishLoadingFeedList:)];
[request setDidFailSelector:@selector(finishedWithError:)];
[request setTimeOutSeconds:15];
[request startAsynchronous];
if (appDelegate.backgroundCompletionHandler) {
urlFeedList = [urlFeedList stringByAppendingString:@"&background_ios=true"];
}
[appDelegate.networkManager GET:urlFeedList parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishLoadingFeedList:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
[self finishedWithError:error statusCode:httpResponse.statusCode];
}];
self.lastUpdate = [NSDate date];
if (showLoader) {
[self.notifier hide];
// [self.notifier hide];
}
[self showRefreshNotifier];
}
- (void)finishedWithError:(ASIHTTPRequest *)request {
[MBProgressHUD hideHUDForView:self.view animated:YES];
// User clicking on another link before the page loads is OK.
[self informError:[request error]];
[self finishRefresh];
self.isOffline = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate.dashboardViewController refreshStories];
}
[self showOfflineNotifier];
[self loadNotificationStory];
[[NSNotificationCenter defaultCenter] postNotificationName:@"FinishedLoadingFeedsNotification" object:nil];
}
- (void)finishLoadingFeedList:(ASIHTTPRequest *)request {
if ([request responseStatusCode] == 403) {
- (void)finishedWithError:(NSError *)error statusCode:(NSInteger)statusCode {
if (statusCode == 403) {
NSLog(@"Showing login");
return [appDelegate showLogin];
} else if ([request responseStatusCode] >= 400) {
if ([request responseStatusCode] == 429) {
} else if (statusCode >= 400) {
if (statusCode == 429) {
[self informError:@"Slow down. You're rate-limited."];
} else if ([request responseStatusCode] == 503) {
} else if (statusCode == 503) {
[self informError:@"In maintenance mode"];
} else {
[self informError:@"The server barfed!"];
@ -486,19 +463,30 @@ static UIFont *userLabelFont;
}
return;
}
[MBProgressHUD hideHUDForView:self.view animated:YES];
// User clicking on another link before the page loads is OK.
[self informError:error];
[self finishRefresh];
self.isOffline = YES;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate.dashboardViewController refreshStories];
}
[self showOfflineNotifier];
[self loadNotificationStory];
[[NSNotificationCenter defaultCenter] postNotificationName:@"FinishedLoadingFeedsNotification" object:nil];
}
- (void)finishLoadingFeedList:(NSDictionary *)results {
appDelegate.hasNoSites = NO;
appDelegate.recentlyReadStories = [NSMutableDictionary dictionary];
appDelegate.unreadStoryHashes = [NSMutableDictionary dictionary];
self.isOffline = NO;
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
appDelegate.activeUsername = [results objectForKey:@"user"];
@ -742,7 +730,7 @@ static UIFont *userLabelFont;
[self showExplainerOnEmptyFeedlist];
[self layoutHeaderCounts:0];
[self refreshHeaderCounts];
[self checkForFeedNotifications];
[appDelegate checkForFeedNotifications];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && finished) {
[appDelegate.dashboardViewController refreshStories];
@ -976,6 +964,8 @@ static UIFont *userLabelFont;
self.feedTitlesTable.backgroundColor = UIColorFromRGB(0xf4f4f4);
[self.feedTitlesTable reloadData];
[self resetupGestures];
}
- (void)updateThemeBrightness {
@ -1026,19 +1016,15 @@ static UIFont *userLabelFont;
dispatch_sync(dispatch_get_main_queue(), ^{
[[NSUserDefaults standardUserDefaults] setObject:@"Deleting..." forKey:specifier.key];
});
[appDelegate.database inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"DROP TABLE unread_hashes"];
[db executeUpdate:@"DROP TABLE unread_counts"];
[db executeUpdate:@"DROP TABLE accounts"];
[db executeUpdate:@"DROP TABLE stories"];
[db executeUpdate:@"DROP TABLE cached_images"];
[appDelegate setupDatabase:db];
[appDelegate.database inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"VACUUM"];
[appDelegate setupDatabase:db force:YES];
[appDelegate deleteAllCachedImages];
dispatch_sync(dispatch_get_main_queue(), ^{
[[NSUserDefaults standardUserDefaults] setObject:@"Cleared all stories and images!"
forKey:specifier.key];
});
}];
[appDelegate deleteAllCachedImages];
dispatch_sync(dispatch_get_main_queue(), ^{
[[NSUserDefaults standardUserDefaults] setObject:@"Cleared all stories and images!"
forKey:specifier.key];
});
});
}
}
@ -1388,21 +1374,17 @@ heightForHeaderInSection:(NSInteger)section {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_as_read",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
for (NSString *feedId in feedIds) {
[request addPostValue:feedId forKey:@"feed_id"];
}
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:feedIds forKey:@"feed_id"];
if (days) {
[request setPostValue:[NSNumber numberWithInteger:cutoffTimestamp]
[params setObject:[NSNumber numberWithInteger:cutoffTimestamp]
forKey:@"cutoff_timestamp"];
}
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailedMarkStoryRead:)];
[request setUserInfo:@{@"feeds": feedIds,
@"cutoffTimestamp": [NSNumber numberWithInteger:cutoffTimestamp]}];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAllAsRead:params];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailedMarkStoryRead:error withParams:params];
}];
if (!days) {
for (NSString *feedId in feedIds) {
@ -1414,22 +1396,20 @@ heightForHeaderInSection:(NSInteger)section {
}
- (void)markEverythingReadWithDays:(NSInteger)days {
NSTimeInterval cutoffTimestamp = [[NSDate date] timeIntervalSince1970];
cutoffTimestamp -= (days * 60*60*24);
NSArray *feedIds = [appDelegate allFeedIds];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_all_as_read",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[NSNumber numberWithInteger:days]
forKey:@"days"];
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailedMarkStoryRead:)];
[request setUserInfo:@{@"feeds": feedIds,
@"cutoffTimestamp": [NSNumber numberWithInteger:cutoffTimestamp]}];
[request setDelegate:self];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[NSNumber numberWithInteger:days]
forKey:@"days"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAllAsRead:params];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailedMarkStoryRead:error withParams:params];
}];
if (!days) {
for (NSString *feedId in feedIds) {
@ -1444,41 +1424,37 @@ heightForHeaderInSection:(NSInteger)section {
NSDictionary *feedsStories = [appDelegate markVisibleStoriesRead];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_feed_stories_as_read",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[feedsStories JSONRepresentation] forKey:@"feeds_stories"];
[request setDelegate:self];
[request setUserInfo:@{@"stories": feedsStories}];
[request setDidFinishSelector:@selector(finishMarkAllAsRead:)];
[request setDidFailSelector:@selector(requestFailedMarkStoryRead:)];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[feedsStories JSONRepresentation] forKey:@"feeds_stories"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAllAsRead:params];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailedMarkStoryRead:error withParams:params];
}];
}
- (void)requestFailedMarkStoryRead:(ASIFormDataRequest *)request {
[appDelegate markStoriesRead:[request.userInfo objectForKey:@"stories"]
inFeeds:[request.userInfo objectForKey:@"feeds"]
cutoffTimestamp:[[request.userInfo objectForKey:@"cutoffTimestamp"] integerValue]];
- (void)requestFailedMarkStoryRead:(NSError *)error withParams:(NSDictionary *)params {
[appDelegate markStoriesRead:[params objectForKey:@"stories"]
inFeeds:[params objectForKey:@"feed_id"]
cutoffTimestamp:[[params objectForKey:@"cutoff_timestamp"] integerValue]];
[self showOfflineNotifier];
self.isOffline = YES;
[self.feedTitlesTable reloadData];
}
- (void)finishMarkAllAsRead:(ASIFormDataRequest *)request {
if (request.responseStatusCode != 200) {
[self requestFailedMarkStoryRead:request];
return;
}
- (void)finishMarkAllAsRead:(NSDictionary *)params {
// This seems fishy post-ASI rewrite. This needs to know about a cutoff timestamp which it is never given.
self.isOffline = NO;
if ([[request.userInfo objectForKey:@"cutoffTimestamp"] integerValue]) {
if ([[params objectForKey:@"cutoff_timestamp"] integerValue]) {
id feed;
if ([[request.userInfo objectForKey:@"feeds"] count] == 1) {
feed = [[request.userInfo objectForKey:@"feeds"] objectAtIndex:0];
if ([[params objectForKey:@"feed_id"] count] == 1) {
feed = [[params objectForKey:@"feed_id"] objectAtIndex:0];
}
[self refreshFeedList:feed];
} else if ([request.userInfo objectForKey:@"feeds"]) {
[appDelegate markFeedReadInCache:[request.userInfo objectForKey:@"feeds"]];
} else if ([params objectForKey:@"feed_id"]) {
[appDelegate markFeedReadInCache:[params objectForKey:@"feed_id"]];
}
}
@ -1738,13 +1714,12 @@ heightForHeaderInSection:(NSInteger)section {
- (void)loadFavicons {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/favicons",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDidFinishSelector:@selector(saveAndDrawFavicons:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self saveAndDrawFavicons:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)loadAvatars {
@ -1770,18 +1745,9 @@ heightForHeaderInSection:(NSInteger)section {
- (void)saveAndDrawFavicons:(ASIHTTPRequest *)request {
__block NSString *responseString = [request responseString];
- (void)saveAndDrawFavicons:(NSDictionary *)results {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0ul);
dispatch_async(queue, ^{
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
for (id feed_id in results) {
// NSMutableDictionary *feed = [[appDelegate.dictFeeds objectForKey:feed_id] mutableCopy];
// [feed setValue:[results objectForKey:feed_id] forKey:@"favicon"];
@ -1801,11 +1767,9 @@ heightForHeaderInSection:(NSInteger)section {
[self loadAvatars];
});
});
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSError *error = [request error];
- (void)requestFailed:(NSError *)error {
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
@ -1838,26 +1802,26 @@ heightForHeaderInSection:(NSInteger)section {
urlString = [NSString stringWithFormat:@"%@/reader/refresh_feeds",
self.appDelegate.url];
}
NSURL *urlFeedList = [NSURL URLWithString:urlString];
if (!feedId) {
[self.appDelegate cancelOfflineQueue];
}
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:urlFeedList];
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
[request setValidatesSecureCertificate:NO];
[request setDelegate:self];
[request setResponseEncoding:NSUTF8StringEncoding];
[request setDefaultResponseEncoding:NSUTF8StringEncoding];
if (feedId) {
[request setUserInfo:@{@"feedId": [NSString stringWithFormat:@"%@", feedId]}];
}
[request setDidFinishSelector:@selector(finishRefreshingFeedList:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setTimeOutSeconds:30];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishRefreshingFeedList:responseObject feedId:feedId];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response;
if ([httpResponse statusCode] == 403) {
NSLog(@"Showing login after refresh");
return [appDelegate showLogin];
} else if ([httpResponse statusCode] == 503) {
return [self informError:@"In maintenance mode"];
} else if ([httpResponse statusCode] >= 500) {
return [self informError:@"The server barfed!"];
}
[self requestFailed:error];
}];
dispatch_async(dispatch_get_main_queue(), ^{
if (!feedId) {
@ -1867,26 +1831,9 @@ heightForHeaderInSection:(NSInteger)section {
}
- (void)finishRefreshingFeedList:(ASIHTTPRequest *)request {
if ([request responseStatusCode] == 403) {
NSLog(@"Showing login after refresh");
return [appDelegate showLogin];
} else if ([request responseStatusCode] == 503) {
return [self informError:@"In maintenance mode"];
} else if ([request responseStatusCode] >= 500) {
return [self informError:@"The server barfed!"];
}
- (void)finishRefreshingFeedList:(NSDictionary *)results feedId:(NSString *)feedId {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSDictionary *newFeedCounts = [results objectForKey:@"feeds"];
NSInteger intelligenceLevel = [appDelegate selectedIntelligence];
for (id feed in newFeedCounts) {
@ -1945,7 +1892,7 @@ heightForHeaderInSection:(NSInteger)section {
[appDelegate.folderCountCache removeAllObjects];
[self.feedTitlesTable reloadData];
[self refreshHeaderCounts];
if (![request.userInfo objectForKey:@"feedId"]) {
if (!feedId) {
[self.appDelegate startOfflineQueue];
}
[self loadFavicons];
@ -2058,31 +2005,6 @@ heightForHeaderInSection:(NSInteger)section {
userInfoBarButton, nil];
}
- (void)checkForFeedNotifications {
NSMutableArray *notificationFeedIds = [NSMutableArray array];
for (NSDictionary *feed in appDelegate.dictFeeds.allValues) {
NSArray *types = [feed objectForKey:@"notification_types"];
if (types) {
for (NSString *notificationType in types) {
if ([notificationType isEqualToString:@"ios"]) {
[appDelegate registerForRemoteNotifications];
}
}
if ([types count]) {
[notificationFeedIds addObject:[feed objectForKey:@"id"]];
}
}
}
appDelegate.notificationFeedIds = [notificationFeedIds sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSString *feed1Title = [[[appDelegate.dictFeeds objectForKey:[NSString stringWithFormat:@"%@", obj1]] objectForKey:@"feed_title"] lowercaseString];
NSString *feed2Title = [[[appDelegate.dictFeeds objectForKey:[NSString stringWithFormat:@"%@", obj2]] objectForKey:@"feed_title"] lowercaseString];
return [feed1Title compare:feed2Title];
}];
}
- (void)refreshHeaderCounts {
if (!appDelegate.activeUsername) {
userAvatarButton.customView.hidden = YES;
@ -2146,13 +2068,13 @@ heightForHeaderInSection:(NSInteger)section {
[self finishRefresh];
}
- (void)showSyncingNotifier:(float)progress hoursBack:(int)hours {
- (void)showSyncingNotifier:(float)progress hoursBack:(NSInteger)hours {
// [self.notifier hide];
self.notifier.style = NBSyncingProgressStyle;
if (hours < 2) {
self.notifier.title = @"Storing past hour";
} else if (hours < 24) {
self.notifier.title = [NSString stringWithFormat:@"Storing past %d hours", hours];
self.notifier.title = [NSString stringWithFormat:@"Storing past %ld hours", (long)hours];
} else if (hours < 48) {
self.notifier.title = @"Storing yesterday";
} else {
@ -2163,13 +2085,13 @@ heightForHeaderInSection:(NSInteger)section {
[self.notifier show];
}
- (void)showCachingNotifier:(float)progress hoursBack:(int)hours {
- (void)showCachingNotifier:(float)progress hoursBack:(NSInteger)hours {
// [self.notifier hide];
self.notifier.style = NBSyncingProgressStyle;
if (hours < 2) {
self.notifier.title = @"Images from last hour";
} else if (hours < 24) {
self.notifier.title = [NSString stringWithFormat:@"Images from %d hours ago", hours];
self.notifier.title = [NSString stringWithFormat:@"Images from %ld hours ago", (long)hours];
} else if (hours < 48) {
self.notifier.title = @"Images from yesterday";
} else {

View file

@ -33,6 +33,7 @@
[selectedBackground setBackgroundColor:UIColorFromRGB(0xECEEEA)];
[self setSelectedBackgroundView:selectedBackground];
NSDictionary *controlAttrs = @{NSForegroundColorAttributeName: [UIColor lightGrayColor]};
self.filterControl = [[UISegmentedControl alloc] initWithItems:@[@"Unread Stories",
@"Focus Stories"]];
self.filterControl.tintColor = UIColorFromRGB(0x8F918B);
@ -44,6 +45,7 @@
[self.filterControl setImage:[UIImage imageNamed:@"unread_green.png"] forSegmentAtIndex:1];
[self.filterControl setWidth:CGRectGetWidth(self.frame)*0.46 forSegmentAtIndex:0];
[self.filterControl setWidth:CGRectGetWidth(self.frame)*0.46 forSegmentAtIndex:1];
[self.filterControl setTitleTextAttributes:controlAttrs forState:UIControlStateNormal];
self.filterControl.frame = CGRectMake(36, 38, CGRectGetWidth(self.frame), 28);
[self.contentView addSubview:self.filterControl];
@ -65,6 +67,7 @@
[self.notificationTypeControl setWidth:CGRectGetWidth(self.frame)*0.23 forSegmentAtIndex:1];
[self.notificationTypeControl setWidth:CGRectGetWidth(self.frame)*0.23 forSegmentAtIndex:2];
[self.notificationTypeControl setWidth:CGRectGetWidth(self.frame)*0.23 forSegmentAtIndex:3];
[self.notificationTypeControl setTitleTextAttributes:controlAttrs forState:UIControlStateNormal];
self.notificationTypeControl.frame = CGRectMake(36, 76, CGRectGetWidth(self.frame), 28);
[self.contentView addSubview:self.notificationTypeControl];
}
@ -75,20 +78,26 @@
- (void)layoutSubviews {
[super layoutSubviews];
self.imageView.frame = CGRectMake(10.0, 10.0, 16.0, 16.0);
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
CGRect frame = self.textLabel.frame;
frame.origin.x = 35.0;
frame.size.width = self.detailTextLabel.frame.origin.x - self.textLabel.frame.origin.x;
self.textLabel.frame = frame;
self.textLabel.backgroundColor = [UIColor clearColor];
self.textLabel.textColor = UIColorFromRGB(0x303030);
self.textLabel.shadowColor = UIColorFromRGB(0xF0F0F0);
self.textLabel.shadowOffset = CGSizeMake(0, 1);
self.textLabel.highlightedTextColor = UIColorFromRGB(0x303030);
self.detailTextLabel.highlightedTextColor = UIColorFromRGB(0x505050);
self.detailTextLabel.textColor = UIColorFromRGB(0x505050);
self.backgroundColor = UIColorFromRGB(0xFFFFFF);
self.backgroundView.backgroundColor = UIColorFromRGB(0xFFFFFF);
self.selectedBackgroundView.backgroundColor = UIColorFromRGB(0xECEEEA);
CGRect textFrame = self.textLabel.frame;
self.imageView.frame = CGRectMake(10.0, 10.0, 16.0, 16.0);
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.textLabel sizeToFit];
CGRect frame = self.textLabel.frame;
frame.origin.x = 35.0;
frame.size.width = self.detailTextLabel.frame.origin.x - self.textLabel.frame.origin.x;
self.textLabel.frame = frame;
CGRect textFrame = self.textLabel.frame;
textFrame.origin.y = 10;
self.textLabel.frame = textFrame;
@ -96,15 +105,6 @@
detailFrame.origin.y = 10;
self.detailTextLabel.frame = detailFrame;
self.textLabel.highlightedTextColor = UIColorFromRGB(0x303030);
self.detailTextLabel.highlightedTextColor = UIColorFromRGB(0x505050);
self.detailTextLabel.textColor = UIColorFromRGB(0x505050);
self.backgroundColor = UIColorFromRGB(0xFFFFFF);
self.backgroundView.backgroundColor = UIColorFromRGB(0xFFFFFF);
self.selectedBackgroundView.backgroundColor = UIColorFromRGB(0xECEEEA);
CGFloat detailTextLabelWidth = self.detailTextLabel.attributedText.size.width;
CGRect detailTextLabelFrame = self.detailTextLabel.frame;
CGFloat detailTextLabelExtraWidth = detailTextLabelWidth - detailTextLabelFrame.size.width;
@ -114,7 +114,7 @@
self.detailTextLabel.frame = detailTextLabelFrame;
CGRect textLabelFrame = self.textLabel.frame;
textLabelFrame.size.width -= detailTextLabelExtraWidth;
textLabelFrame.size.width = self.detailTextLabel.frame.origin.x - self.textLabel.frame.origin.x;
self.textLabel.frame = textLabelFrame;
}
@ -153,21 +153,23 @@
NSString *urlString = [NSString stringWithFormat:@"%@/notifications/feed/",
appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:self.feedId forKey:@"feed_id"];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:self.feedId forKey:@"feed_id"];
NSMutableArray *notifications = [NSMutableArray array];
for (NSString *notificationType in notificationTypes) {
[request addPostValue:notificationType forKey:@"notification_types"];
[notifications addObject:notificationType];
}
[request setPostValue:notificationFilter forKey:@"notification_filter"];
[request setCompletionBlock:^{
[params setObject:notifications forKey:@"notification_types"];
[params setObject:notificationFilter forKey:@"notification_filter"];
[appDelegate updateNotifications:params feed:self.feedId];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"Saved notifications %@: %@ / %@", self.feedId, notificationTypes, notificationFilter);
}];
[request setFailedBlock:^{
[appDelegate checkForFeedNotifications];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed to save notifications: %@ / %@", notificationTypes, notificationFilter);
}];
[request setDelegate:self];
[request startAsynchronous];
}
@end

View file

@ -13,6 +13,7 @@
@interface NotificationsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
NewsBlurAppDelegate *appDelegate;
NSArray *notificationFeedIds;
}
@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate;

View file

@ -38,6 +38,7 @@
notificationsTable.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);;
notificationsTable.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
notificationsTable.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:notificationsTable];
}
@ -53,8 +54,17 @@
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
notificationsTable.backgroundColor = UIColorFromRGB(0xECEEEA);
notificationsTable.separatorColor = UIColorFromRGB(0xF0F0F0);
notificationsTable.sectionIndexColor = UIColorFromRGB(0x303030);
notificationsTable.sectionIndexBackgroundColor = UIColorFromRGB(0xDCDFD6);
notificationFeedIds = [appDelegate.notificationFeedIds copy];
[notificationsTable reloadData];
[notificationsTable setContentOffset:CGPointZero];
self.view.backgroundColor = UIColorFromRGB(NEWSBLUR_WHITE_COLOR);
}
@ -104,11 +114,11 @@ viewForHeaderInSection:(NSInteger)section {
customView.opaque = NO;
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.opaque = NO;
headerLabel.textColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0];
headerLabel.textColor = UIColorFromRGB(0x4C4C4C);
headerLabel.highlightedTextColor = UIColorFromRGB(NEWSBLUR_WHITE_COLOR);
headerLabel.font = [UIFont boldSystemFontOfSize:11];
headerLabel.frame = CGRectMake(36.0, 1.0, 286.0, headerLabelHeight);
headerLabel.shadowColor = [UIColor colorWithRed:.94 green:0.94 blue:0.97 alpha:1.0];
headerLabel.shadowColor = UIColorFromRGB(0xF0F0F7);
headerLabel.shadowOffset = CGSizeMake(0.0, 1.0);
if (self.feedId && section == 0) {
headerLabel.text = @"SITE NOTIFICATIONS";
@ -152,11 +162,31 @@ viewForHeaderInSection:(NSInteger)section {
if (self.feedId && section == 0) {
return 1;
}
return MAX(appDelegate.notificationFeedIds.count, 1);
return MAX(notificationFeedIds.count, 1);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CGRect vb = self.view.bounds;
BOOL feedSection = NO;
if (self.feedId != nil && indexPath.section == 0) feedSection = YES;
if (notificationFeedIds.count == 0 && !feedSection) {
UITableViewCell *cell = [[UITableViewCell alloc] init];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.backgroundColor = [UIColor clearColor];
CGRect vb = self.view.bounds;
CGFloat height = [self tableView:tableView heightForRowAtIndexPath:indexPath];
UILabel *msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, vb.size.width, height)];
[cell.contentView addSubview:msg];
msg.text = @"No notifications yet.";
msg.textColor = UIColorFromRGB(0x7a7a7a);
if (vb.size.width > 320) {
msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 20.0];
} else {
msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 14.0];
}
msg.textAlignment = NSTextAlignmentCenter;
return cell;
}
static NSString *CellIdentifier = @"NotificationFeedCellIdentifier";
NotificationFeedCell *cell = [tableView
@ -167,37 +197,24 @@ viewForHeaderInSection:(NSInteger)section {
initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:CellIdentifier];
}
if (appDelegate.notificationFeedIds.count == 0) {
UILabel *msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, vb.size.width, 140)];
[cell.contentView addSubview:msg];
msg.text = @"No results.";
msg.textColor = UIColorFromRGB(0x7a7a7a);
if (vb.size.width > 320) {
msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 20.0];
} else {
msg.font = [UIFont fontWithName:@"Helvetica-Bold" size: 14.0];
}
msg.textAlignment = NSTextAlignmentCenter;
} else {
NSDictionary *feed;
NSString *feedIdStr;
if (self.feedId && indexPath.section == 0) {
feedIdStr = feedId;
feed = [appDelegate.dictFeeds objectForKey:feedId];
} else {
feedIdStr = [NSString stringWithFormat:@"%@",
appDelegate.notificationFeedIds[indexPath.row]];
feed = [appDelegate.dictFeeds objectForKey:feedIdStr];
}
cell.feedId = feedIdStr;
cell.textLabel.text = [feed objectForKey:@"feed_title"];
cell.imageView.image = [self.appDelegate getFavicon:feedIdStr isSocial:NO isSaved:NO];
cell.detailTextLabel.text = [NSString localizedStringWithFormat:NSLocalizedString(@"%@ stories/month", @"average stories per month"), feed[@"average_stories_per_month"]];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSDictionary *feed;
NSString *feedIdStr;
if (self.feedId && indexPath.section == 0) {
feedIdStr = feedId;
feed = [appDelegate.dictFeeds objectForKey:feedId];
} else {
feedIdStr = [NSString stringWithFormat:@"%@",
notificationFeedIds[indexPath.row]];
feed = [appDelegate.dictFeeds objectForKey:feedIdStr];
}
cell.feedId = feedIdStr;
cell.textLabel.text = [feed objectForKey:@"feed_title"];
cell.imageView.image = [self.appDelegate getFavicon:feedIdStr isSocial:NO isSaved:NO];
cell.detailTextLabel.text = [NSString localizedStringWithFormat:NSLocalizedString(@"%@ stories/month", @"average stories per month"), feed[@"average_stories_per_month"]];
return cell;
}

View file

@ -6,7 +6,6 @@
// Copyright 2010 NewsBlur. All rights reserved.
//
#import "NewsBlurAppDelegate.h"
#import "NBContainerViewController.h"
#import "OriginalStoryViewController.h"
#import "NSString+HTML.h"

View file

@ -9,7 +9,6 @@
#import <UIKit/UIKit.h>
@class NewsBlurAppDelegate;
@class ASIHTTPRequest;
@interface ProfileBadge : UITableViewCell {
NewsBlurAppDelegate *appDelegate;
@ -40,9 +39,6 @@
- (void)refreshWithProfile:(NSDictionary *)profile showStats:(BOOL)showStats withWidth:(int)newWidth;
- (IBAction)doFollowButton:(id)sender;
- (void)finishFollowing:(ASIHTTPRequest *)request;
- (void)finishUnfollowing:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)initProfile;
@end

View file

@ -9,7 +9,6 @@
#import "ProfileBadge.h"
#import "NewsBlurAppDelegate.h"
#import "Utilities.h"
#import "ASIHTTPRequest.h"
#import "UIImageView+AFNetworking.h"
#import <QuartzCore/QuartzCore.h>
@ -325,31 +324,22 @@
self.appDelegate.url];
}
NSURL *url = [NSURL URLWithString:urlString];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[self.activeProfile objectForKey:@"user_id"] forKey:@"user_id"];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setDelegate:self];
[request setPostValue:[self.activeProfile objectForKey:@"user_id"] forKey:@"user_id"];
if ([self.followButton.currentTitle isEqualToString:@"Follow"]) {
[request setDidFinishSelector:@selector(finishFollowing:)];
} else {
[request setDidFinishSelector:@selector(finishUnfollowing:)];
}
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if ([self.followButton.currentTitle isEqualToString:@"Follow"]) {
[self finishFollowing:responseObject];
} else {
[self finishUnfollowing:responseObject];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)finishFollowing:(ASIHTTPRequest *)request {
- (void)finishFollowing:(NSDictionary *)results {
[self.activityIndicator stopAnimating];
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
@ -373,15 +363,9 @@
}
- (void)finishUnfollowing:(ASIHTTPRequest *)request {
- (void)finishUnfollowing:(NSDictionary *)results {
[self.activityIndicator stopAnimating];
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
NSLog(@"ERROR");
@ -405,9 +389,8 @@
[self refreshWithProfile:newProfile showStats:self.shouldShowStats withWidth:0];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
- (void)requestFailed:(NSError *)error {
[self.activityIndicator stopAnimating];
NSError *error = [request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}

View file

@ -9,8 +9,7 @@
#import <UIKit/UIKit.h>
#import "NewsBlurAppDelegate.h"
@interface ShareViewController : BaseViewController <ASIHTTPRequestDelegate, UITextViewDelegate> {
NewsBlurAppDelegate *appDelegate;
@interface ShareViewController : BaseViewController <UITextViewDelegate> {
NSString *activeReplyId;
}
@ -34,9 +33,6 @@
- (IBAction)doToggleButton:(id)sender;
- (IBAction)doShareThisStory:(id)sender;
- (IBAction)doReplyToComment:(id)sender;
- (void)finishShareThisStory:(ASIHTTPRequest *)request;
- (void)finishAddReply:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)replaceStory:(NSDictionary *)newStory withReplyId:(NSString *)replyId;
- (void)adjustShareButtons;
- (void)adjustCommentField:(CGSize)kbSize;

View file

@ -14,7 +14,6 @@
#import <QuartzCore/QuartzCore.h>
#import "Utilities.h"
#import "DataUtilities.h"
#import "ASIHTTPRequest.h"
#import "StoriesCollection.h"
#import "NSString+HTML.h"
@ -354,64 +353,51 @@
[appDelegate.storyPageControl showShareHUD:@"Sharing"];
NSString *urlString = [NSString stringWithFormat:@"%@/social/share_story",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSMutableArray *services = [NSMutableArray array];
NSString *feedIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]];
NSString *storyIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"id"]];
[request setPostValue:feedIdStr forKey:@"feed_id"];
[request setPostValue:storyIdStr forKey:@"story_id"];
[params setObject:feedIdStr forKey:@"feed_id"];
[params setObject:storyIdStr forKey:@"story_id"];
if (facebookButton.selected) {
[request addPostValue:@"facebook" forKey:@"post_to_services"];
[services addObject:@"facebook"];
}
if (twitterButton.selected) {
[request addPostValue:@"twitter" forKey:@"post_to_services"];
}
if (appdotnetButton.selected) {
[request addPostValue:@"appdotnet" forKey:@"post_to_services"];
[services addObject:@"twitter"];
}
[params setObject:services forKey:@"post_to_services"];
if (appDelegate.storiesCollection.isSocialRiverView) {
if ([[appDelegate.activeStory objectForKey:@"friend_user_ids"] count] > 0) {
[request setPostValue:[NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"friend_user_ids"][0]] forKey:@"source_user_id"];
[params setObject:[NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"friend_user_ids"][0]] forKey:@"source_user_id"];
} else if ([[appDelegate.activeStory objectForKey:@"public_user_ids"] count] > 0) {
[request setPostValue:[NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"public_user_ids"][0]] forKey:@"source_user_id"];
[params setObject:[NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"public_user_ids"][0]] forKey:@"source_user_id"];
}
} else {
if ([appDelegate.activeStory objectForKey:@"social_user_id"] != nil) {
NSString *sourceUserIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"social_user_id"]];
[request setPostValue:sourceUserIdStr forKey:@"source_user_id"];
[params setObject:sourceUserIdStr forKey:@"source_user_id"];
}
}
NSString *comments = commentField.text;
if ([comments length]) {
[request setPostValue:comments forKey:@"comments"];
[params setObject:comments forKey:@"comments"];
}
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishShareThisStory:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishShareThisStory:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
[appDelegate hideShareView:YES];
}
- (void)finishShareThisStory:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
if (request.responseStatusCode != 200) {
return [self requestFailed:request];
}
- (void)finishShareThisStory:(NSDictionary *)results {
NSArray *userProfiles = [results objectForKey:@"user_profiles"];
appDelegate.storiesCollection.activeFeedUserProfiles = [DataUtilities
updateUserProfiles:appDelegate.storiesCollection.activeFeedUserProfiles
@ -437,53 +423,36 @@
NSString *feedIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"story_feed_id"]];
NSString *storyIdStr = [NSString stringWithFormat:@"%@", [appDelegate.activeStory objectForKey:@"id"]];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:feedIdStr forKey:@"story_feed_id"];
[request setPostValue:storyIdStr forKey:@"story_id"];
[request setPostValue:[appDelegate.activeComment objectForKey:@"user_id"] forKey:@"comment_user_id"];
[request setPostValue:commentField.text forKey:@"reply_comments"];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:feedIdStr forKey:@"story_feed_id"];
[params setObject:storyIdStr forKey:@"story_id"];
[params setObject:[appDelegate.activeComment objectForKey:@"user_id"] forKey:@"comment_user_id"];
[params setObject:commentField.text forKey:@"reply_comments"];
if (self.activeReplyId) {
[request setPostValue:activeReplyId forKey:@"reply_id"];
[params setObject:activeReplyId forKey:@"reply_id"];
}
[request setDelegate:self];
[request setDidFinishSelector:@selector(finishAddReply:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishAddReply:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
[appDelegate hideShareView:NO];
}
- (void)finishAddReply:(ASIHTTPRequest *)request {
- (void)finishAddReply:(NSDictionary *)results {
NSLog(@"Successfully added.");
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
if (request.responseStatusCode != 200) {
return [self requestFailed:request];
}
// add the comment into the activeStory dictionary
NSDictionary *newStory = [DataUtilities updateComment:results for:appDelegate];
[self replaceStory:newStory withReplyId:[results objectForKey:@"reply_id"]];
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSString *error;
- (void)requestFailed:(NSError *)error {
[MBProgressHUD hideHUDForView:appDelegate.storyPageControl.view animated:NO];
if ([request error]) {
error = [NSString stringWithFormat:@"%@", [request error]];
} else {
error = @"The server barfed!";
}
NSLog(@"Error: %@", error);
[appDelegate.storyPageControl.currentPage informError:error];
}

View file

@ -104,10 +104,6 @@
- (void)toggleStorySaved;
- (BOOL)toggleStorySaved:(NSDictionary *)story;
- (void)syncStoryAsSaved:(NSDictionary *)story;
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request;
- (void)syncStoryAsUnsaved:(NSDictionary *)story;
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request;
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request;
@end

View file

@ -343,28 +343,26 @@
- (void)syncStoryAsRead:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_hashes_as_read",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[story objectForKey:@"story_hash"]
forKey:@"story_hash"];
[params setObject:[story objectForKey:@"story_feed_id"]
forKey:@"story_feed_id"];
[request setPostValue:[story objectForKey:@"story_hash"]
forKey:@"story_hash"];
[request setDidFinishSelector:@selector(finishMarkAsRead:)];
[request setDidFailSelector:@selector(failedMarkAsRead:)];
[request setDelegate:self];
[request setUserInfo:story];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAsRead:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedMarkAsRead:params];
}];
}
- (void)finishMarkAsRead:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self failedMarkAsRead:request];
}
- (void)finishMarkAsRead:(NSDictionary *)results {
}
- (void)failedMarkAsRead:(ASIFormDataRequest *)request {
NSString *storyFeedId = [request.userInfo objectForKey:@"story_feed_id"];
NSString *storyHash = [request.userInfo objectForKey:@"story_hash"];
- (void)failedMarkAsRead:(NSDictionary *)params {
NSString *storyFeedId = [params objectForKey:@"story_feed_id"];
NSString *storyHash = [params objectForKey:@"story_hash"];
[appDelegate queueReadStories:@{storyFeedId: @[storyHash]}];
}
@ -372,38 +370,27 @@
- (void)syncStoryAsUnread:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_as_unread",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[request setPostValue:[story objectForKey:@"story_hash"]
[params setObject:[story objectForKey:@"story_hash"]
forKey:@"story_id"];
[request setPostValue:[story objectForKey:@"story_feed_id"]
[params setObject:[story objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAsUnread:)];
[request setDidFailSelector:@selector(failedMarkAsUnread:)];
[request setDelegate:self];
[request setUserInfo:story];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAsUnread:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedMarkAsUnread:params];
}];
}
- (void)finishMarkAsUnread:(ASIFormDataRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)finishMarkAsUnread:(NSDictionary *)results {
if ([request responseStatusCode] != 200 || [[results objectForKey:@"code"] integerValue] < 0) {
return [self failedMarkAsUnread:request];
}
}
- (void)failedMarkAsUnread:(ASIFormDataRequest *)request {
NSString *storyFeedId = [request.userInfo objectForKey:@"story_feed_id"];
NSString *storyHash = [request.userInfo objectForKey:@"story_hash"];
- (void)failedMarkAsUnread:(NSDictionary *)params {
NSString *storyFeedId = [params objectForKey:@"story_feed_id"];
NSString *storyHash = [params objectForKey:@"story_hash"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
@ -413,7 +400,7 @@
// Offline means can't unread a story unless it was read while offline.
[self markStoryRead:storyHash feedId:storyFeedId];
// [self.storyTitlesTable reloadData];
[appDelegate failedMarkAsUnread:request];
[appDelegate failedMarkAsUnread:params];
} else {
// Offline but read story while offline, so it never touched the server.
[appDelegate.unreadStoryHashes setObject:[NSNumber numberWithBool:YES] forKey:storyHash];
@ -723,93 +710,66 @@
- (void)syncStoryAsSaved:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_as_starred",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[request setPostValue:[story objectForKey:@"story_hash"]
[params setObject:[story objectForKey:@"story_hash"]
forKey:@"story_id"];
[request setPostValue:[story objectForKey:@"story_feed_id"]
[params setObject:[story objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
NSMutableArray *tags = [NSMutableArray array];
for (NSString *userTag in [story objectForKey:@"user_tags"]) {
[request addPostValue:userTag
forKey:@"user_tags"];
[tags addObject:userTag];
}
[params setObject:tags forKey:@"user_tags"];
[request setDidFinishSelector:@selector(finishMarkAsSaved:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setUserInfo:@{@"story_feed_id":[story
objectForKey:@"story_feed_id"],
@"story_hash":[story
objectForKey:@"story_hash"]}];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAsSaved:responseObject withParams:params];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self markStory:params asSaved:NO];
[appDelegate failedMarkAsSaved:params];
}];
}
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self failedMarkAsSaved:request];
}
- (void)finishMarkAsSaved:(NSDictionary *)results withParams:(NSDictionary *)params {
[self updateSavedStoryCounts:results withParams:params];
[self updateSavedStoryCounts:request];
[appDelegate finishMarkAsSaved:request];
[appDelegate finishMarkAsSaved:params];
}
- (void)updateSavedStoryCounts:(ASIFormDataRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)updateSavedStoryCounts:(NSDictionary *)results withParams:(NSDictionary *)params {
NSArray *savedStories = [appDelegate updateStarredStoryCounts:results];
NSMutableDictionary *allFolders = [appDelegate.dictFolders mutableCopy];
[allFolders setValue:savedStories forKey:@"saved_stories"];
appDelegate.dictFolders = allFolders;
}
- (void)failedMarkAsSaved:(ASIFormDataRequest *)request {
[self markStory:request.userInfo asSaved:NO];
[appDelegate failedMarkAsSaved:request];
}
- (void)syncStoryAsUnsaved:(NSDictionary *)story {
- (void)syncStoryAsUnsaved:(NSDictionary *)story {
NSString *urlString = [NSString stringWithFormat:@"%@/reader/mark_story_as_unstarred",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[story
objectForKey:@"story_hash"]
forKey:@"story_id"];
[request setPostValue:[story
objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
[request setDidFinishSelector:@selector(finishMarkAsUnsaved:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setUserInfo:@{@"story_feed_id":[story
objectForKey:@"story_feed_id"],
@"story_hash":[story
objectForKey:@"story_hash"]}];
[request setDelegate:self];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[story
objectForKey:@"story_hash"]
forKey:@"story_id"];
[params setObject:[story
objectForKey:@"story_feed_id"]
forKey:@"feed_id"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishMarkAsUnsaved:responseObject withParams:params];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self markStory:params asSaved:YES];
[appDelegate failedMarkAsUnsaved:params];
}];
}
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self failedMarkAsUnsaved:request];
}
[self updateSavedStoryCounts:request];
[appDelegate finishMarkAsUnsaved:request];
- (void)finishMarkAsUnsaved:(NSDictionary *)results withParams:(NSDictionary *)params {
[self updateSavedStoryCounts:results withParams:params];
[appDelegate finishMarkAsUnsaved:params];
}
- (void)failedMarkAsUnsaved:(ASIFormDataRequest *)request {
[self markStory:request.userInfo asSaved:YES];
[appDelegate failedMarkAsUnsaved:request];
- (void)failedMarkAsUnsaved:(NSDictionary *)params {
[self markStory:params asSaved:YES];
[appDelegate failedMarkAsUnsaved:params];
}
@end

View file

@ -11,7 +11,6 @@
#import "BaseViewController.h"
@class NewsBlurAppDelegate;
@class ASIHTTPRequest;
@interface StoryDetailViewController : BaseViewController
<UIScrollViewDelegate, UIGestureRecognizerDelegate,
@ -28,9 +27,6 @@ UIActionSheetDelegate> {
BOOL inDoubleTap;
BOOL hasScrolled;
NSURL *activeLongPressUrl;
NSInteger actionSheetViewImageIndex;
NSInteger actionSheetCopyImageIndex;
NSInteger actionSheetSaveImageIndex;
CGSize preRotateSize;
CGFloat scrollPct;
@ -80,10 +76,7 @@ UIActionSheetDelegate> {
- (void)openShareDialog;
- (void)openTrainingDialog:(int)x yCoordinate:(int)y width:(int)width height:(int)height;
- (void)openUserTagsDialog:(int)x yCoordinate:(int)y width:(int)width height:(int)height;
- (void)finishLikeComment:(ASIHTTPRequest *)request;
- (void)subscribeToBlurblog;
- (void)finishSubscribeToBlurblog:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)setActiveStoryAtIndex:(NSInteger)activeStoryIndex;
- (NSString *)getHeader;
- (NSString *)getShareBar;
@ -99,6 +92,5 @@ UIActionSheetDelegate> {
- (void)showTextOrStoryView;
- (void)showStoryView;
- (void)fetchTextView;
- (void)finishFetchTextView:(ASIHTTPRequest *)request;
@end

View file

@ -7,6 +7,7 @@
//
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
#import "StoryDetailViewController.h"
#import "NewsBlurAppDelegate.h"
#import "NewsBlurViewController.h"
@ -15,9 +16,6 @@
#import "UserProfileViewController.h"
#import "ShareViewController.h"
#import "StoryPageControl.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import "AFHTTPRequestOperation.h"
#import "Base64.h"
#import "Utilities.h"
#import "NSString+HTML.h"
@ -293,9 +291,11 @@
- (void)viewWillLayoutSubviews {
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
[super viewWillLayoutSubviews];
[appDelegate.storyPageControl layoutForInterfaceOrientation:orientation];
[self changeWebViewWidth];
[self drawFeedGradient];
dispatch_async(dispatch_get_main_queue(), ^{
[appDelegate.storyPageControl layoutForInterfaceOrientation:orientation];
[self changeWebViewWidth];
[self drawFeedGradient];
});
// NSLog(@"viewWillLayoutSubviews: %.2f", self.webView.scrollView.bounds.size.width);
}
@ -375,7 +375,7 @@
fontStyleClass = [userPreferences stringForKey:@"fontStyle"];
if (!fontStyleClass) {
fontStyleClass = @"NB-helvetica";
fontStyleClass = @"GothamNarrow-Book";
}
fontSizeClass = [fontSizeClass stringByAppendingString:[userPreferences stringForKey:@"story_font_size"]];
@ -1537,7 +1537,7 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
return NO;
}
} else if ([url.host hasSuffix:@"itunes.apple.com"]) {
[[UIApplication sharedApplication] openURL:url];
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
return NO;
}
@ -1716,39 +1716,19 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
self.appDelegate.url];
}
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[self.activeStory objectForKey:@"id"] forKey:@"story_id"];
[params setObject:[self.activeStory objectForKey:@"story_feed_id"] forKey:@"story_feed_id"];
[params setObject:[appDelegate.activeComment objectForKey:@"user_id"] forKey:@"comment_user_id"];
[request setPostValue:[self.activeStory
objectForKey:@"id"]
forKey:@"story_id"];
[request setPostValue:[self.activeStory
objectForKey:@"story_feed_id"]
forKey:@"story_feed_id"];
[request setPostValue:[appDelegate.activeComment objectForKey:@"user_id"] forKey:@"comment_user_id"];
[request setDidFinishSelector:@selector(finishLikeComment:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setDelegate:self];
[request startAsynchronous];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishLikeComment:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)finishLikeComment:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
if (request.responseStatusCode != 200) {
return [self requestFailed:request];
}
- (void)finishLikeComment:(NSDictionary *)results {
// add the comment into the activeStory dictionary
NSDictionary *newStory = [DataUtilities updateComment:results for:appDelegate];
@ -1776,17 +1756,11 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
}
- (void)requestFailed:(ASIHTTPRequest *)request {
NSLog(@"Error in story detail: %@", [request error]);
NSString *error;
- (void)requestFailed:(NSError *)error {
NSLog(@"Error in story detail: %@", error);
[MBProgressHUD hideHUDForView:appDelegate.storyPageControl.view animated:NO];
if ([request error]) {
error = [NSString stringWithFormat:@"%@", [request error]];
} else {
error = @"The server barfed!";
}
[self informError:error];
}
@ -1909,17 +1883,28 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
title = title.length ? title : alt;
activeLongPressUrl = [NSURL URLWithString:src];
UIActionSheet *actions = [[UIActionSheet alloc] initWithTitle:title.length ? title : nil
delegate:self
cancelButtonTitle:@"Done"
destructiveButtonTitle:nil
otherButtonTitles:nil];
actionSheetViewImageIndex = [actions addButtonWithTitle:@"View and zoom"];
actionSheetCopyImageIndex = [actions addButtonWithTitle:@"Copy image"];
actionSheetSaveImageIndex = [actions addButtonWithTitle:@"Save to camera roll"];
[actions showFromRect:CGRectMake(pt.x, pt.y, 1, 1)
inView:appDelegate.storyPageControl.view animated:YES];
// [actions showInView:appDelegate.storyPageControl.view];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title.length ? title : nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"View and zoom" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[appDelegate showOriginalStory:activeLongPressUrl];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Copy image" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self fetchImage:activeLongPressUrl copy:YES save:NO];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Save to camera roll" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[self fetchImage:activeLongPressUrl copy:NO save:YES];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}]];
[alert setModalPresentationStyle:UIModalPresentationPopover];
UIPopoverPresentationController *popover = [alert popoverPresentationController];
popover.sourceRect = CGRectMake(pt.x, pt.y, 1, 1);
popover.sourceView = appDelegate.storyPageControl.view;
[self presentViewController:alert animated:YES completion:nil];
}
- (void)showLinkContextMenu:(CGPoint)pt {
@ -1986,38 +1971,38 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
return pt;
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == actionSheetViewImageIndex) {
[appDelegate showOriginalStory:activeLongPressUrl];
} else if (buttonIndex == actionSheetCopyImageIndex ||
buttonIndex == actionSheetSaveImageIndex) {
[self fetchImage:activeLongPressUrl buttonIndex:buttonIndex];
}
}
- (void)fetchImage:(NSURL *)url buttonIndex:(NSInteger)buttonIndex {
- (void)fetchImage:(NSURL *)url copy:(BOOL)copy save:(BOOL)save {
[MBProgressHUD hideHUDForView:self.webView animated:YES];
[appDelegate.storyPageControl showShareHUD:buttonIndex == actionSheetCopyImageIndex ?
[appDelegate.storyPageControl showShareHUD:copy ?
@"Copying..." : @"Saving..."];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc]
initWithRequest:urlRequest];
[requestOperation setResponseSerializer:[AFImageResponseSerializer serializer]];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager setResponseSerializer:[AFImageResponseSerializer serializer]];
[manager GET:url.absoluteString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
UIImage *image = responseObject;
if (buttonIndex == actionSheetCopyImageIndex) {
if (copy) {
[UIPasteboard generalPasteboard].image = image;
[self flashCheckmarkHud:@"copied"];
} else if (buttonIndex == actionSheetSaveImageIndex) {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
[self flashCheckmarkHud:@"saved"];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (save) {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
changeRequest.creationDate = [NSDate date];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
[self flashCheckmarkHud:@"saved"];
} else {
[MBProgressHUD hideHUDForView:self.webView animated:NO];
[self informError:error];
}
});
}];
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[MBProgressHUD hideHUDForView:self.webView animated:YES];
[self informError:@"Could not fetch image"];
}];
[requestOperation start];
}
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
@ -2033,21 +2018,19 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
[appDelegate.storyPageControl showShareHUD:@"Following"];
NSString *urlString = [NSString stringWithFormat:@"%@/social/follow",
self.appDelegate.url];
NSURL *url = [NSURL URLWithString:urlString];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:[appDelegate.storiesCollection.activeFeed
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[appDelegate.storiesCollection.activeFeed
objectForKey:@"user_id"]
forKey:@"user_id"];
[request setDidFinishSelector:@selector(finishSubscribeToBlurblog:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request setDelegate:self];
[request startAsynchronous];
}
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishSubscribeToBlurblog:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self requestFailed:error];
}];
}
- (void)finishSubscribeToBlurblog:(ASIHTTPRequest *)request {
- (void)finishSubscribeToBlurblog:(NSDictionary *)results {
[MBProgressHUD hideHUDForView:appDelegate.storyPageControl.view animated:NO];
self.storyHUD = [MBProgressHUD showHUDAddedTo:self.webView animated:YES];
self.storyHUD.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"37x-Checkmark.png"]];
@ -2293,17 +2276,18 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
NSString *urlString = [NSString stringWithFormat:@"%@/rss_feeds/original_text",
self.appDelegate.url];
ASIFormDataRequest *request = [self formRequestWithURL:urlString];
[request addPostValue:[self.activeStory objectForKey:@"id"] forKey:@"story_id"];
[request addPostValue:[self.activeStory objectForKey:@"story_feed_id"] forKey:@"feed_id"];
[request setUserInfo:@{@"storyId": [self.activeStory objectForKey:@"id"]}];
[request setDidFinishSelector:@selector(finishFetchTextView:)];
[request setDidFailSelector:@selector(failedFetchText:)];
[request setDelegate:self];
[request startAsynchronous];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setObject:[self.activeStory objectForKey:@"id"] forKey:@"story_id"];
[params setObject:[self.activeStory objectForKey:@"story_feed_id"] forKey:@"feed_id"];
NSString *storyId = [self.activeStory objectForKey:@"id"];
[appDelegate.networkManager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self finishFetchTextView:responseObject storyId:storyId];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self failedFetchText:error];
}];
}
- (void)failedFetchText:(ASIHTTPRequest *)request {
- (void)failedFetchText:(NSError *)error {
[self.appDelegate.storyPageControl hideNotifier];
[MBProgressHUD hideHUDForView:self.webView animated:YES];
if (self.activeStory == appDelegate.storyPageControl.currentPage.activeStory) {
@ -2313,22 +2297,13 @@ shouldStartLoadWithRequest:(NSURLRequest *)request
[appDelegate.storyPageControl setTextButton:self];
}
- (void)finishFetchTextView:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
NSData *responseData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
- (void)finishFetchTextView:(NSDictionary *)results storyId:(NSString *)storyId {
if ([[results objectForKey:@"failed"] boolValue]) {
[self failedFetchText:request];
[self failedFetchText:nil];
return;
}
if (![[request.userInfo objectForKey:@"storyId"]
isEqualToString:[self.activeStory objectForKey:@"id"]]) {
if (![storyId isEqualToString:[self.activeStory objectForKey:@"id"]]) {
[self.appDelegate.storyPageControl hideNotifier];
[MBProgressHUD hideHUDForView:self.webView animated:YES];
self.inTextView = NO;

View file

@ -8,18 +8,13 @@
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
#import "NewsBlurAppDelegate.h"
#import "THCircularProgressView.h"
#import "NBNotifier.h"
@class NewsBlurAppDelegate;
@class ASIHTTPRequest;
#import "StoryDetailViewController.h"
@interface StoryPageControl : BaseViewController
<UIScrollViewDelegate, UIPopoverControllerDelegate, UIPopoverPresentationControllerDelegate, UIGestureRecognizerDelegate> {
NewsBlurAppDelegate *appDelegate;
THCircularProgressView *circularProgressView;
UIButton *buttonPrevious;
UIButton *buttonNext;
@ -38,7 +33,6 @@
CGFloat scrollPct;
}
@property (nonatomic) IBOutlet NewsBlurAppDelegate *appDelegate;
@property (nonatomic) StoryDetailViewController *currentPage;
@property (nonatomic) StoryDetailViewController *nextPage;
@property (nonatomic) StoryDetailViewController *previousPage;
@ -96,16 +90,10 @@
- (void)animateIntoPlace:(BOOL)animated;
- (void)changePage:(NSInteger)pageIndex;
- (void)changePage:(NSInteger)pageIndex animated:(BOOL)animated;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)setNextPreviousButtons;
- (void)setTextButton;
- (void)setTextButton:(StoryDetailViewController *)storyViewController;
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request;
- (BOOL)failedMarkAsSaved:(ASIFormDataRequest *)request;
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request;
- (BOOL)failedMarkAsUnsaved:(ASIFormDataRequest *)request;
- (BOOL)failedMarkAsUnread:(ASIFormDataRequest *)request;
- (void)subscribeToBlurblog;
- (IBAction)toggleFontSize:(id)sender;
@ -127,4 +115,10 @@
- (IBAction)tapProgressBar:(id)sender;
- (IBAction)toggleTextView:(id)sender;
- (void)finishMarkAsSaved:(NSDictionary *)params;
- (BOOL)failedMarkAsSaved:(NSDictionary *)params;
- (void)finishMarkAsUnsaved:(NSDictionary *)params;
- (BOOL)failedMarkAsUnsaved:(NSDictionary *)params;
- (BOOL)failedMarkAsUnread:(NSDictionary *)params;
@end

View file

@ -14,8 +14,6 @@
#import "FontSettingsViewController.h"
#import "UserProfileViewController.h"
#import "ShareViewController.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import "Base64.h"
#import "Utilities.h"
#import "NSString+HTML.h"
@ -29,7 +27,6 @@
@implementation StoryPageControl
@synthesize appDelegate;
@synthesize currentPage, nextPage, previousPage;
@synthesize circularProgressView;
@synthesize separatorBarButton;
@ -67,6 +64,7 @@
- (void)viewDidLoad {
[super viewDidLoad];
appDelegate = [NewsBlurAppDelegate sharedAppDelegate];
currentPage = [[StoryDetailViewController alloc]
initWithNibName:@"StoryDetailViewController"
bundle:nil];
@ -559,7 +557,7 @@
}
- (BOOL)isPhoneOrCompact {
return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone || self.appDelegate.isCompactWidth;
return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone || appDelegate.isCompactWidth;
}
- (void)updateTraverseBackground {
@ -623,15 +621,15 @@
if (newIndex > 0 && newIndex >= [appDelegate.storiesCollection.activeFeedStoryLocations count]) {
pageController.pageIndex = -2;
if (self.appDelegate.storiesCollection.feedPage < 100 &&
!self.appDelegate.feedDetailViewController.pageFinished &&
!self.appDelegate.feedDetailViewController.pageFetching) {
[self.appDelegate.feedDetailViewController fetchNextPage:^() {
if (appDelegate.storiesCollection.feedPage < 100 &&
!appDelegate.feedDetailViewController.pageFinished &&
!appDelegate.feedDetailViewController.pageFetching) {
[appDelegate.feedDetailViewController fetchNextPage:^() {
// NSLog(@"Fetched next page, %d stories", [appDelegate.activeFeedStoryLocations count]);
[self applyNewIndex:newIndex pageController:pageController];
}];
} else if (!self.appDelegate.feedDetailViewController.pageFinished &&
!self.appDelegate.feedDetailViewController.pageFetching) {
} else if (!appDelegate.feedDetailViewController.pageFinished &&
!appDelegate.feedDetailViewController.pageFetching) {
[appDelegate.navigationController
popToViewController:[appDelegate.navigationController.viewControllers
objectAtIndex:0]
@ -981,7 +979,7 @@
buttonNext.enabled = YES;
NSInteger nextIndex = [appDelegate.storiesCollection indexOfNextUnreadStory];
NSInteger unreadCount = [appDelegate unreadCount];
BOOL pageFinished = self.appDelegate.feedDetailViewController.pageFinished;
BOOL pageFinished = appDelegate.feedDetailViewController.pageFinished;
if ((nextIndex == -1 && unreadCount > 0 && !pageFinished) ||
nextIndex != -1) {
[buttonNext setTitle:[@"Next" uppercaseString] forState:UIControlStateNormal];
@ -1043,18 +1041,14 @@
[appDelegate openTrainStory:self.fontSettingsButton];
}
- (void)finishMarkAsSaved:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self requestFailed:request];
}
- (void)finishMarkAsSaved:(NSDictionary *)params {
[appDelegate.feedDetailViewController redrawUnreadStory];
[self refreshHeaders];
[self.currentPage flashCheckmarkHud:@"saved"];
}
- (BOOL)failedMarkAsSaved:(ASIFormDataRequest *)request {
if (![[request.userInfo objectForKey:@"story_hash"]
- (BOOL)failedMarkAsSaved:(NSDictionary *)params {
if (![[params objectForKey:@"story_hash"]
isEqualToString:[currentPage.activeStory objectForKey:@"story_hash"]]) {
return NO;
}
@ -1063,20 +1057,16 @@
return YES;
}
- (void)finishMarkAsUnsaved:(ASIFormDataRequest *)request {
if ([request responseStatusCode] != 200) {
return [self requestFailed:request];
}
[appDelegate.storiesCollection markStory:[request.userInfo objectForKey:@"story"] asSaved:NO];
- (void)finishMarkAsUnsaved:(NSDictionary *)params {
[appDelegate.storiesCollection markStory:[params objectForKey:@"story"] asSaved:NO];
[appDelegate.feedDetailViewController redrawUnreadStory];
[self refreshHeaders];
[self.currentPage flashCheckmarkHud:@"unsaved"];
}
- (BOOL)failedMarkAsUnsaved:(ASIFormDataRequest *)request {
if (![[request.userInfo objectForKey:@"story_hash"]
- (BOOL)failedMarkAsUnsaved:(NSDictionary *)params {
if (![[params objectForKey:@"story_hash"]
isEqualToString:[currentPage.activeStory objectForKey:@"story_hash"]]) {
return NO;
}
@ -1085,8 +1075,8 @@
return YES;
}
- (BOOL)failedMarkAsUnread:(ASIFormDataRequest *)request {
if (![[request.userInfo objectForKey:@"story_hash"]
- (BOOL)failedMarkAsUnread:(NSDictionary *)params {
if (![[params objectForKey:@"story_hash"]
isEqualToString:[currentPage.activeStory objectForKey:@"story_hash"]]) {
return NO;
}
@ -1163,10 +1153,10 @@
- (IBAction)toggleFontSize:(id)sender {
UINavigationController *fontSettingsNavigationController = self.appDelegate.fontSettingsNavigationController;
UINavigationController *fontSettingsNavigationController = appDelegate.fontSettingsNavigationController;
[fontSettingsNavigationController popToRootViewControllerAnimated:NO];
[self.appDelegate showPopoverWithViewController:fontSettingsNavigationController contentSize:CGSizeZero barButtonItem:self.fontSettingsButton];
[appDelegate showPopoverWithViewController:fontSettingsNavigationController contentSize:CGSizeZero barButtonItem:self.fontSettingsButton];
}
- (void)setFontStyle:(NSString *)fontStyle {
@ -1230,10 +1220,10 @@
#pragma mark Story Traversal
- (IBAction)doNextUnreadStory:(id)sender {
FeedDetailViewController *fdvc = self.appDelegate.feedDetailViewController;
FeedDetailViewController *fdvc = appDelegate.feedDetailViewController;
NSInteger nextLocation = [appDelegate.storiesCollection locationOfNextUnreadStory];
NSInteger unreadCount = [appDelegate unreadCount];
BOOL pageFinished = self.appDelegate.feedDetailViewController.pageFinished;
BOOL pageFinished = appDelegate.feedDetailViewController.pageFinished;
[self.loadingIndicator stopAnimating];

View file

@ -203,7 +203,7 @@ NSString * const ThemeStyleDark = @"dark";
colors = [NSMutableSet set];
}
[colors addObject:[NSString stringWithFormat:@"0x%06lX", rgbValue]];
[colors addObject:[NSString stringWithFormat:@"0x%06lX", (long)rgbValue]];
NSLog(@"all unique colors: %@", [[colors allObjects] sortedArrayUsingSelector:@selector(compare:)]); // log
}

View file

@ -53,4 +53,4 @@
- (IBAction)doCloseDialog:(id)sender;
@end
@end

View file

@ -80,37 +80,29 @@
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
HUD.labelText = @"Loading trainer...";
NSString *feedId = [self feedId];
NSURL *url = [NSURL URLWithString:[NSString
stringWithFormat:@"%@/reader/feeds_trainer?feed_id=%@",
self.appDelegate.url, feedId]];
__weak __typeof(&*self)weakSelf = self;
AFHTTPRequestOperation *request = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
[request setResponseSerializer:[AFJSONResponseSerializer serializer]];
[request setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
if (!strongSelf) return;
[MBProgressHUD hideHUDForView:strongSelf.view animated:YES];
NSDictionary *results = [responseObject objectAtIndex:0];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/feeds_trainer?feed_id=%@",
self.appDelegate.url, feedId];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSDictionary *results = responseObject;
NSMutableDictionary *newClassifiers = [[results objectForKey:@"classifiers"] mutableCopy];
[appDelegate.storiesCollection.activeClassifiers setObject:newClassifiers
forKey:feedId];
appDelegate.storiesCollection.activePopularAuthors = [results objectForKey:@"feed_authors"];
appDelegate.storiesCollection.activePopularTags = [results objectForKey:@"feed_tags"];
[self renderTrainer];
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed fetch trainer: %@", error);
[self informError:@"Could not load trainer"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^() {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate hidePopover];
} else {
[appDelegate.navigationController dismissViewControllerAnimated:YES completion:nil];
}
});
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[appDelegate hidePopover];
} else {
[appDelegate.navigationController dismissViewControllerAnimated:YES completion:nil];
}
});
}];
[request start];
} else {
[self renderTrainer];
}

View file

@ -21,17 +21,17 @@ typedef enum {
} NBFeedListType;
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
@property (assign, nonatomic) int psWidth;
@property (assign, nonatomic) int psPadding;
@property (assign, nonatomic) int ntWidth;
@property (assign, nonatomic) int ntPadding;
@property (assign, nonatomic) int psCount;
@property (assign, nonatomic) int ntCount;
@property (assign, nonatomic) int blueCount;
@property (assign, nonatomic) NSInteger psWidth;
@property (assign, nonatomic) NSInteger psPadding;
@property (assign, nonatomic) NSInteger ntWidth;
@property (assign, nonatomic) NSInteger ntPadding;
@property (assign, nonatomic) NSInteger psCount;
@property (assign, nonatomic) NSInteger ntCount;
@property (assign, nonatomic) NSInteger blueCount;
@property (assign, nonatomic) CGRect rect;
- (void)drawInRect:(CGRect)r ps:(int)ps nt:(int)nt listType:(NBFeedListType)listType;
- (void)calculateOffsets:(int)ps nt:(int)nt;
- (int)offsetWidth;
- (void)drawInRect:(CGRect)r ps:(NSInteger)ps nt:(NSInteger)nt listType:(NBFeedListType)listType;
- (void)calculateOffsets:(NSInteger)ps nt:(NSInteger)nt;
- (NSInteger)offsetWidth;
@end

View file

@ -32,7 +32,7 @@ const int COUNT_HEIGHT = 15;
return [self drawInRect:r ps:psCount nt:ntCount listType:NBFeedListFolder];
}
- (void)drawInRect:(CGRect)r ps:(int)ps nt:(int)nt listType:(NBFeedListType)listType {
- (void)drawInRect:(CGRect)r ps:(NSInteger)ps nt:(NSInteger)nt listType:(NBFeedListType)listType {
rect = CGRectInset(r, 12, 12);
rect.size.width -= 18; // Scrollbar padding
@ -47,8 +47,8 @@ const int COUNT_HEIGHT = 15;
}
[self calculateOffsets:ps nt:nt];
int psOffset = ps == 0 ? 0 : psWidth - 20;
int ntOffset = nt == 0 ? 0 : ntWidth - 20;
NSInteger psOffset = ps == 0 ? 0 : psWidth - 20;
NSInteger ntOffset = nt == 0 ? 0 : ntWidth - 20;
if (ps > 0 || blueCount) {
CGRect rr;
@ -81,7 +81,7 @@ const int COUNT_HEIGHT = 15;
[UIView drawRoundRectangleInRect:rr withRadius:4];
NSString *psStr = [NSString stringWithFormat:@"%d", ps];
NSString *psStr = [NSString stringWithFormat:@"%ld", (long)ps];
CGSize size = [psStr sizeWithAttributes:@{NSFontAttributeName: indicatorFont}];
float x_pos = (rr.size.width - size.width) / 2;
float y_pos = (rr.size.height - size.height) / 2;
@ -125,7 +125,7 @@ const int COUNT_HEIGHT = 15;
[UIColorFromLightDarkRGB(0xB3B6AD, 0xA3A69D) set];
[UIView drawRoundRectangleInRect:rr withRadius:4];
NSString *ntStr = [NSString stringWithFormat:@"%d", nt];
NSString *ntStr = [NSString stringWithFormat:@"%ld", (long)nt];
CGSize size = [ntStr sizeWithAttributes:@{NSFontAttributeName: indicatorFont}];
float x_pos = (rr.size.width - size.width) / 2;
float y_pos = (rr.size.height - size.height) / 2;
@ -142,7 +142,7 @@ const int COUNT_HEIGHT = 15;
}
}
- (void)calculateOffsets:(int)ps nt:(int)nt {
- (void)calculateOffsets:(NSInteger)ps nt:(NSInteger)nt {
psWidth = ps == 0 ? 0 : ps < 10 ? 14 : ps < 100 ? 22 : ps < 1000 ? 28 : ps < 10000 ? 34 : 40;
ntWidth = nt == 0 ? 0 : nt < 10 ? 14 : nt < 100 ? 22 : nt < 1000 ? 28 : nt < 10000 ? 34 : 40;
@ -150,8 +150,8 @@ const int COUNT_HEIGHT = 15;
ntPadding = nt == 0 ? 0 : 2;
}
- (int)offsetWidth {
int width = 0;
- (NSInteger)offsetWidth {
NSInteger width = 0;
if (self.psCount > 0) {
width += psWidth + psPadding;
}

View file

@ -7,13 +7,13 @@
//
#import <UIKit/UIKit.h>
#import "ASIHTTPRequest.h"
#import "NewsBlurAppDelegate.h"
@class NewsBlurAppDelegate;
@class ProfileBadge;
@interface UserProfileViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate, ASIHTTPRequestDelegate> {
@interface UserProfileViewController : BaseViewController
<UITableViewDataSource, UITableViewDelegate> {
NewsBlurAppDelegate *appDelegate;
UILabel *followingCount;
@ -31,11 +31,8 @@
@property (nonatomic) NSArray *activitiesArray;
@property (nonatomic) NSString *activitiesUsername;
@property (nonatomic) NSDictionary *userProfile;
@property (nonatomic) ASIHTTPRequest *request;
- (void)getUserProfile;
- (void)requestFinished:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
- (void)doCancelButton;
@end

View file

@ -11,7 +11,6 @@
#import "ProfileBadge.h"
#import "SmallActivityCell.h"
#import "FollowGrid.h"
#import "ASIHTTPRequest.h"
#import "Utilities.h"
#import "MBProgressHUD.h"
#import <QuartzCore/QuartzCore.h>
@ -24,7 +23,6 @@
@synthesize activitiesArray;
@synthesize activitiesUsername;
@synthesize userProfile;
@synthesize request;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
@ -38,8 +36,6 @@
- (void)dealloc {
self.profileTable.dataSource = nil;
self.profileTable.delegate = nil;
request.delegate = nil;
[request cancel];
}
- (void)viewDidLoad {
@ -114,27 +110,17 @@
"&category=follow&category=comment_reply&category=comment_like&category=sharedstory",
self.appDelegate.url,
appDelegate.activeUserProfileId];
NSURL *url = [NSURL URLWithString:urlString];
request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestFinished:)];
[request setDidFailSelector:@selector(requestFailed:)];
[request startAsynchronous];
[appDelegate.networkManager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self requestFinished:responseObject];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[self informError:error];
}];
}
- (void)requestFinished:(ASIHTTPRequest *)_request {
- (void)requestFinished:(NSDictionary *)results {
[MBProgressHUD hideHUDForView:self.view animated:YES];
NSString *responseString = [_request responseString];
NSData *responseData=[responseString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *results = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
// int statusCode = [request responseStatusCode];
int code = [[results valueForKey:@"code"] intValue];
if (code == -1) {
NSLog(@"ERROR");
@ -161,12 +147,6 @@
[self.view addSubview:self.profileTable];
}
- (void)requestFailed:(ASIHTTPRequest *)_request {
NSError *error = [_request error];
NSLog(@"Error: %@", error);
[appDelegate informError:error];
}
#pragma mark -
#pragma mark Table View - Profile Modules List

View file

@ -9,17 +9,12 @@
#import <Foundation/Foundation.h>
#import "NewsBlurAppDelegate.h"
#import "FMDatabaseQueue.h"
#import "ASINetworkQueue.h"
@interface OfflineFetchImages : NSOperation
@property (nonatomic) NewsBlurAppDelegate *appDelegate;
@property (readwrite) ASINetworkQueue *imageDownloadOperationQueue;
- (BOOL)fetchImages;
- (NSArray *)uncachedImageUrls;
- (void)storeCachedImage:(ASIHTTPRequest *)request;
- (void)storeFailedImage:(ASIHTTPRequest *)request;
- (void)cachedImageQueueFinished:(ASINetworkQueue *)queue;
@end

View file

@ -14,7 +14,6 @@
#import "Utilities.h"
@implementation OfflineFetchImages
@synthesize imageDownloadOperationQueue;
@synthesize appDelegate;
- (void)main {
@ -29,27 +28,18 @@
- (BOOL)fetchImages {
if (self.isCancelled) {
NSLog(@"Images cancelled.");
[imageDownloadOperationQueue cancelAllOperations];
return NO;
}
// NSLog(@"Fetching images...");
NSLog(@"Fetching images...");
NSArray *urls = [self uncachedImageUrls];
if (imageDownloadOperationQueue) {
[imageDownloadOperationQueue cancelAllOperations];
imageDownloadOperationQueue = nil;
}
imageDownloadOperationQueue = [[ASINetworkQueue alloc] init];
imageDownloadOperationQueue.maxConcurrentOperationCount = 8;
imageDownloadOperationQueue.delegate = self;
if (![[[NSUserDefaults standardUserDefaults]
objectForKey:@"offline_image_download"] boolValue] ||
![[[NSUserDefaults standardUserDefaults]
objectForKey:@"offline_allowed"] boolValue] ||
[urls count] == 0) {
NSLog(@"Finished caching images. %d total", appDelegate.totalUncachedImagesCount);
NSLog(@"Finished caching images. %ld total", (long)appDelegate.totalUncachedImagesCount);
dispatch_async(dispatch_get_main_queue(), ^{
[appDelegate.feedsViewController showDoneNotifier];
[appDelegate.feedsViewController hideNotifier];
@ -68,31 +58,38 @@
}
NSMutableArray *downloadRequests = [NSMutableArray array];
for (NSArray *urlArray in urls) {
NSURL *url = [NSURL URLWithString:[urlArray objectAtIndex:0]];
NSString *storyHash = [urlArray objectAtIndex:1];
NSString *storyTimestamp = [urlArray objectAtIndex:2];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setValidatesSecureCertificate:NO];
[request setUserInfo:@{@"story_hash": storyHash, @"story_timestamp": storyTimestamp}];
[request setDelegate:self];
[request setDidFinishSelector:@selector(storeCachedImage:)];
[request setDidFailSelector:@selector(storeFailedImage:)];
[request setTimeOutSeconds:5];
[downloadRequests addObject:request];
}
[imageDownloadOperationQueue setQueueDidFinishSelector:@selector(cachedImageQueueFinished:)];
[imageDownloadOperationQueue setShouldCancelAllRequestsOnFailure:NO];
[imageDownloadOperationQueue go];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[imageDownloadOperationQueue addOperations:downloadRequests waitUntilFinished:YES];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
[manager.requestSerializer setTimeoutInterval:5];
manager.responseSerializer = [AFImageResponseSerializer serializer];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_group_t group = dispatch_group_create();
for (ASIHTTPRequest *request in downloadRequests) {
[request setRawResponseData:nil];
for (NSArray *urlArray in urls) {
NSString *urlString = [[urlArray objectAtIndex:0] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *storyHash = [urlArray objectAtIndex:1];
NSInteger storyTimestamp = [[urlArray objectAtIndex:2] integerValue];
dispatch_group_enter(group);
// NSLog(@" ---> Fetching offline image: %@", urlString);
[manager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
// NSLog(@" ---> Fetched %@: %@", storyHash, urlString);
UIImage *image = (UIImage *)responseObject;
[self storeCachedImage:urlString withImage:image storyHash:storyHash storyTimestamp:storyTimestamp];
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
// NSLog(@" ---> Failed to fetch image %@: %@", storyHash, urlString);
[self storeFailedImage:storyHash];
dispatch_group_leave(group);
}];
}
[imageDownloadOperationQueue reset];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"Queue finished: %ld total (%ld remaining)", (long)appDelegate.totalUncachedImagesCount, (long)appDelegate.remainingUncachedImagesCount);
[self updateProgress];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// dispatch_async(dispatch_get_main_queue(), ^{
// [appDelegate.feedsViewController hideNotifier];
// });
@ -131,7 +128,6 @@
}
[cursor close];
[self updateProgress];
}];
return urls;
@ -140,9 +136,9 @@
- (void)updateProgress {
if (self.isCancelled) return;
int start = (int)[[NSDate date] timeIntervalSince1970];
int end = appDelegate.latestCachedImageDate;
int seconds = start - (end ? end : start);
NSInteger start = (NSInteger)[[NSDate date] timeIntervalSince1970];
NSInteger end = appDelegate.latestCachedImageDate;
NSInteger seconds = start - (end ? end : start);
__block int hours = (int)round(seconds / 60.f / 60.f);
__block float progress = 0.f;
@ -155,33 +151,25 @@
});
}
- (void)storeCachedImage:(ASIHTTPRequest *)request {
- (void)storeCachedImage:(NSString *)imageUrl withImage:(UIImage *)image storyHash:(NSString *)storyHash storyTimestamp:(NSInteger)storyTimestamp {
if (self.isCancelled) {
NSLog(@"Image cancelled.");
[request clearDelegatesAndCancel];
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,
(unsigned long)NULL), ^{
NSString *storyHash = [[request userInfo] objectForKey:@"story_hash"];
int storyTimestamp = [[[request userInfo] objectForKey:@"story_timestamp"] intValue];
if ([request responseStatusCode] == 200) {
NSData *responseData = [request responseData];
NSString *md5Url = [Utilities md5:[[request originalURL] absoluteString]];
NSData *responseData = UIImageJPEGRepresentation(image, 0.6);
NSString *md5Url = [Utilities md5:imageUrl];
// NSLog(@"Storing image: %@ (%d bytes - %d in queue)", storyHash, [responseData length], [imageDownloadOperationQueue requestsCount]);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"story_images"];
NSString *fullPath = [cacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", md5Url, [[[request originalURL] absoluteString] pathExtension]]];
[fileManager createFileAtPath:fullPath contents:responseData attributes:nil];
} else {
NSLog(@"Failed to fetch: %@ / %@", [[request originalURL] absoluteString], storyHash);
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"story_images"];
NSString *fullPath = [cacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", md5Url, [imageUrl pathExtension]]];
[fileManager createFileAtPath:fullPath contents:responseData attributes:nil];
[appDelegate.database inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"UPDATE cached_images SET "
@ -199,16 +187,16 @@
}
}
appDelegate.remainingUncachedImagesCount--;
if (appDelegate.remainingUncachedImagesCount % 10 == 0) {
[self updateProgress];
@synchronized (self) {
appDelegate.remainingUncachedImagesCount--;
if (appDelegate.remainingUncachedImagesCount % 10 == 0) {
[self updateProgress];
}
}
});
}
- (void)storeFailedImage:(ASIHTTPRequest *)request {
NSString *storyHash = [[request userInfo] objectForKey:@"story_hash"];
- (void)storeFailedImage:(NSString *)storyHash {
[appDelegate.database inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"UPDATE cached_images SET "
"image_cached = 1, failed = 1 WHERE story_hash = ?",
@ -216,13 +204,4 @@
}];
}
- (void)cachedImageQueueFinished:(ASINetworkQueue *)queue {
NSLog(@"Queue finished: %d total (%d remaining)", appDelegate.totalUncachedImagesCount, appDelegate.remainingUncachedImagesCount);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self fetchImages];
// dispatch_async(dispatch_get_main_queue(), ^{
// [self.feedsViewController hideNotifier];
// });
}
@end

View file

@ -7,11 +7,10 @@
//
#import <Foundation/Foundation.h>
#import "AFHTTPRequestOperation.h"
#import "NewsBlurAppDelegate.h"
@interface OfflineFetchStories : NSOperation {
AFHTTPRequestOperation *request;
}
@property (nonatomic) NewsBlurAppDelegate *appDelegate;

View file

@ -11,7 +11,6 @@
#import "NewsBlurViewController.h"
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"
#import "AFHTTPRequestOperation.h"
#import "SBJson4.h"
#import "NSObject+SBJSON.h"
@ -66,31 +65,23 @@
}
__block NSCondition *lock = [NSCondition new];
__weak __typeof(&*self)weakSelf = self;
[lock lock];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/reader/river_stories?include_hidden=true&page=0&h=%@",
self.appDelegate.url, [hashes componentsJoinedByString:@"&h="]]];
if (request) request = nil;
request = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
[request setResponseSerializer:[AFJSONResponseSerializer serializer]];
[request setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
__strong __typeof(&*weakSelf)strongSelf = weakSelf;
if (!strongSelf) return;
[strongSelf storeAllUnreadStories:responseObject withHashes:hashes];
NSString *urlString = [NSString stringWithFormat:@"%@/reader/river_stories?include_hidden=true&page=0&h=%@",
self.appDelegate.url, [hashes componentsJoinedByString:@"&h="]];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.completionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
[manager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[self storeAllUnreadStories:responseObject withHashes:hashes];
[lock signal];
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"Failed fetch all unreads: %@", error);
[lock signal];
}];
[request setCompletionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, (unsigned long)NULL)];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[request start];
[request waitUntilFinished];
[request.outputStream close];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[lock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:30]];
[lock unlock];
@ -139,10 +130,10 @@
- (void)updateProgress {
if (self.isCancelled) return;
int start = (int)[[NSDate date] timeIntervalSince1970];
int end = appDelegate.latestFetchedStoryDate;
int seconds = start - (end ? end : start);
__block int hours = (int)round(seconds / 60.f / 60.f);
NSInteger start = (int)[[NSDate date] timeIntervalSince1970];
NSInteger end = appDelegate.latestFetchedStoryDate;
NSInteger seconds = start - (end ? end : start);
__block NSInteger hours = (int)round(seconds / 60.f / 60.f);
__block float progress = 0.f;
if (appDelegate.totalUnfetchedStoryCount) {

Some files were not shown because too many files have changed in this diff Show more