/*
| Copyright (C) 2002-2007 Jorg Schuler <jcsjcs at users sourceforge net>
| Copyright (C) 2009 Christophe Fergeau <cfergeau at mandriva com>
| Part of the gtkpod project.
|
| URL: http://www.gtkpod.org/
| URL: http://gtkpod.sourceforge.net/
|
| The code contained in this file is free software; you can redistribute
| it and/or modify it under the terms of the GNU Lesser General Public
| License as published by the Free Software Foundation; either version
| 2.1 of the License, or (at your option) any later version.
|
| This file is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
| Lesser General Public License for more details.
|
| You should have received a copy of the GNU Lesser General Public
| License along with this code; if not, write to the Free Software
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
| USA
|
| iTunes and iPod are trademarks of Apple
|
| This product is not supported/written/published by Apple!
|
| $Id$
*/
#include <config.h>
#include "itdb.h"
#include "itdb_device.h"
#include "itdb_private.h"
#include "itdb_tzinfo_data.h"
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <glib/gstdio.h>
#ifdef __CYGWIN__
extern __IMPORT long _timezone;
#endif
static gboolean parse_tzdata (const char *tzname, time_t start, time_t end,
int *offset, gboolean *has_dst, int *dst_offset);
/* Mac epoch is 1st January 1904 00:00 in local time */
G_GNUC_INTERNAL time_t device_time_mac_to_time_t (Itdb_Device *device, guint64 mactime)
{
g_return_val_if_fail (device, 0);
if (mactime != 0) return (time_t)(mactime - 2082844800 - device->timezone_shift);
else return (time_t)mactime;
}
G_GNUC_INTERNAL guint64 device_time_time_t_to_mac (Itdb_Device *device, time_t timet)
{
g_return_val_if_fail (device, 0);
if (timet != 0)
return ((guint64)timet) + 2082844800 + device->timezone_shift;
else return 0;
}
static char *
get_preferences_path (const Itdb_Device *device)
{
const gchar *p_preferences[] = {"Preferences", NULL};
char *dev_path;
char *prefs_filename;
if (device->mountpoint == NULL) {
return NULL;
}
dev_path = itdb_get_device_dir (device->mountpoint);
if (dev_path == NULL) {
return NULL;
}
prefs_filename = itdb_resolve_path (dev_path, p_preferences);
g_free (dev_path);
return prefs_filename;
}
static gboolean itdb_device_read_raw_timezone (const char *prefs_path,
glong offset,
gint16 *timezone)
{
FILE *f;
int result;
if (timezone == NULL) {
return FALSE;
}
f = fopen (prefs_path, "r");
if (f == NULL) {
return FALSE;
}
result = fseek (f, offset, SEEK_SET);
if (result != 0) {
fclose (f);
return FALSE;
}
result = fread (timezone, sizeof (*timezone), 1, f);
if (result != 1) {
fclose (f);
return FALSE;
}
fclose (f);
*timezone = GINT16_FROM_LE (*timezone);
return TRUE;
}
static gboolean raw_timezone_to_utc_shift_4g (gint16 raw_timezone,
gint *utc_shift)
{
const int GMT_OFFSET = 0x19;
if (utc_shift == NULL) {
return FALSE;
}
if ((raw_timezone < 0) || (raw_timezone > (2*12) << 1)) {
/* invalid timezone */
return FALSE;
}
raw_timezone -= GMT_OFFSET;
*utc_shift = (raw_timezone >> 1) * 3600;
if (raw_timezone & 1) {
/* Adjust for DST */
*utc_shift += 3600;
}
return TRUE;
}
static gboolean raw_timezone_to_utc_shift_5g (gint16 raw_timezone,
gint *utc_shift)
{
const int TZ_SHIFT = 8;
if (utc_shift == NULL) {
return FALSE;
}
/* The iPod stores the timezone information as a number of minutes
* from Tokyo timezone which increases when going eastward (ie
* going from Tokyo to LA and then to Europe).
* The calculation below shifts the origin so that 0 corresponds
* to UTC-12 and the max is 24*60 and corresponds to UTC+12
* Finally, we substract 12*60 to that value to get a signed number
* giving the timezone relative to UTC.
*/
*utc_shift = raw_timezone*60 - TZ_SHIFT*3600;
return TRUE;
}
static gboolean raw_timezone_to_utc_shift_6g (gint16 city_id,
gint *utc_shift)
{
const ItdbTimezone *it;
for (it = timezones; it->city_name != NULL; it++) {
if (it->city_index == city_id) {
int offset;
gboolean unused1;
int unused2;
gboolean got_tzinfo;
got_tzinfo = parse_tzdata (it->tz_name, time (NULL), time (NULL),
&offset, &unused1, &unused2);
if (!got_tzinfo) {
return FALSE;
}
*utc_shift = offset*60;
return TRUE;
}
}
/* unknown city ID */
return FALSE;
}
static gint get_local_timezone (void)
{
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
/*
* http://www.gnu.org/software/libc/manual/html_node/Time-Zone-Functions.html
*
* Variable: long int timezone
*
* This contains the difference between UTC and the latest local
* standard time, in seconds west of UTC. For example, in the
* U.S. Eastern time zone, the value is 5*60*60. Unlike the
* tm_gmtoff member of the broken-down time structure, this value is
* not adjusted for daylight saving, and its sign is reversed. In
* GNU programs it is better to use tm_gmtoff, since it contains the
* correct offset even when it is not the latest one.
*/
time_t t = time(NULL);
glong seconds_east_utc;
# ifdef HAVE_LOCALTIME_R
{
struct tm tmb;
localtime_r(&t, &tmb);
seconds_east_utc = tmb.tm_gmtoff;
}
# else /* !HAVE_LOCALTIME_R */
{
struct tm* tp;
tp = localtime(&t);
seconds_east_utc = tp->tm_gmtoff;
}
# endif /* !HAVE_LOCALTIME_R */
return seconds_east_utc; /* mimic the old behaviour when global variable 'timezone' from the 'time.h' header was returned */
#elif __CYGWIN__ /* !HAVE_STRUCT_TM_TM_GMTOFF */
return (gint) _timezone * -1; /* global variable defined by time.h, see man tzset */
#else /* !HAVE_STRUCT_TM_TM_GMTOFF && !__CYGWIN__ */
return timezone * -1; /* global variable defined by time.h, see man tzset */
#endif
}
/* This function reads the timezone information from the iPod and sets it in
* the Itdb_Device structure. If an error occurs, the function returns silently
* and the timezone shift is set to 0
*/
G_GNUC_INTERNAL void itdb_device_set_timezone_info (Itdb_Device *device)
{
gint16 raw_timezone;
gint timezone = 0;
gboolean result;
struct stat stat_buf;
int status;
char *prefs_path;
guint offset;
gboolean (*raw_timezone_converter) (gint16 raw_timezone, gint *utc_shift);
device->timezone_shift = get_local_timezone ();
prefs_path = get_preferences_path (device);
if (!prefs_path) {
return;
}
status = g_stat (prefs_path, &stat_buf);
if (status != 0) {
g_free (prefs_path);
return;
}
switch (stat_buf.st_size) {
case 2892: /* seen on iPod 4g */
offset = 0xb10;
raw_timezone_converter = raw_timezone_to_utc_shift_4g;
break;
case 2924: /* seen on iPod video */
offset = 0xb22;
raw_timezone_converter = raw_timezone_to_utc_shift_5g;
break;
case 2952: /* seen on nano 3g and iPod classic */
case 2956: /* seen on iPod classic */
case 2960: /* seen on nano 4g */
offset = 0xb70;
raw_timezone_converter = raw_timezone_to_utc_shift_6g;
break;
default:
/* We don't know how to get the timezone of this ipod model,
* assume the computer timezone and the ipod timezone match
*/
g_free (prefs_path);
return;
}
result = itdb_device_read_raw_timezone (prefs_path, offset,
&raw_timezone);
g_free (prefs_path);
if (!result) {
return;
}
result = raw_timezone_converter (raw_timezone, &timezone);
if (!result) {
return;
}
if ((timezone < -12*3600) || (timezone > 12 * 3600)) {
return;
}
device->timezone_shift = timezone;
}
/*
* The following function was copied from libgweather/gweather-timezone.c
*
* Copyright 2008, Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* libgweather uses it for a different purpose than libgpod's, namely
* finding if a given world location uses DST (not 'now' but at some point
* during the year) and getting the offset.
* libgpod only needs to know the offset from UTC, and if start == end when
* calling this function, this is exactly what we get!
*/
#define ZONEINFO_DIR "/usr/share/zoneinfo"
#define TZ_MAGIC "TZif"
#define TZ_HEADER_SIZE 44
#define TZ_TIMECNT_OFFSET 32
#define TZ_TRANSITIONS_OFFSET 44
#define TZ_TTINFO_SIZE 6
#define TZ_TTINFO_GMTOFF_OFFSET 0
#define TZ_TTINFO_ISDST_OFFSET 4
static gboolean
parse_tzdata (const char *tzname, time_t start, time_t end,
int *offset, gboolean *has_dst, int *dst_offset)
{
char *filename, *contents;
gsize length;
int timecnt, transitions_size, ttinfo_map_size;
int initial_transition = -1, second_transition = -1;
gint32 *transitions;
char *ttinfo_map, *ttinfos;
gint32 initial_offset, second_offset;
char initial_isdst, second_isdst;
int i;
filename = g_build_filename (ZONEINFO_DIR, tzname, NULL);
if (!g_file_get_contents (filename, &contents, &length, NULL)) {
g_free (filename);
return FALSE;
}
g_free (filename);
if (length < TZ_HEADER_SIZE ||
strncmp (contents, TZ_MAGIC, strlen (TZ_MAGIC)) != 0) {
g_free (contents);
return FALSE;
}
timecnt = GUINT32_FROM_BE (*(guint32 *)(contents + TZ_TIMECNT_OFFSET));
transitions = (void *)(contents + TZ_TRANSITIONS_OFFSET);
transitions_size = timecnt * sizeof (*transitions);
ttinfo_map = (void *)(contents + TZ_TRANSITIONS_OFFSET + transitions_size);
ttinfo_map_size = timecnt;
ttinfos = (void *)(ttinfo_map + ttinfo_map_size);
/* @transitions is an array of @timecnt time_t values. We need to
* find the transition into the current offset, which is the last
* transition before @start. If the following transition is before
* @end, then note that one too, since it presumably means we're
* doing DST.
*/
for (i = 1; i < timecnt && initial_transition == -1; i++) {
if (GINT32_FROM_BE (transitions[i]) > start) {
initial_transition = ttinfo_map[i - 1];
if (GINT32_FROM_BE (transitions[i]) < end)
second_transition = ttinfo_map[i];
}
}
if (initial_transition == -1) {
if (timecnt)
initial_transition = ttinfo_map[timecnt - 1];
else
initial_transition = 0;
}
/* Copy the data out of the corresponding ttinfo structs */
initial_offset = *(gint32 *)(ttinfos +
initial_transition * TZ_TTINFO_SIZE +
TZ_TTINFO_GMTOFF_OFFSET);
initial_offset = GINT32_FROM_BE (initial_offset);
initial_isdst = *(ttinfos +
initial_transition * TZ_TTINFO_SIZE +
TZ_TTINFO_ISDST_OFFSET);
if (second_transition != -1) {
second_offset = *(gint32 *)(ttinfos +
second_transition * TZ_TTINFO_SIZE +
TZ_TTINFO_GMTOFF_OFFSET);
second_offset = GINT32_FROM_BE (second_offset);
second_isdst = *(ttinfos +
second_transition * TZ_TTINFO_SIZE +
TZ_TTINFO_ISDST_OFFSET);
*has_dst = (initial_isdst != second_isdst);
} else
*has_dst = FALSE;
if (!*has_dst)
*offset = initial_offset / 60;
else {
if (initial_isdst) {
*offset = second_offset / 60;
*dst_offset = initial_offset / 60;
} else {
*offset = initial_offset / 60;
*dst_offset = second_offset / 60;
}
}
g_free (contents);
return TRUE;
}