text:
"""View and edit HTTP Archives.

To list all URLs in an archive:
  $ ./httparchive.py ls archive.wpr

To view the content of all URLs from example.com:
  $ ./httparchive.py cat --host example.com archive.wpr

To view the content of a particular URL:
  $ ./httparchive.py cat --host www.example.com --full_path /foo archive.wpr

To view the content of all URLs:
  $ ./httparchive.py cat archive.wpr

To edit a particular URL:
  $ ./httparchive.py edit --host www.example.com --full_path /foo archive.wpr

To print statistics of an archive:
  $ ./httparchive.py stats archive.wpr

To print statistics of a set of URLs:
  $ ./httparchive.py stats --host www.example.com archive.wpr

To merge multiple archives
  $ ./httparchive.py merge --merged_file new.wpr archive1.wpr archive2.wpr ...
"""

import calendar
import certutils
import difflib
import email.utils
import httplib
import httpzlib
import json
import logging
import optparse
import os
import persistentmixin
import StringIO
import subprocess
import sys
import tempfile
import time
import urlparse
from collections import defaultdict

def LogRunTime(fn):
  """Annotation which logs the run time of the function."""
  def wrapped(self, *args, **kwargs):
    start_time = time.time()
    try:
      return fn(self, *args, **kwargs)
    finally:
      run_time = (time.time() - start_time) * 1000.0;
      logging.debug('%s: %dms', fn.__name__, run_time)
  return wrapped

class HttpArchiveException(Exception):
  """Base class for all exceptions in httparchive."""
  pass

class HttpArchive(dict, persistentmixin.PersistentMixin):
  """Dict with ArchivedHttpRequest keys and ArchivedHttpResponse values.

  PersistentMixin adds the following methods:
    AssertWritable(filename)
    Load(filename)
    Persist(filename)

  Attributes:
    responses_by_host: dict of {hostname, {request: response}}. This must remain
        in sync with the underlying dict of self. It is used as an optimization
        so that get_requests() doesn't have to linearly search all requests in
        the archive to find potential matches.
  """

  def __init__(self):
    self.responses_by_host = defaultdict(dict)

  def __setstate__(self, state):
    """Influence how to unpickle.

    Args:
      state: a dictionary for __dict__
    """
    self.__dict__.update(state)
    self.responses_by_host = defaultdict(dict)
    for request in self:
      self.responses_by_host[request.host][request] = self[request]

  def __getstate__(self):
    """Influence how to pickle.

    Returns:
      a dict to use for pickling
    """
    state = self.__dict__.copy()
    del state['responses_by_host']
    return state

  def __setitem__(self, key, value):
    super(HttpArchive, self).__setitem__(key, value)
    if hasattr(self, 'responses_by_host'):
      self.responses_by_host[key.host][key] = value

  def __delitem__(self, key):
    super(HttpArchive, self).__delitem__(key)
    del self.responses_by_host[key.host][key]

  def get(self, request, default=None):
    """Return the archived response for a given request.

    Does extra checking for handling some HTTP request headers.

    Args:
      request: instance of ArchivedHttpRequest
      default: default value to return if request is not found

    Returns:
      Instance of ArchivedHttpResponse or default if no matching
      response is found
    """
    if request in self:
      return self[request]
    return self.get_conditional_response(request, default)

  def get_conditional_response(self, request, default):
    """Get the response based on the conditional HTTP request headers.

    Args:
      request: an ArchivedHttpRequest representing the original request.
      default: default ArchivedHttpResponse
          original request with matched headers removed.

    Returns:
      an ArchivedHttpResponse with a status of 200, 302 (not modified), or
          412 (precondition failed)
    """
    response = default
    if request.is_conditional():
      stripped_request = request.create_request_without_conditions()
      if stripped_request in self:
        response = self[stripped_request]
        if response.status == 200:
          status = self.get_conditional_status(request, response)
          if status != 200:
            response = create_response(status)
    return response

  def get_conditional_status(self, request, response):
    status = 200
    last_modified = email.utils.parsedate(
        response.update_date(response.get_header('last-modified')))
    response_etag = response.get_header('etag')
    is_get_or_head = request.command.upper() in ('GET', 'HEAD')

    match_value = request.headers.get('if-match', None)
    if match_value:
      if self.is_etag_match(match_value, response_etag):
        status = 200
      else:
        status = 412  # precondition failed
    none_match_value = request.headers.get('if-none-match', None)
    if none_match_value:
      if self.is_etag_match(none_match_value, response_etag):
        status = 304
      elif is_get_or_head:
        status = 200
      else:
        status = 412
    if is_get_or_head and last_modified:
      for header in ('if-modified-since', 'if-unmodified-since'):
        date = email.utils.parsedate(request.headers.get(header, None))
        if date:
          if ((header == 'if-modified-since' and last_modified > date) or
              (header == 'if-unmodified-since' and last_modified < date)):
            if status != 412:
              status = 200
          else:
            status = 304  # not modified
    return status

  def is_etag_match(self, request_etag, response_etag):
    """Determines whether the entity tags of the request/response matches.

    Args:
      request_etag: the value string of the "if-(none)-match:"
                    portion of the request header
      response_etag: the etag value of the response

    Returns:
      True on match, False otherwise
    """
    response_etag = response_etag.strip('" ')
    for etag in request_etag.split(','):
      etag = etag.strip('" ')
      if etag in ('*', response_etag):
        return True
    return False

  def get_requests(self, command=None, host=None, full_path=None, is_ssl=None,
                   use_query=True):
    """Return a list of requests that match the given args."""
    if host:
      return [r for r in self.responses_by_host[host]
              if r.matches(command, None, full_path, is_ssl,
                           use_query=use_query)]
    else:
      return [r for r in self
              if r.matches(command, host, full_path, is_ssl,
                           use_query=use_query)]

  def ls(self, command=None, host=None, full_path=None):
    """List all URLs that match given params."""
    return ''.join(sorted(
        '%s\n' % r for r in self.get_requests(command, host, full_path)))

  def cat(self, command=None, host=None, full_path=None):
    """Print the contents of all URLs that match given params."""
    out = StringIO.StringIO()
    for request in self.get_requests(command, host, full_path):
      print >>out, str(request)
      print >>out, 'Untrimmed request headers:'
      for k in request.headers:
        print >>out, '    %s: %s' % (k, request.headers[k])
      if request.request_body:
        print >>out, request.request_body
      print >>out, '---- Response Info', '-' * 51
      response = self[request]
      chunk_lengths = [len(x) for x in response.response_data]
      print >>out, ('Status: %s\n'
                    'Reason: %s\n'
                    'Headers delay: %s\n'
                    'Response headers:') % (
          response.status, response.reason, response.delays['headers'])
      for k, v in response.headers:
        print >>out, '    %s: %s' % (k, v)
      print >>out, ('Chunk count: %s\n'
                    'Chunk lengths: %s\n'
                    'Chunk delays: %s') % (
          len(chunk_lengths), chunk_lengths, response.delays['data'])
      body = response.get_data_as_text()
      print >>out, '---- Response Data', '-' * 51
      if body:
        print >>out, body
      else:
        print >>out, '[binary data]'
      print >>out, '=' * 70
    return out.getvalue()

  def stats(self, command=None, host=None, full_path=None):
    """Print stats about the archive for all URLs that match given params."""
    matching_requests = self.get_requests(command, host, full_path)
    if not matching_requests:
      print 'Failed to find any requests matching given command, host, path.'
      return

    out = StringIO.StringIO()
    stats = {}
    stats['Total'] = len(matching_requests)
    stats['Domains'] = defaultdict(int)
    stats['HTTP_response_code'] = defaultdict(int)
    stats['content_type'] = defaultdict(int)
    stats['Documents'] = defaultdict(int)

    for request in matching_requests:
      stats['Domains'][request.host] += 1
      stats['HTTP_response_code'][self[request].status] += 1

      content_type = self[request].get_header('content-type')
      # Remove content type options for readability and higher level groupings.
      str_content_type = str(content_type.split(';')[0]
                            if content_type else None)
      stats['content_type'][str_content_type] += 1

      #  Documents are the main URL requested and not a referenced resource.
      if str_content_type == 'text/html' and not 'referer' in request.headers:
        stats['Documents'][request.host] += 1

    print >>out, json.dumps(stats, indent=4)
    return out.getvalue()

  def merge(self, merged_archive=None, other_archives=None):
    """Merge multiple archives into merged_archive by 'chaining' resources,
    only resources that are not part of the accumlated archive are added"""
    if not other_archives:
      print 'No archives passed to merge'
      return

    # Note we already loaded 'replay_file'.
    print 'Loaded %d responses' % len(self)

    for archive in other_archives:
      if not os.path.exists(archive):
        print 'Error: Replay file "%s" does not exist' % archive
        return

      http_archive_other = HttpArchive.Load(archive)
      print 'Loaded %d responses from %s' % (len(http_archive_other), archive)
      for r in http_archive_other:
        # Only resources that are not already part of the current archive
        # get added.
        if r not in self:
          print '\t %s ' % r
          self[r] = http_archive_other[r]
    self.Persist('%s' % merged_archive)

  def edit(self, command=None, host=None, full_path=None):
    """Edits the single request which matches given params."""
    editor = os.getenv('EDITOR')
    if not editor:
      print 'You must set the EDITOR environmental variable.'
      return

    matching_requests = self.get_requests(command, host, full_path)
    if not matching_requests:
      print ('Failed to find any requests matching given command, host, '
             'full_path.')
      return

    if len(matching_requests) > 1:
      print 'Found multiple matching requests. Please refine.'
      print self.ls(command, host, full_path)

    response = self[matching_requests[0]]
    tmp_file = tempfile.NamedTemporaryFile(delete=False)
    tmp_file.write(response.get_response_as_text())
    tmp_file.close()
    subprocess.check_call([editor, tmp_file.name])
    response.set_response_from_text(''.join(open(tmp_file.name).readlines()))
    os.remove(tmp_file.name)

  def find_closest_request(self, request, use_path=False):
    """Find the closest matching request in the archive to the given request.

    Args:
      request: an ArchivedHttpRequest
      use_path: If True, closest matching request's path component must match.
        (Note: this refers to the 'path' component within the URL, not the
         'full path' which includes the query string component.)
        If use_path=True, candidate will NOT match in example below
        e.g. request   = GET www.test.com/path?aaa
             candidate = GET www.test.com/diffpath?aaa
    Returns:
      If a close match is found, return the instance of ArchivedHttpRequest.
      Otherwise, return None.
    """
    full_path = request.full_path if use_path else None
    requests = self.get_requests(request.command, request.host, full_path,
                                 is_ssl=request.is_ssl, use_query=not use_path)

    if not requests:
      return None

    if len(requests) == 1:
      return requests[0]

    matcher = difflib.SequenceMatcher(b=request.formatted_request)

    # quick_ratio() is cheap to compute, but ratio() is expensive. So we call
    # quick_ratio() on all requests, sort them descending, and then loop through
    # until we find a candidate whose ratio() is >= the next quick_ratio().
    # This works because quick_ratio() is guaranteed to be an upper bound on
    # ratio().
    candidates = []
    for candidate in requests:
      matcher.set_seq1(candidate.formatted_request)
      candidates.append((matcher.quick_ratio(), candidate))

    candidates.sort(reverse=True, key=lambda c: c[0])

    best_match = (0, None)
    for i in xrange(len(candidates)):
      matcher.set_seq1(candidates[i][1].formatted_request)
      best_match = max(best_match, (matcher.ratio(), candidates[i][1]))
      if i + 1 < len(candidates) and best_match[0] >= candidates[i+1][0]:
        break
    return best_match[1]

  def diff(self, request):
    """Diff the given request to the closest matching request in the archive.

    Args:
      request: an ArchivedHttpRequest
    Returns:
      If a close match is found, return a textual diff between the requests.
      Otherwise, return None.
    """
    request_lines = request.formatted_request.split('\n')
    closest_request = self.find_closest_request(request)
    if closest_request:
      closest_request_lines = closest_request.formatted_request.split('\n')
      return '\n'.join(difflib.ndiff(closest_request_lines, request_lines))
    return None

  def set_root_cert(self, cert_path):
    with open(cert_path, 'r') as cert_file:
      cert_str = cert_file.read()
    cert_str_response = create_response(200, body=cert_str)
    root_request = ArchivedHttpRequest('ROOT_CERT', '', '', None, {})
    self[root_request] = cert_str_response

  def _get_server_cert(self, host):
    """Gets certificate from the server and stores it in archive"""
    request = ArchivedHttpRequest('SERVER_CERT', host, '', None, {})
    if request not in self:
      self[request] = create_response(200, body=certutils.get_host_cert(host))
    return self[request].response_data[0]

  def _get_root_cert(self):
    request = ArchivedHttpRequest('ROOT_CERT', '', '', None, {})
    if request not in self:
      raise KeyError('Root cert is not in the archive')
    return self[request].response_data[0]

  def _generate_cert(self, host):
    """Generate cert with the SNI field from the real server's response."""
    root_ca_cert_str = self._get_root_cert()
    return certutils.generate_cert(
        root_ca_cert_str, self._get_server_cert(host), host)

  def get_certificate(self, host):
    request = ArchivedHttpRequest('DUMMY_CERT', host, '', None, {})
    if request not in self:
      self[request] = create_response(200, body=self._generate_cert(host))
    return self[request].response_data[0]

class ArchivedHttpRequest(object):
  """Record all the state that goes into a request.

  ArchivedHttpRequest instances are considered immutable so they can
  serve as keys for HttpArchive instances.
  (The immutability is not enforced.)

  Upon creation, the headers are "trimmed" (i.e. edited or dropped)
  and saved to self.trimmed_headers to allow requests to match in a wider
  variety of playback situations (e.g. using different user agents).

  For unpickling, 'trimmed_headers' is recreated from 'headers'. That
  allows for changes to the trim function and can help with debugging.
  """
  CONDITIONAL_HEADERS = [
      'if-none-match', 'if-match',
      'if-modified-since', 'if-unmodified-since']

  def __init__(self, command, host, full_path, request_body, headers,
               is_ssl=False):
    """Initialize an ArchivedHttpRequest.

    Args:
      command: a string (e.g. 'GET' or 'POST').
      host: a host name (e.g. 'www.google.com').
      full_path: a request path.  Includes everything after the host & port in
          the URL (e.g. '/search?q=dogs').
      request_body: a request body string for a POST or None.
      headers: {key: value, ...} where key and value are strings.
      is_ssl: a boolean which is True iff request is make via SSL.
    """
    self.command = command
    self.host = host
    self.full_path = full_path
    self.path = urlparse.urlparse(full_path).path if full_path else None
    self.request_body = request_body
    self.headers = headers
    self.is_ssl = is_ssl
    self.trimmed_headers = self._TrimHeaders(headers)
    self.formatted_request = self._GetFormattedRequest()

  def __str__(self):
    scheme = 'https' if self.is_ssl else 'http'
    return '%s %s://%s%s %s' % (
        self.command, scheme, self.host, self.full_path, self.trimmed_headers)

  def __repr__(self):
    return repr((self.command, self.host, self.full_path, self.request_body,
                 self.trimmed_headers, self.is_ssl))

  def __hash__(self):
    """Return a integer hash to use for hashed collections including dict."""
    return hash(repr(self))

  def __eq__(self, other):
    """Define the __eq__ method to match the hash behavior."""
    return repr(self) == repr(other)

  def __setstate__(self, state):
    """Influence how to unpickle.

    "headers" are the original request headers.
    "trimmed_headers" are the trimmed headers used for matching requests
    during replay.

    Args:
      state: a dictionary for __dict__
    """
    if 'full_headers' in state:
      # Fix older version of archive.
      state['headers'] = state['full_headers']
      del state['full_headers']
    if 'headers' not in state:
      raise HttpArchiveException(
          'Archived HTTP request is missing "headers". The HTTP archive is'
          ' likely from a previous version and must be re-recorded.')
    if 'path' in state:
      # before, 'path' and 'path_without_query' were used and 'path' was
      # pickled.  Now, 'path' has been renamed to 'full_path' and
      # 'path_without_query' has been renamed to 'path'.  'full_path' is
      # pickled, but 'path' is not.  If we see 'path' here it means we are
      # dealing with an older archive.
      state['full_path'] = state['path']
      del state['path']
    state['trimmed_headers'] = self._TrimHeaders(dict(state['headers']))
    if 'is_ssl' not in state:
      state['is_ssl'] = False
    self.__dict__.update(state)
    self.path = urlparse.urlparse(self.full_path).path
    self.formatted_request = self._GetFormattedRequest()

  def __getstate__(self):
    """Influence how to pickle.

    Returns:
      a dict to use for pickling
    """
    state = self.__dict__.copy()
    del state['trimmed_headers']
    del state['path']
    del state['formatted_request']
    return state

  def _GetFormattedRequest(self):
    """Format request to make diffs easier to read.

    Returns:
      A string consisting of the request. Example:
      'GET www.example.com/path\nHeader-Key: header value\n'
    """
    parts = ['%s %s%s\n' % (self.command, self.host, self.full_path)]
    if self.request_body:
      parts.append('%s\n' % self.request_body)
    for k, v in self.trimmed_headers:
      k = '-'.join(x.capitalize() for x in k.split('-'))
      parts.append('%s: %s\n' % (k, v))
    return ''.join(parts)

  def matches(self, command=None, host=None, full_path=None, is_ssl=None,
              use_query=True):
    """Returns true iff the request matches all parameters.

    Args:
      command: a string (e.g. 'GET' or 'POST').
      host: a host name (e.g. 'www.google.com').
      full_path: a request path with query string (e.g. '/search?q=dogs')
      is_ssl: whether the request is secure.
      use_query:
        If use_query is True, request matching uses both the hierarchical path
        and query string component.
        If use_query is False, request matching only uses the hierarchical path

        e.g. req1 = GET www.test.com/index?aaaa
             req2 = GET www.test.com/index?bbbb

        If use_query is True, req1.matches(req2) evaluates to False
        If use_query is False, req1.matches(req2) evaluates to True

    Returns:
      True iff the request matches all parameters
    """
    if command is not None and command != self.command:
      return False
    if is_ssl is not None and is_ssl != self.is_ssl:
      return False
    if host is not None and host != self.host:
      return False
    if full_path is None:
      return True
    if use_query:
      return full_path == self.full_path
    else:
      return self.path == urlparse.urlparse(full_path).path

  @classmethod
  def _TrimHeaders(cls, headers):
    """Removes headers that are known to cause problems during replay.

    These headers are removed for the following reasons:
    - accept: Causes problems with www.bing.com. During record, CSS is fetched
              with *. During replay, it's text/css.
    - accept-charset, accept-language, referer: vary between clients.
    - cache-control:  sometimes sent from Chrome with 'max-age=0' as value.
    - connection, method, scheme, url, version: Cause problems with spdy.
    - cookie: Extremely sensitive to request/response order.
    - keep-alive: Not supported by Web Page Replay.
    - user-agent: Changes with every Chrome version.
    - proxy-connection: Sent for proxy requests.

    Another variant to consider is dropping only the value from the header.
    However, this is particularly bad for the cookie header, because the
    presence of the cookie depends on the responses we've seen when the request
    is made.

    Args:
      headers: {header_key: header_value, ...}

    Returns:
      [(header_key, header_value), ...]  # (with undesirable headers removed)
    """
    # TODO(tonyg): Strip sdch from the request headers because we can't
    # guarantee that the dictionary will be recorded, so replay may not work.
    if 'accept-encoding' in headers:
      headers['accept-encoding'] = headers['accept-encoding'].replace(
          'sdch', '')
      # A little clean-up
      if headers['accept-encoding'].endswith(','):
        headers['accept-encoding'] = headers['accept-encoding'][:-1]
    undesirable_keys = [
        'accept', 'accept-charset', 'accept-language', 'cache-control',
        'connection', 'cookie', 'keep-alive', 'method',
        'referer', 'scheme', 'url', 'version', 'user-agent', 'proxy-connection',
        'x-chrome-variations']
    return sorted([(k, v) for k, v in headers.items()
                   if k.lower() not in undesirable_keys])

  def is_conditional(self):
    """Return list of headers that match conditional headers."""
    for header in self.CONDITIONAL_HEADERS:
      if header in self.headers:
        return True
    return False

  def create_request_without_conditions(self):
    stripped_headers = dict((k, v) for k, v in self.headers.iteritems()
                            if k.lower() not in self.CONDITIONAL_HEADERS)
    return ArchivedHttpRequest(
        self.command, self.host, self.full_path, self.request_body,
        stripped_headers, self.is_ssl)

class ArchivedHttpResponse(object):
  """All the data needed to recreate all HTTP response."""

  # CHUNK_EDIT_SEPARATOR is used to edit and view text content.
  # It is not sent in responses. It is added by get_data_as_text()
  # and removed by set_data().
  CHUNK_EDIT_SEPARATOR = '[WEB_PAGE_REPLAY_CHUNK_BOUNDARY]'

  # DELAY_EDIT_SEPARATOR is used to edit and view server delays.
  DELAY_EDIT_SEPARATOR = ('\n[WEB_PAGE_REPLAY_EDIT_ARCHIVE --- '
                          'Delays are above. Response content is below.]\n')

  def __init__(self, version, status, reason, headers, response_data,
               delays=None):
    """Initialize an ArchivedHttpResponse.

    Args:
      version: HTTP protocol version used by server.
          10 for HTTP/1.0, 11 for HTTP/1.1 (same as httplib).
      status: Status code returned by server (e.g. 200).
      reason: Reason phrase returned by server (e.g. "OK").
      headers: list of (header, value) tuples.
      response_data: list of content chunks.
          Concatenating the chunks gives the complete contents
          (i.e. the chunks do not have any lengths or delimiters).
          Do not include the final, zero-length chunk that marks the end.
      delays: dict of (ms) delays for 'connect', 'headers' and 'data'.
          e.g. {'connect': 50, 'headers': 150, 'data': [0, 10, 10]}
          connect - The time to connect to the server.
            Each resource has a value because Replay's record mode captures it.
            This includes the time for the SYN and SYN/ACK (1 rtt).
          headers - The time elapsed between the TCP connect and the headers.
            This typically includes all the server-time to generate a response.
          data - If the response is chunked, these are the times for each chunk.
    """
    self.version = version
    self.status = status
    self.reason = reason
    self.headers = headers
    self.response_data = response_data
    self.delays = delays
    self.fix_delays()

  def fix_delays(self):
    """Initialize delays, or check the number of data delays."""
    expected_num_delays = len(self.response_data)
    if not self.delays:
      self.delays = {
          'connect': 0,
          'headers': 0,
          'data': [0] * expected_num_delays
          }
    else:
      num_delays = len(self.delays['data'])
      if num_delays != expected_num_delays:
        raise HttpArchiveException(
            'Server delay length mismatch: %d (expected %d): %s',
            num_delays, expected_num_delays, self.delays['data'])

  def __repr__(self):
    return repr((self.version, self.status, self.reason, sorted(self.headers),
                 self.response_data))

  def __hash__(self):
    """Return a integer hash to use for hashed collections including dict."""
    return hash(repr(self))

  def __eq__(self, other):
    """Define the __eq__ method to match the hash behavior."""
    return repr(self) == repr(other)

  def __setstate__(self, state):
    """Influence how to unpickle.

    Args:
      state: a dictionary for __dict__
    """
    if 'server_delays' in state:
      state['delays'] = {
          'connect': 0,
          'headers': 0,
          'data': state['server_delays']
          }
      del state['server_delays']
    elif 'delays' not in state:
      state['delays'] = None
    self.__dict__.update(state)
    self.fix_delays()

  def get_header(self, key, default=None):
    for k, v in self.headers:
      if key.lower() == k.lower():
        return v
    return default

  def set_header(self, key, value):
    for i, (k, v) in enumerate(self.headers):
      if key == k:
        self.headers[i] = (key, value)
        return
    self.headers.append((key, value))

  def remove_header(self, key):
    for i, (k, v) in enumerate(self.headers):
      if key.lower() == k.lower():
        self.headers.pop(i)
        return

  def _get_epoch_seconds(self, date_str):
    """Return the epoch seconds of a date header.

    Args:
      date_str: a date string (e.g. "Thu, 01 Dec 1994 16:00:00 GMT")
    Returns:
      epoch seconds as a float
    """
    date_tuple = email.utils.parsedate(date_str)
    if date_tuple:
      return calendar.timegm(date_tuple)
    return None

  def update_date(self, date_str, now=None):
    """Return an updated date based on its delta from the "Date" header.

    For example, if |date_str| is one week later than the "Date" header,
    then the returned date string is one week later than the current date.

    Args:
      date_str: a date string (e.g. "Thu, 01 Dec 1994 16:00:00 GMT")
    Returns:
      a date string
    """
    date_seconds = self._get_epoch_seconds(self.get_header('date'))
    header_seconds = self._get_epoch_seconds(date_str)
    if date_seconds and header_seconds:
      updated_seconds = header_seconds + (now or time.time()) - date_seconds
      return email.utils.formatdate(updated_seconds, usegmt=True)
    return date_str

  def is_gzip(self):
    return self.get_header('content-encoding') == 'gzip'

  def is_compressed(self):
    return self.get_header('content-encoding') in ('gzip', 'deflate')

  def is_chunked(self):
    return self.get_header('transfer-encoding') == 'chunked'

  def get_data_as_text(self):
    """Return content as a single string.

    Uncompresses and concatenates chunks with CHUNK_EDIT_SEPARATOR.
    """
    content_type = self.get_header('content-type')
    if (not content_type or
        not (content_type.startswith('text/') or
             content_type == 'application/x-javascript' or
             content_type.startswith('application/json'))):
      return None
    if self.is_compressed():
      uncompressed_chunks = httpzlib.uncompress_chunks(
          self.response_data, self.is_gzip())
    else:
      uncompressed_chunks = self.response_data
    return self.CHUNK_EDIT_SEPARATOR.join(uncompressed_chunks)

  def get_delays_as_text(self):
    """Return delays as editable text."""
    return json.dumps(self.delays, indent=2)

  def get_response_as_text(self):
    """Returns response content as a single string.

    Server delays are separated on a per-chunk basis. Delays are in seconds.
    Response content begins after DELAY_EDIT_SEPARATOR
    """
    data = self.get_data_as_text()
    if data is None:
      logging.warning('Data can not be represented as text.')
      data = ''
    delays = self.get_delays_as_text()
    return self.DELAY_EDIT_SEPARATOR.join((delays, data))

  def set_data(self, text):
    """Inverse of get_data_as_text().

    Split on CHUNK_EDIT_SEPARATOR and compress if needed.
    """
    text_chunks = text.split(self.CHUNK_EDIT_SEPARATOR)
    if self.is_compressed():
      self.response_data = httpzlib.compress_chunks(text_chunks, self.is_gzip())
    else:
      self.response_data = text_chunks
    if not self.is_chunked():
      content_length = sum(len(c) for c in self.response_data)
      self.set_header('content-length', str(content_length))

  def set_delays(self, delays_text):
    """Inverse of get_delays_as_text().

    Args:
      delays_text: JSON encoded text such as the following:
          {
            connect: 80,
            headers: 80,
            data: [6, 55, 0]
          }
        Times are in milliseconds.
        Each data delay corresponds with one response_data value.
    """
    try:
      self.delays = json.loads(delays_text)
    except (ValueError, KeyError) as e:
      logging.critical('Unable to parse delays %s: %s', delays_text, e)
    self.fix_delays()

  def set_response_from_text(self, text):
    """Inverse of get_response_as_text().

    Modifies the state of the archive according to the textual representation.
    """
    try:
      delays, data = text.split(self.DELAY_EDIT_SEPARATOR)
    except ValueError:
      logging.critical(
          'Error parsing text representation. Skipping edits.')
      return
    self.set_delays(delays)
    self.set_data(data)

def create_response(status, reason=None, headers=None, body=None):
  """Convenience method for creating simple ArchivedHttpResponse objects."""
  if reason is None:
    reason = httplib.responses.get(status, 'Unknown')
  if headers is None:
    headers = [('content-type', 'text/plain')]
  if body is None:
    body = "%s %s" % (status, reason)
  return ArchivedHttpResponse(11, status, reason, headers, [body])

def main():
  class PlainHelpFormatter(optparse.IndentedHelpFormatter):
    def format_description(self, description):
      if description:
        return description + '\n'
      else:
        return ''

  option_parser = optparse.OptionParser(
      usage='%prog [ls|cat|edit|stats|merge] [options] replay_file(s)',
      formatter=PlainHelpFormatter(),
      description=__doc__,
      epilog='http://code.google.com/p/web-page-replay/')

  option_parser.add_option('-c', '--command', default=None,
      action='store',
      type='string',
      help='Only show URLs matching this command.')
  option_parser.add_option('-o', '--host', default=None,
      action='store',
      type='string',
      help='Only show URLs matching this host.')
  option_parser.add_option('-p', '--full_path', default=None,
      action='store',
      type='string',
      help='Only show URLs matching this full path.')
  option_parser.add_option('-f', '--merged_file', default=None,
        action='store',
        type='string',
        help='The output file to use when using the merge command.')

  options, args = option_parser.parse_args()

  # Merge command expects an umlimited number of archives.
  if len(args) < 2:
    print 'args: %s' % args
    option_parser.error('Must specify a command and replay_file')

  command = args[0]
  replay_file = args[1]

  if not os.path.exists(replay_file):
    option_parser.error('Replay file "%s" does not exist' % replay_file)

  http_archive = HttpArchive.Load(replay_file)
  if command == 'ls':
    print http_archive.ls(options.command, options.host, options.full_path)
  elif command == 'cat':
    print http_archive.cat(options.command, options.host, options.full_path)
  elif command == 'stats':
    print http_archive.stats(options.command, options.host, options.full_path)
  elif command == 'merge':
    if not options.merged_file:
      print 'Error: Must specify a merged file name (use --merged_file)'
      return
    http_archive.merge(options.merged_file, args[2:])
  elif command == 'edit':
    http_archive.edit(options.command, options.host, options.full_path)
    http_archive.Persist(replay_file)
  else:
    option_parser.error('Unknown command "%s"' % command)
  return 0

if __name__ == '__main__':
  sys.exit(main())


If in reality, your home or office needs long range wifi you have no choice rather than get the right long-range Wi-Fi antenna. Take an instance where your wi-fi is working perfectly well, and you move the workstation to the bedroom, and there is no signal.
Definitely, you need to extend the coverage area of the router by extra feet. To achieve this, you need a wifi antenna (high gain). The best wifi receivers thrive under specific niche applications.
Ask yourself, do I have the right information about outdoor wifi antenna? This will help you determine which wifi antenna is right for you.
In this article, we gathered what in our outlook feels to be the best long-range wi-fi booster.
Bearifi 802.11ac Antenna. This is a long-range booster which uses unique BearExtender technology. It has a 10dBi deep-seated directional antenna and high-speed 802.11ac dual band.
It has sturdy water and dust resistant cover, and it easily connects to your computer and can extend the range from your PC. However, it doesn't work as a repeater nor does it set up a new wifi hotspot.
They can be permanently installed either in your house, workplace or outside.
It is straightforward to create wifi hotspots using any typical router.
Transmits long range wifi both on water and land.
Using an ethernet cable, it can connect to your pc instantly.
Less power consumption – connected devices don't consume any power.
It uses a single data and power cable.
Requires no software installation, therefore, it is an easy set-up operation.
As described we found out that Bearifi 802.11ac is the best long range wifi antenna. It is highly rated on Amazon with numerous customer reviews are visible. Now that we already shared an overview of this receiver lets scribble down some great features.
It has a water and dust proof cover and therefore can be used on both land and water.
It has 2.4/5 GHz dual band.
It is compatible with Windows Vista, Windows 7, Windows 8.1 and Windows 10. It is not compatible with Android, IOS, Mac OS X and Windows RT Tablet.
The NextG USB-Yagi long-range wi-fi antenna is developed to connect via USB to your laptop or desktop. It connects using any wireless-N wireless-G or wireless- B.
It extends wireless connection with a speed of up to 300 Mbp. The speed can be altered across various applications. Don't get baffled when the wifi range is reduced by houses, trees, and other physical objects as TurboTenna NextG USB-Yagi long range wifi receiver is a perfect solution.
Has 2.4GHz high power speed which is recommended for a long distance wifi antenna.
It supports KALI Linux and Windows Vista, 7, 8, 10. No support for Mac OS.
Requires a few clicks to install making it easy to use.
Proxicast cross-polarized MIMO antenna has signals enhanced for any cellular application. Its compatibility with different cellular operators such as Verizon, Telus, Sprint, AT&T, Rogers, Bell Mobility, T-Mobility among others has never been questioned.
It also supports 3G, 4G / LTE. It is a perfect outdoor wifi antenna because its made of rust-free and robust hardware. It can be fitted on utility poles, rooftops, and surveillance trailers.
It is lightweight and waterproof making it a perfect external wifi booster.
It is specifically developed for 4G/LTE network.
It has a MIMO design, 10dBi and 75-degree cross-polarised directional beam width.
Requires two coax extension cables and 2x Female N connectors.
The TP-Link 2.4 GHz Parabolic antenna is a perfect fit for long range wifi dispersion. It is designed as an outdoor wifi antenna.
It is a 24 dBi directional operation receiver. It is designed for a range of up to 56 km. It has excellent performance as the surface is designed with a steel reflector.
It is easy to use and is compatible with routers, access points, network adapter and bridges along with other wireless devices.
It has a compact structure making the receiver resistant to wind.
It is easy to mount and install as it is lightweight and require no software installation.
With 24 dBi wifi can be distributed to an extremely long distance.
The TP513 Yagi WiFi antenna is suitable for multipoint and directional band applications. It has 2.4GHz frequency. It comes along with industry standard and pigtail N-Female connectors.
This booster works well even without grounding. However, we recommend you add a surge protector to prevent lightning. It allows vertical and horizontal polarisation.
It has two U bolts which help during mounting on the pole.
Comes along with lightning protection making it more reliable and safe to use.
It has 2.4 GHz making it a perfect long distance wifi antenna.
The Proxicast Universal Omni-Directional Antenna is another long-range wi-fi antenna. It supports 3G, 4G/LTE, and GPS. The omnidirectional receiver supports service operators in both Canada and the US such as AT&T, Verizon, Telus, T-Mobile, Rogers, Bell Mobility to mention.
This is the perfect booster for stationary and mobile applications. It is also highly compatible with routers from Cisco, Sierra wireless, Digi, Proxicast and even different modems.
It has 1575 MHz GPS frequency.
High compatible with 3G, 4G/LTE modems and broadband routers.
It has 5 dBi and can cover a wide range in your home area and compound.
The TP542 Panel WiFi booster is not just another long-range wi-fi antenna as it is designed with the modest technology. It is a dual band receiver with 2.4 GHz and 5 GHz frequency.
It can send its signals in one direction and is designed for stationary places. It also has an N-Female connector. Therefore, you can connect directly to devices such as Wireless access points, routers, and wifi cards.
Even though it is listed seventh, it is an ideal receiver for your house or building. It has an excellent rating on Amazon. Just try it.
the long-range wi-fi antenna comes along with U bolts, screws, and washers. – Pointing it in the right direction will improve the frequency of the signal.
It has a compact design making it economise on space.
One of the most mind-boggling times come when selecting the most effective long-range wifi booster. To start with you need to have a vivid image of the wifi range.
2-5 dBi antenna is preferably for short range wifi distribution, and a case study could be a home, while 9 dBi antenna works well for long range wifi dispersion.
If you want to dispatch wifi from your office to your home which is a few miles away, you need 7-9 dBi antenna. The distance will determine the antenna to purchase.
For extended range wifi distribution, you need 2.4GHz frequency as 5GHz works perfectly for short range.
In summary, there are many long range wi-fi antennas available in the market but when buying one you need to have gone through a specific guide. We have described the easy tips to follow when purchasing your long range wifi booster.
In summary, there are numerous long-range wi-fi antennas in the market but selecting the best has never been an easy task.
To mention we recommend Bearifi 802.11ac Antenna as your receiver of choice, but we don't limit you from exploring other boosters.
In a nutshell, pick the long-range wi-fi antenna that suits your application, and you will always stay connected.


text:
Q: Using laptop as third monitor? Best way to set up (without thunderbolt)? I currently  have a 27' iMac that uses a Samsung 16' monitor as a second display using Mini Display Port to VGA. I also have a 2011 Macbook Pro and was wondering if there was a way to set up this up as a third monitor to the iMac. The MDP is already in use, so what would be my options (if there are any) for connecting a laptop as another monitor? Also note that this monitor does not have thunderbolt, so thats out.
Thanks in advance for help.

A: Take a look at AirDisplay. It supports turning you Mac, Windows, iOS or Android into an external screen.
There are several other apps like this such as Screen Recycler, but AirDisplay is the one I currently use.
Any of these apps will likely work best on a wired ethernet connection.

A: Screen Sharing option in the syst preferences -sharing would be a option, but obviously it will show the same screen.
Check the set up at this Apple article https://support.apple.com/kb/PH18696?locale=en_US

redpajama_set_name: RedPajamaStackExchange


Create a guest machine completely from the command line. Virt-install does not require an XML image descriptor file. virt-install will prompt for any required information not provided on the command line. Following is the required information.
Use KVM or KQEMU kernel acceleration if available. Recommended when the guest OS is compatible.
If an image has multiple boot descriptors, use descriptor n. The first descriptor is descriptor 0. When omitted, the one that best fits the current hypervisor is chosen.
File to use for virtual CDROM device. The file may be an ISO file, CDROM device, or a URL that refers to an ISO image to fetch.
Warn if the number of virtual CPUs for the guest exceeds the number of physical CPUs in the host system.
Allow guest to use only the given set of CPUs. A set is a comma-separated list of numbers or number ranges: e.g., 0,2 or 0–2,5,6.
Print debugging information to standard output.
Required. The path to the file, disk partition, or logical volume to use to store the new guest's virtual disk. If the original system has multiple disks, this option should be given once per disk.
Print information on the command and options, then exit.
Set the keymap to use for the virtual console. By default, an English keymap is used.


You have come from the lovely Leanne Mitchell Blog hops are full circles, so you can start hopping here and then at the end of each post is a link to the next one. There is also a list of participants at the end in case of any broken lines.
The Designer Series Paper I chose is the Tea Room paper, this paper is not as thick as some of the others which make this perfect for the 2 projects I made. The first one is a cute party hat which will be for my niece Lily on her 11th birthday. I just used a cereal box or you can use any soft cardboard I pulled apart a cheap old party hat as a template and used liquid tombow to glue together. The silver ribbon is retired but I needed silver so used it, the other ribbons are Rich Razzelberry Velvet and metallic edge Berry Burst. The flower I made out of vellum which I have a few in my stash I made a while ago, you can use a pompom if you like. I cut the silver circle out using the starburst punch and cut out the 11 with Rose Shimmer Paper and used the sentiment from the Happy Birthday Gorgeous stamp set using Rich Razzelberry ink and popped it up with some dimemsionals.
For my second project I made an envelope flip book style to add small gifts. I used 4 Whisper White envelopes and scored 1/4inch above the fold line of each envelope, I then added score tape just above the fold and a bit of liquid glue and adhered each envelope (if you google envelope flip book there are lots of tutorials very easy) after that its up to you how you decorate it, I also used a 2 1/4n inch punch and punch out a semi circle opening so you can get the goodies in and out easier. The butterfly is cut out of Rose glimmer paper and I used the Springtime impression thinlet. For the border on each page I used the Edgelit Thinlet set these are beautiful and cut out easily.
The above picture is what I put in the inside of each pocket. I made the little see through bags using vellum and washy tape and put in our new buttons. Its also a good way to use up your retired stock to give as gifts.


Few firearms ever earn the level of respect the Browning BPS boasts. The BPS has a solid reputation for long-lasting durability and an uncanny ability to keep on working in spite of what Mother Nature does to stop it. This shouldn`t come as a surprise because the BPS has what most other pump shotguns don`t features like rigid dual machined steel action bars, a forged and machined steel receiver, a convenient top-tang safety and bottom ejection for ambidextrous operation, and legendary Browning balance and handling.
The BPS continues to be the pump shotgun serious shotgunners like you can always rely on. Its advantages are more than just being a great shotgun for the left- OR right-handed shooter. The bottom ejection design and full, two-sided steel receiver combine for a level of durability and strength that is not common among pump action shotguns today.


Senator Elizabeth Warren's strange insistence that trace amounts of American Indian DNA confirm her past claims to minority status has drawn snorts from her fellow liberals, as well as from conservatives. Even the Cherokee Nation piled on, saying she is "undermining tribal interests." Americans from all sides who disdain identity politics should recognize, however, the yeoman's work the senior senator from Massachusetts is doing to expose the empty core of our new national pastime, the victimhood Olympics. From protected statuses to grievance-mongering, set asides, racial preferences, bias training, political correctness and hegemonic narratives, the whole edifice of identity politics rests on the foundational notion that we are all members of monolithic groups. Each is imbued with group-think and scrambles for pole position on the national stage through power relations — or some such. Warren is making it plainly obvious that we're all a mix, a bit of this and a dash of that. If there is a power struggle taking place, it is internal — your French genes fighting your English ones in some intestinal Waterloo. Warren is soldiering on, claiming the DNA results she released Monday vindicate her long-standing claims of Native American heritage. For the record, what the DNA revealed was that Warren has between 1.56 percent and 0.097 percent American Indian DNA — and measured, at that, against DNA samples from Peru, Mexico and Colombia, not from the United States. "A DNA test is useless to determine tribal citizenship. President Trump will doubtless continue ridiculing Warren's claims of inherited disadvantage, keeping the issue alive.
A citizen of the Cherokee Nation on Monday said Sen. Elizabeth Warren is "more concerned" about her political future than the interests of tribal sovereignty and Native rights. Warren's long-standing assertion of Native American heritage was called into question Monday after the results of her DNA test were made public. Warren published the DNA results as a rebuttal to President Donald Trump, who has long mocked her ancestral claims by referring to her as "Pocahontas." While the results indicated the Massachusetts Senator may indeed have some Native American heritage – anywhere from 1/64 to 1/1,024 – the matter was not settled for everyone. Rebecca Nagle, a citizen of the Cherokee Nation, told Fox News' 'The Ingraham Angle' that Native American identity "is not up to Elizabeth Warren." "[A]n important thing for non-native people to understand is that Native identity is up to Native people. It's not up to a scientist in a lab, it is not up to President Donald Trump's twitter feed, and it's not up to Elizabeth Warren," Nagle said. "We live in a country where most of the non-Native population doesn't understand or know basic information about our identity, about tribal sovereignty, and about our history as Native people. And so these myths that – you know – Native identity can be determined by a DNA test, directly undermines our rights as Native Americans and our tribal sovereignty." Nagle alluded to past comments Warren made in which she mentioned her distant relative's "high cheekbones" as evidence of her Native heritage. "During this entire controversy, Elizabeth Warren has relied on stereotypes and popular misconceptions about Native Americans to prop up her false claims," Nagle said. Nagle criticized Warren for publishing the results of DNA test and ignoring "hordes of Native people have been talking about how DNA tests in no way prove Native heritage or any connection to a Native tribe." "I think that her decision shows that she does not listen to Native people and is more concerned about her own political career and her ambitions than tribal sovereignty and Native rights," Nagle said.


text:
Cover > Translations of catalan literature > fiction > Baltasar Porcel
Baltasar Porcel
Visat núm. 11
(abril 2011)
by Pere Antoni Pons
Baltasar Porcel i Pujol (Andratx, Majorca, 1937 - Barcelona, 2009) is one of the widest-ranging writers in the history of Catalan literature, as much for the quality of his work as for its quantity and versatility. Porcel is known above all as a novelist, but he has also cultivated¬—with strength, audacity and rigor¬—many other genres: drama, short story, travel writing, historical and political essay, interviews with people relevant to the world of politics and art, as well as opinion pieces, which appeared almost daily from 1982 in La Vanguardia, a column in which immediate¬—and often controversial¬—reflection provoked by the conjuncture of daily events does not impede in-depth analysis, nor complex, robust, and wide-ranging thought.
Andratx and Majorca, the town and island where Baltasar Porcel was born and lived for the first years of his life, suffered the penuries of the post-war period (after the civil war of 1936-1939) in the forties and fifties, which ultimately meant the accumulation of a long string of historical penuries, including everything from a nearly endemic poverty to the inevitable difficulty of field work. The town where Porcel grew up was a place dominated by nature, by the presence of the sea, the mountains, the plants and cycles of the countryside. Family links were also fundamental, the warmth of clans, the events—sea battles against pirate Turks and North Africans—experienced by ancestors who were still alive through the memories transmitted by those who knew them. This was a world in which fatality predominated and subsistence was the only objective, but in which there were also opportunities for the more daring and ambitious: emigration to Cuba or smuggling, for example. Or reading and literary ambition. In Porcel's case, this atmosphere and these elements had decisive effects, not only on his personality, but also on all of his work: an important—the most important?—dimension of his novels consists in the mythic recreation—impassioned and adventurous, imaginative and tragic—of this atavistic and intractable Majorca.
When he was sixteen, Porcel moved to Palma, where he frequented certain literary circles and established a close relationship with Llorenç Villalonga. This closed and somewhat provincial world soon deceived him, however, and he decided to leave for Barcelona. His first books—Els condemnats (The Condemned), Solnegre (Blacksun)—had already been published and had won him his first prizes. Settling in the Catalan capital allowed him to channel his vocation more fully. Further, as luck would have it, Porcel attended the War Council convened by Jordi Pujol, future president of the Catalan Generalitat, which began a long friendship and political complicity that would last for many years. In the late sixties, Porcel began his collaboration with the journals Serra d'or and Destino, where his interviews with the great Catalan and Spanish figures of the era stood out. In this way he came to know Josep Pla, who would become, much more than Villalonga, his great teacher: a model of a professional dedicated to his work, and author of a vast and laborious oeuvre.
It was in the late sixties and early seventies that Porcel definitively opened his field of action, interesting himself in the great events happening around the world, from Paris in May 1968 to Mao's China and California's hippie, countries and occurrences that he knew first hand thanks to his many travels, from which he extracted abundant literary material. Parallel to this ever more restless and expansive life, Porcel established himself as a powerful novelist (Cavalls cap a la fosca [Horses into the Dark], is from 1975), and as an intellectual to reckon with, wise and incisive, with the capacity and lucidity to act and maneuver in reality.
Thus the three backdrops of Baltasar Porcel's vital and intellectual action become defined. The three backdrops are: a) the intimate Majorca that the artist converts into a novel; b) the socio-politically convulsed and complex Catalonia and Spain that the opinionated author explains and influences (in this respect, the friendship that united Porcel to King Juan Carlos during the transition and early democratic period is very relevant); c) the broader world that the impenitent traveler knows from end to end. It was precisely in the late seventies and early eighties that Porcel the traveler made a discovery that would leave a personal and creative mark: black Africa, where he traveled tirelessly and where, in 1984, a novel took shape that simultaneously had elements of classic adventure and of existential litany, titled Els dies immortals (The Immortal Days). As an example of the ease with which Porcel is a writer of the world, in that same year, 1984, Les illes encantades (The Enchanted Isles) appeared, a recompilation of nonfiction material with which he had explained and conveyed the Balearic Islands and his relationship to them. Islands as homeland of departure and the world as homeland of arrival, novels as the backbone of an oeuvre in which books of other genres also achieve a tremendous quality: Porcel is son and brother, of this there is no doubt, of all the artists who have aspired to the absolute.
During the 80s and 90s, the author of Andratx participated very actively in the dynamics of the Spanish and Catalan sociopolitical world. He continued to write in the press, he continued working—as a constructive critic—with President Pujol, and he continued to write his novels, which continued to bring him awards. In 1988, he won the Sant Jordi with Les primaveres i les tardors (Springs and Autumns), the nucleus, together with El cor del senglar (Heart of the Boar), of what has come to be called "the Andratx myth." It was also during these years that Porcel founded and directed the Catalan Institute of the Mediterranean, a personal bet in favor of the knowledge and interrelationship of the peoples of the Mediterranean. From this Mediterranean fervor, for which Porcel has always waved a flag, came one of his most spectacular books: Mediterrània (Mediterranean). Tumultuous swells, which would be for the Mediterranean what Magris' Danube was for that great river of the same name.
Through his experiences as an agent of the sociopolitical system, Porcel has developed a kind of novel that does not have anything to do with either the myth of Majorca or the world adventures that predominated in his work from the sixties through the eighties. A type of realist novel, crude, brutal, but also sarcastic and mocking, normally with a protagonist who is ruined, bitter, cynical, and who analyzes and explains with excruciating disenchantment the miseries of a certain Barcelonan life (Lola i els peixos morts [Lola and the Dead Fish], Ulisses a alta mar [Ulysses on the High Seas]) and that of a Majorca devastated by tourism (Olympia a mitjanit [Olympia at Midnight]).
These novels are the definitive proof of the heterogeneity of Porcel's talent: both old and modern, comparable as much to Homer or Heracles as Faulkner, Camus, or Céline. In 2007, Baltasar Porcel was awarded with the Prize of Honor of Catalan Letters.
Translated by Robin Vogelzang
Baltasar Porcel, Frankfurt, 2007, Institut Ramon Llull
Horses into the Night (1975)
by John L. Getman
Difunts sota els ametllers en flor (1970)
by Volker Glab
Heart of the Boar (2000)
The Permanent Revolution (1978)
Olympia at Midnight (2004)
Ulisses a alta mar (1997)
by Francesco Ardolino
Horses into the night
Català | Español | Français | Italiano
Difunts sota els ametllers en flor
Català | Deutsch | Français
El cor del senglar
Català | Español
El divorci de Berta Barca
Els argonautes
Els dies immortals
Els escorpins
La lluna i el cala llamp
Les pomes d'or
Springs and Autumns
Català | Español | Français
Lola i els peixos morts
L'emperador o l'ull de ven
Català | Français
Olympia a mitjanit
Solnegre
Ulisses a alta mar
Català | Español | Italiano
El lirisme en Porcel
by Frederic Barberà
Memòria i història
Edited Work of Baltasar Porcel
Translated Books of Baltasar Porcel
Traduccions de Baltasar Porcel en antologies i revistes

redpajama_set_name: RedPajamaCommonCrawl


text:
State Auditor Launches Investigation into PERA Raises
The New Mexico State Auditor's office has launched an investigation into the massive, and possibly improper, pay raises given to Public Employees Retirement Association employees over the past five years.
State Auditor Brian Colón told ABQReport on Thursday that his office was proceeding with the investigation that had started out as a fact-finding effort under previous auditor Wayne Johnson.
"There is an ongoing investigation by the office of the State Auditor," Colón said.
In 2018 alone, PERA employees got $630,000 in salary increases. Those pay hikes, which in some cases amounted to 30 and 50 percent, come directly out of PERA investment funds, not the state's general fund. And those increases come at a time when PERA's funds are facing a $6 billion long-term shortfall, and when the PERA staff and board are considering a solvency plan that would cut cost-of-living-adjustment increases for three years to PERA's 40,000 retirees.
On Tuesday, PERA's 12-member board voted unanimously to launch an investigation/audit into the raises. They're particularly concerned about a 2 percent pay hike that PERA Executive Director Wayne Probst gave to himself last year. The hike was approved by the state Legislature and was intended for rank-and-file state workers. But Probst is an exempt employee who serves at the pleasure of PERA's board, and only the board can increase his salary. Probst gave himself the 2 percent raise apparently without taking it to the board for approval.
Some of the raises over the last five years are indeed eye-popping. Employee Kristin Varela went from making $20.90 an hour as a financial analyst in fiscal year 2015, to making $61.88 an hour as the agency's deputy director in FY18. That's a 196 percent increase.
Here are the 2018 raises that Probst gave to PERA workers.
Here are four years worth of raises that went to PERA workers.
It will be interesting to see how PERA board members react to questions from Colón's staff. During an executive session meeting on Tuesday, several board members were angry that details of the raises, which are public record,dd were published by the ABQReport. Some board members accused their colleagues of leaking the public information to this publication.
The previous stories I've writen about the PERA raises are posted below.
PERA employee raises
Wayne Probst
Brian Colon


text:
Car hire at Gatwick Airport
Hiring a car at Gatwick Airport
Gatwick Airport (LGW) is located close to Crawley, West Sussex. It offers international flights and is the second largest airport in the UK, offering great links to the city of London and other locations in the south-east of England.
You can reach the centre of Crawley in as little as 15 minutes via the M23 and head into central London in an hour and a half via the same road. The accessibility of the airport means that hiring a car from there can allow you to start your trip as soon as you land, no matter where you're heading to.
There's plenty of choice when it comes to hiring a car at Gatwick Airport, ensuring you can find the best deal to suit your budget and needs. The Holiday Autos website allows you to see which car hire companies have vehicles available from Gatwick Airport for the dates of your travel. It lays out everything you need to make a decision without any hidden costs, so you know exactly what you're getting.
Booking through Holidays Autos also means you benefit from around the clock support as well as free cancellation, so you have peace of mind before and during your trip.
A total of ten car hire companies offer vehicles at Gatwick Airport, with car hire desks being located in both the North and South terminals. It is a good idea to check whether the company you've booked with is located is in the North or South terminal, as you may need to get an airport shuttle bus to the other terminal to pick up your car.
Some companies have pick-up points elsewhere if you have arranged alternative transport from the airport, so you should check the options when arranging your car.
Best Car Hire Companies Gatwick Airport
Car Hire Reviews Gatwick Airport
"The pick-up was easy, it took 5 minutes from arriving at the desk to driving out the car, and the return took even less time. Booking with Holiday Autos was easy and I received the car that I had asked for, just perfect!"
Hiring a car means that you have plenty of choice when it comes to deciding where to go once you've collected your luggage at Gatwick Airport. You can head to any number of historical sites, shopping destinations and great parks, with the whole of the south-east being open to you.
Whether you want to stay in this area of England or explore further afield, car hire will help you to see and do as much as possible. This ensures that you can get the most out of your trip whether you're travelling for business or pleasure.
Nearby Gatwick Airport
If you need a quiet place to go after the busy Gatwick Airport, drive 15 minutes down the M23 to Tilgate Park in Crawley. Not only does Tilgate Park offer stunning open spaces and a beautiful lake, it also has a nature centre, which is home to a mix of animals. You can also head to Go Ape for a fun tree-top adventure that features zip-wires and Tarzan swings, as well as stunning views.
Gatwick Airport is also close to Chessington World of Adventures, which is a great day out for the whole family. The resort is just 35 minutes from the airport via the M23 and London Orbital Motorway. As well as the rollercoasters of the theme park, you can also explore Chessington Zoo and Sea Life Centre, giving you plenty to do.
There are also some interesting options for days out further away from Gatwick Airport, including Leeds Castle. Located in Kent, you can reach the castle in less than hour if you take the London Orbital Motorway and then turn onto the M20. The castle offers tours to learn about the history and there are beautiful grounds to explore. You can also stay the night in one of the holiday cottages or at the Knight's Glamping grounds, offering a unique experience.
Just under two hours away from the airport is the New Forest, which you can drive to by taking the M3. The forest is a beautiful place to go for a holiday or a day out and will help you relax fully after your time at the airport. Not only is there nature to enjoy, including New Forest ponies, you can also explore museums, gardens and wildlife parks.
Top attractions to visit near Gatwick Airport
Car hire allows you to make the most of the area around Gatwick Airport, including some of the top attractions in the region.
Whether you stay close to Crawley, head into London or go elsewhere, you'll have plenty of choices when it comes to food and drink. Even the fussiest of eaters is sure to find something great among the popular restaurants, hidden gems and street food vendors in the London area. If you drive to any of the lakes or rivers in the area, you can also enjoy a drink and a bite to eat while taking in great views, especially during the spring and summer months.
Those who like to feel a bit of adrenaline pumping might want to take the 15-minute journey down the M23 to Southern Pursuits. Located on Tulleys Farm, Crawley, Southern Pursuits offers you a chance to try your hand at quad biking, archery, off-road karting and clay pigeon shooting. This is a great way to enjoy the outdoors and try something a bit different.
You can also head into the city of London for great shopping experiences and plenty of other options when it comes to things to do. Spend a day exploring city landmarks, grab a ticket to a show or simply head to one of London's amazing parks.
For those who want a slightly slower pace during a trip, there is plenty of sightseeing to do close to Gatwick Airport. One great place is Hever Castle, which is only half an hour away from the airport and has an interesting history. Not only is the castle an attractive building with beautiful grounds, it also has a great history to learn about.
You may also want to visit the 19th-century gardens of the Nymans estate. The estate is now owned by the National Trust, offering beautiful views of the Sussex countryside that seem a world away from the busy airport.
Car hire in Birmingham
Car hire in Birmingham airport
Car hire in Gatwick
Car hire in London
Car hire in Edinburgh
Car hire in Edinburgh airport
Car hire in Glasgow
Car hire in Glasgow airport
Car hire in Belfast city airport
Car hire in Belfast international airport


text:
Frozen Endzone
Frozen Endzone dev diary: Origin Story
By Paul Taylor published 12 September 13
Mode 7 Games, creators of our Strategy Game of the Year in 2011 , share an update on their progress with Frozen Endzone .
Welcome to the first in the series of this behind-the-scenes look at Frozen Endzone. Although it's our third game, Endzone feels a lot like a difficult second album. Our previous title Frozen Synapse had an amazing critical reception, winning awards and exceeding all of our expectations. Following something like that that is a pretty torturous soul-searching process as you try to find the needle of an intelligent decision in the haystack of random gibbering that accumulates in 4 years of making a single game.
We were confident from the outset that simultaneous-turn-based gameplay was something we wanted to continue. Here's Lead Designer and Programmer Ian Hardingham:
"Two years before I even started coding Frozen Synapse, I spent the Christmas break playing Chaos League 's simultaneous-turn-based mode. I was spellbound by the idea of a turn-based sports game,and absolutely loved inventing different formations and plays for every match. "Competitive geometry" might sound dry, but the thrill of both you and your opponent viewing the exact same field,but often seeing totally different things, is very exciting to me. I soon got frustrated with Chaos League's flaws--much as I did with Laser Squad Nemesis --but the idea for Frozen Endzone was born that holiday season.
I wanted to make a game which was more creative and a bit less punishing of lapses in concentration. But it had to be as deep, if not deeper. The sports game idea that came from Chaos League was perfect."
The first phase of making the game was to prototype the gameplay, and this happened inside Frozen Synapse itself. The mechanics, though pretty different, required the same framework to operate so this ended up being a great choice.
"It took six months of full-time prototyping to come up with the core mechanic," says Ian. "Originally the game was to be based around allocating points to Speed, Power, and Reactions for each of your players; maybe gambling on making a player run at full speed for the next turn, but if he was tackled the result would be disastrous. There was promise in this but it was too fiddly. I spent all day every day tweaking rules and playing against (Level Designer and Tester) Bin, and eventually found the core mechanic: when you're stationary, you're powerful; when you're moving, you're vulnerable. It sounds extremely simple but I believe the best game mechanics are."
At the same time, our Lead Artist Rich Whitelock started concepting. Initially, we wanted a VR-like, almost Tron-style look again but as time went on we decided to move away from that as we felt like we had a chance to do something more unique. We wanted the robots to seem like machines and have a real physicality to them. Here's one of the first concepts:
"I was trying to create a concept that didn't fall into typical tropes of concept art," he told me. "This utilised techniques (lighting, rendering, materials etc.) which were feasible for the eventual realtime game engine and showed the game in a state of play. Readability was a priority. Exploring different ideas led to towards a strong kind-of-tech-fantasy atmosphere in both the stadium locale and the lighting and FX."
We decided to use Torque 3D for this project mostly down to Ian's familiarity with it and the ease with which we could add new features. Our ninja freelancer Martin Johnson was brought into work mostly on rendering stuff to see if we could execute Rich's concepts.
Ex-Sony animator Martin Binfield was brought in to work on early animation stuff. We wanted the robots to have a big and characterful style but also a heavy realism: it needed to be impactful when two of them clashed in a big tackle or block. Martin's distinctive animation style now pervades the game.
Over the next few months, I'll be letting you know what's currently happening with Frozen Endzone as we reach beta and beyond.
Follow Paul Taylor on Twitter @mode7games .
Playing Total War: Warhammer 3 as the series' weirdest, most ambitious faction
PC Gamer is part of Future US Inc, an international media group and leading digital publisher. Visit our corporate site.


text:
Home » Trending » Charlie Murphy's Nose Job: Did the Happy Valley Actress Get Work Done on Her Nose?
Charlie Murphy's Nose Job: Did the Happy Valley Actress Get Work Done on Her Nose?
Jan 18, 2023 @ 1:45 GMT+0000
Found in: Actress, BBC, Celebrity Plastic Surgery, Happy Valley
Charlie Murphy is the latest subject of nose job speculation. The Happy Valley star had a nose that seemed to be very protruding and big in some of her old pictures showing her side profile. Now, there are no photos taken from the exact angle to compare but from the front, her nose looks very narrow and straight and perfect with a refined tip. This has led her fans to suspect Charlie Murphy has had a nose job.
Charlie Murphy is an Irish actress. And you might recognize her from her recent work in Happy Valley. She plays the role of Ann Gallagher in the BBC cop drama. She was born in Enniscorthy to hair salon owners Brenda and Pat Murphy. She has five siblings. Her family relocated to Wexford after she turned 12 years old. She took an interest in acting ever since she was a child.
Following her passion, she went to study drama and acting when she turned 18. She trained at the Gaiety School of Acting from 2006 to 2008. And after completing her training, she built a career in acting. She has done both television and feature films. Her major breakthrough role was the role of Siobhan Delaney in the RTE drama series Love/Hate. Her performance in the show earned her the Irish Film and Television Award in the category of Best TV Actress in 2013.
Charlie Murphy also won the award for Best Actress in a Lead Role at the 2015 Irish Film and Television Awards. She went on to bag more awards as time went on. In 2017, she won a further two IFTA Awards, both of them in the category of Best Actress in a Supporting Role, one time in 2017 for her work as Ann Gallagher in the BBC One drama series Happy Valley and another time in 2018 for her performance as Jessie Eden in the BBC One historical crime drama series Peaky Blinders.
Though she wants nothing more than to act and go back home and relax, the nature of the industry she is in has dealt her speculations of a nose job. She has never acknowledged such rumors and never bothered to confirm or deny those. But these days, people take silence as confirmation. So people truly believe she had work done on her nose. But did she? Let's find out more about Charlie Murphy's nose job!
Previously, we touched on the plastic surgery stories of Maya Jama and Pamela Anderson.
Charlie Murphy's Nose Job: The Happy Valley Star Seemed to Have a Different Nose in Some of her Older Pictures!
Charlie Murphy (@charliemurphyactress) is highly suspected by her fans of having undergone a nose job for cosmetic purposes to make it a more aesthetic one.
Charlie Murphy is easily one of the most in-demand actresses in Ireland and she could easily move to Hollywood and build a bigger career (in terms of fame and popularity) there but she has her feet planted firmly on British soil. Want to know why? Well, it's to deal with the more stressful and superficial parts of the industry she's in with comfort. She loves to act but showbiz has its dark sides. Like the pressure to look perfect and the increasing trend of getting plastic surgery such as Botox and nose job.
The actress, who is now based in London, said that she deals with the pressures that are part and parcel with her industry by going out sometimes.
I do let myself go sometimes. Some days I can't fit into those jeans, and sometimes when I can, I celebrate with a bun!
Charlie Murphy, the latest subject of a nose job speculation, handles stressful things lightly like that. But if she went to Hollywood, she would not be the same and she fears it would steal her identity. She said,
I think if I did that, that side of the industry would cripple me. Immediately I'd be thinking 'I need a boob job' and then 'I need to get a nose job'…I'd rather just continue to seek that out and hope that it keeps coming rather than sacrifice who I am for a rom-com.
You can understand why she did not go to Hollywood. And Charlie Murphy may not have moved there but she sure is getting treated like she's one of them, what with all the speculations about a nose job. After the premiere of Season 3 of Happy Valley, fans noticed her appearance more closely and in more detail and just fell in love with her nose. Had she ever worried that she would feel the pressure of getting a nose job if she came to Hollywood?
Why did she feel that way when hers looked like the nose goals for people these days? I mean, people go to their doctors hoping they could get a nose like hers, a narrow and straight one with a very refined tip. She already has that. Why would she ever think about getting a nose job? Or she did think about it because the one she has now is not the one she had before and so, she bought the trendy one with a nose job.
Come to think of it, Charlie Murphy did have a very protruding nose in some past pictures. From a side profile, her nose looked too big for her face which did not look very good. But now, we don't have side profiles of her in the exact same angle to compare but from the front, her face looks angelic and her nose looks perfect which has led people to believe that she underwent a nose job to make it more shapely and less protruding.
However, Charlie Murphy has never acknowledged these speculations of a nose job, never confirmed or denied them.
What Happened to Becky in Happy Valley? How Did Catherine's Daughter Die? Read More
Maya Jama's Plastic Surgery: Is Cosmetic Treatment the Secret to Her Younger Appearance?
Pamela Anderson's Plastic Surgery: Why Does The Baywatch Star Look Unrecognizable These Days? The Actress Then and Now!
Olivia Culpo's Plastic Surgery: What Are the Beauty Secrets of the Former Miss Universe? Before & After Pictures Examined!
Buddy Valastro's Weight Loss in 2022: Comparing His Before and After Pictures, the Former Cake Boss Star Looks Way Too Slimmer!


FD is an abbreviation for Familial Dysautonomia, a rare, life-threatening, genetic, neurologic disease present at birth.
Dr. Berish Rubin and Dr. Sylvia Anderson's research has vastly improved the lives of children and adults with FD. Their laboratory, The Laboratory for Familial Dysautonomia Research at Fordham University, New York, is dedicated to studying the molecular biology of Familial Dysautonomia (FD). In addition to the discovery of the gene mutation that causes FD, research at the laboratory has produced several life-changing treatment breakthroughs.
Need help? Have questions? We are here to assist you. In addition to the tips listed, we are constantly updating these resources to make living with Familial Dysautonomia as easy as possible.
Research discoveries of Drs. Anderson and Rubin have been embraced by many in the medical community. Physicians worldwide collaborate with these researchers, anxious to make certain that they are using the latest scientific knowledge to treat their Familial Dysautonomia patients.


Everyone has different Christmas traditions they stand by, and if you're like me you believe your traditions are the only right way to do it; I had a flatmate who ate "just whatever, cereal or something" for Christmas breakfast when it's clearly supposed to be coffee and croissants, and another friend who's never watched the queens speech. While, unfortunately many of our holiday traditions aren't the most eco-friendly, fortunately there are easy switches we can make.
I get it. I am one of the first to shun the busy high street in favour of staying home with a mince pie and having all my Christmas shopping delivered to me. However consider the carbon footprint attached to having 15 different packages delivered to your door, instead of a trip into town on the bus. And this is before we consider the other practices we're encouraging by giving money to the big corporations. Shop local where you can, and if you truly can't face the Christmas wrestling matches just to get in through the doors of the shop, have a look online past Amazon and Asos. Buy a Stay Wild Swim product, a Guppybag or Cora Ball, these new products are designed to filter out the harmful micro-plastics in our washing machine cycles, or get crafty and start knitting! Your gift will feel more personal and the environment will thank you.
This is a tricky one because you can't control what wrapping paper you receive, and you also can't control what happens to the wrapping paper your gifts are wrapped in. But did you known the gorgeous shiny and glittery paper that makes our instagram tree's look so good can't be recycled? The good news is there's a lot of stylish paper out there that either can be recycled, or is made from recycled materials itself. Paperchase and Hobbycraft are your friends here. Shop around and you'll be sure to find something with style, or go 2002-retro-kitsch and wrap your presents in old newspaper! While we're on the subject; Christmas cards. Maybe it's a generation thing but I never saw the point of Christmas cards, you're saying 'I care about you, but not a presents worth'. Unless you're spilling your feelings out to a long-lost lover they tend to be a generic 'wishing you well'. They all get binned come January and to me always seemed like a colossal waste. Luckily they seem to be dying out with the invention of social media. Many are even opting to skip sending cards out and spend what they normally would on a charity of their choice. That's a trend I can get behind.
Get a free-range or organic turkey; this is fairly easy with the rise of sustainability demand; shop in-season veg, pay attention to the packaging your dinner ingredients come in, especially veg seems to be packed in a crazy amount of plastic, buy loose where you can (and this is also and excuse to buy the big bottle of gin) try to buy local produce, and reduce food waste by utilising leftovers wherever you can.
Anyone else's dad spend all of December turning lights off and turning the heating down while telling you to put an extra jumper on? Or what about that aunt that used to carefully peel the paper off gifts so she could iron and re-use it? The older generation got a lot right in this area, did you know turning down your thermostat down one degree could save you up to £100 a year on your heating bill? To conserve energy and reduce carbon footprint embrace your elders way (within reason!) And save yourself some cash in the process. Win-win!
There are a so many more small changes you can make at Christmas time. I always find it difficult as December is the busiest month, and we all have a million different things to do, I haven't done half my shopping and have two parties to go to this Saturday alone. All you can do is try your best, and I promise I'll try mine too.


China Tip Sheet April 19: Forget the trade war – ZTE is the real story…Xi to Pyongyang…Ant Financial…NPC agenda…industrial policy…and more!
We can't emphasize this enough – the ZTE slap down from the US is a big deal (see last two days' Tip Sheet).
What's crazy is that Xi Jinping predicted it would happen.
OK, that's not actually true. But that is effectively the message that Party media is trying to send.
On Wednesday, CCTV aired a collection of Xi's statements on technology.
That's exactly what ZTE is seen to have done.
Get smart: The ZTE case will throw the industrial policy push on semiconductors and other core technologies into hyperdrive.
For Chinese regulators, the move against ZTE is more fundamental and threatening than any number of tariffs on steel or even manufacturing exports.
China is putting its money where its mouth is. A rare joint policy release between the securities regulator and the Cybersecurity Administration aims to leverage capital markets to boost funding for industrial policy in the tech space.
The big picture: China's industrial policy is the main reason for economic tensions with the US. China will not abandon its industrial policy, meaning that there seems little hope for a quick resolution to the looming trade war.
In case you missed it, the fine folks at the IMF recently published a solid paper on China's fiscal reform agenda.
It's a great primer on the current state of play and the government's general goals for the next three years.
Get smart: Based on our reading of the policy roadmap from this year's "Two Sessions," fiscal reform is at the top of the agenda.
Go deep: If you want to know where we're at and what is coming with respect to fiscal changes, make the IMF piece your weekend read.
Markets are buzzing about the latest valuations for Ant Financial (the parent company of Alipay), which are coming in at USD 150 billion. That's up from USD 100 billion just a couple of months ago.
Why the 50% jump so quickly?
There's a lot of money staked on the company's ability to navigate a tricky regulatory landscape.
As we pointed out in the April 16Tip sheet, the head of the Party International Department Song Tao was recently in North Korea.
Get smart: Things are moving fast on the Korean peninsula, and China wants to be a part of whatever happens.
That's truer than ever after reports on Tuesday that US CIA Director (and likely next Secretary of State) Mike Pompeo secretly met with Kim over Easter weekend (see NYT link).
Our questions: How much are China and the US discussing North Korea these days? And how does the looming trade war affect those talks?
The National People's Congress Standing Committee (NPSCSC) is meeting next week from April 25-27.
Get smart: We are expecting the legislature to play a larger role in overseeing the implementation of laws, so you should pay attention to those State Council reports that the NPCSC is reviewing.
Advice for companies: Stricter enforcement of environmental laws is a given, so make sure you – and your suppliers! – are compliant.
China's demographics are changing fast: Because of the One Child Policy, the country is set to get old quickly.
And the economy is chaning fast as well: moving from one of low-skilled manufacturing to more high-skilled jobs.
Businesses pay attention: The government is going to be pouring money into worker training. We suggest you find out how to take advantage!


Named for the Japanese art of paper folding, our Origami pouf resembles the intricate yet simple shape of a pinwheel with panels that fold downward over its round shape. Origami adds an artful accent that can be used as a foot rest, floor cushion or can be tucked under a cocktail table. Have fun with this style by choosing bold-colored solids, crazy patterns or geometric stripes. With our Origami pouf, the decor takes on a spirit of its own. Available in more than 250 fabrics and select leathers, COM and COL. Shown in COM.	We specialize in the perfect home decor accents. Our ottomans are customizable and made-to-order in our family owned/operated facility in Los Angeles, CA USA.
Origami is made with a combination of down and foam fill. This is a soft-feeling pouf. If a firm feel is desired, request a quote. Our ottomans are made of kiln-dried Alder wood from sustainably harvested forests, recycled sinuous no-sag spring seating or recycled webbed seating, doweled, glued and corner-blocked joinery, high resiliency foam 2.5 pound. Can be ordered in standard sizes and most styles (not all) in custom sizes. Custom orders ship in eight to ten weeks depending on the request. All orders are made in, and ship from, Los Angeles, CA USA. Make sure to check for authenticity of your Nathan Anthony products. Each product has our law label with our logo and/or will have our name tags.