diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 451bba39..5fbedf97 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -82,7 +82,7 @@ android { defaultConfig { minSdkVersion 8 targetSdkVersion 21 - versionCode 416 - versionName "2.3.3" + versionCode 423 + versionName "2.4.0" } } diff --git a/TMessagesProj/jni/sqlite/sqlite3.c b/TMessagesProj/jni/sqlite/sqlite3.c index 81e9c733..cae0c4ad 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.c +++ b/TMessagesProj/jni/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.8.7.4. By combining all the individual C code files into this +** version 3.8.8.1. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -43,6 +43,53 @@ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ +/* +** Include the header file used to customize the compiler options for MSVC. +** This should be done first so that it can successfully prevent spurious +** compiler warnings due to subsequent content in this file and other files +** that are included by this file. +*/ +/************** Include msvc.h in the middle of sqliteInt.h ******************/ +/************** Begin file msvc.h ********************************************/ +/* +** 2015 January 12 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains code that is specific to MSVC. +*/ +#ifndef _MSVC_H_ +#define _MSVC_H_ + +#if defined(_MSC_VER) +#pragma warning(disable : 4054) +#pragma warning(disable : 4055) +#pragma warning(disable : 4100) +#pragma warning(disable : 4127) +#pragma warning(disable : 4152) +#pragma warning(disable : 4189) +#pragma warning(disable : 4206) +#pragma warning(disable : 4210) +#pragma warning(disable : 4232) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#pragma warning(disable : 4306) +#pragma warning(disable : 4702) +#pragma warning(disable : 4706) +#endif /* defined(_MSC_VER) */ + +#endif /* _MSVC_H_ */ + +/************** End of msvc.h ************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ + /* ** These #defines should enable >2GB file support on POSIX if the ** underlying operating system supports it. If the OS lacks @@ -181,7 +228,7 @@ extern "C" { /* ** These no-op macros are used in front of interfaces to mark those ** interfaces as either deprecated or experimental. New applications -** should not use deprecated interfaces - they are support for backwards +** should not use deprecated interfaces - they are supported for backwards ** compatibility only. Application writers should be aware that ** experimental interfaces are subject to change in point releases. ** @@ -231,9 +278,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.8.7.4" -#define SQLITE_VERSION_NUMBER 3008007 -#define SQLITE_SOURCE_ID "2014-12-09 01:34:36 f66f7a17b78ba617acde90fc810107f34f1a1f2e" +#define SQLITE_VERSION "3.8.8.1" +#define SQLITE_VERSION_NUMBER 3008008 +#define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -325,7 +372,7 @@ SQLITE_API const char *sqlite3_compileoption_get(int N); ** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but ** can be fully or partially disabled using a call to [sqlite3_config()] ** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], -** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the ** sqlite3_threadsafe() function shows only the compile-time setting of ** thread safety, not any run-time changes to that setting made by ** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() @@ -1345,7 +1392,7 @@ struct sqlite3_vfs { ** ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given no the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED @@ -1628,26 +1675,28 @@ struct sqlite3_mem_methods { ** SQLITE_CONFIG_SERIALIZED configuration option. ** ** [[SQLITE_CONFIG_MALLOC]]
SQLITE_CONFIG_MALLOC
-**
^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The argument specifies +**
^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** a pointer to an instance of the [sqlite3_mem_methods] structure. +** The argument specifies ** alternative low-level memory allocation routines to be used in place of ** the memory allocation routines built into SQLite.)^ ^SQLite makes ** its own private copy of the content of the [sqlite3_mem_methods] structure ** before the [sqlite3_config()] call returns.
** ** [[SQLITE_CONFIG_GETMALLOC]]
SQLITE_CONFIG_GETMALLOC
-**
^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] +**
^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which +** is a pointer to an instance of the [sqlite3_mem_methods] structure. +** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation ** routines with a wrapper that simulations memory allocation failure or ** tracks memory usage, for example.
** ** [[SQLITE_CONFIG_MEMSTATUS]]
SQLITE_CONFIG_MEMSTATUS
-**
^This option takes single argument of type int, interpreted as a -** boolean, which enables or disables the collection of memory allocation -** statistics. ^(When memory allocation statistics are disabled, the -** following SQLite interfaces become non-operational: +**
^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** interpreted as a boolean, which enables or disables the collection of +** memory allocation statistics. ^(When memory allocation statistics are +** disabled, the following SQLite interfaces become non-operational: **
** ** [[SQLITE_CONFIG_SCRATCH]]
SQLITE_CONFIG_SCRATCH
-**
^This option specifies a static memory buffer that SQLite can use for -** scratch memory. There are three arguments: A pointer an 8-byte +**
^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer +** that SQLite can use for scratch memory. ^(There are three arguments +** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte ** aligned memory buffer from which the scratch allocations will be ** drawn, the size of each scratch allocation (sz), -** and the maximum number of scratch allocations (N). The sz -** argument must be a multiple of 16. +** and the maximum number of scratch allocations (N).)^ ** The first argument must be a pointer to an 8-byte aligned buffer ** of at least sz*N bytes of memory. -** ^SQLite will use no more than two scratch buffers per thread. So -** N should be set to twice the expected maximum number of threads. -** ^SQLite will never require a scratch buffer that is more than 6 -** times the database page size. ^If SQLite needs needs additional +** ^SQLite will not use more than one scratch buffers per thread. +** ^SQLite will never request a scratch buffer that is more than 6 +** times the database page size. +** ^If SQLite needs needs additional ** scratch memory beyond what is provided by this configuration option, then -** [sqlite3_malloc()] will be used to obtain the memory needed.
+** [sqlite3_malloc()] will be used to obtain the memory needed.

+** ^When the application provides any amount of scratch memory using +** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large +** [sqlite3_malloc|heap allocations]. +** This can help [Robson proof|prevent memory allocation failures] due to heap +** fragmentation in low-memory embedded systems. +** ** ** [[SQLITE_CONFIG_PAGECACHE]]

SQLITE_CONFIG_PAGECACHE
-**
^This option specifies a static memory buffer that SQLite can use for -** the database page cache with the default page cache implementation. +**
^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer +** that SQLite can use for the database page cache with the default page +** cache implementation. ** This configuration should not be used if an application-define page -** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option. -** There are three arguments to this option: A pointer to 8-byte aligned +** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2] +** configuration option. +** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to +** 8-byte aligned ** memory, the size of each page buffer (sz), and the number of pages (N). ** The sz argument should be the size of the largest database page -** (a power of two between 512 and 32768) plus a little extra for each -** page header. ^The page header size is 20 to 40 bytes depending on -** the host architecture. ^It is harmless, apart from the wasted memory, -** to make sz a little too large. The first -** argument should point to an allocation of at least sz*N bytes of memory. +** (a power of two between 512 and 65536) plus some extra bytes for each +** page header. ^The number of extra bytes needed by the page header +** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option +** to [sqlite3_config()]. +** ^It is harmless, apart from the wasted memory, +** for the sz parameter to be larger than necessary. The first +** argument should pointer to an 8-byte aligned block of memory that +** is at least sz*N bytes of memory, otherwise subsequent behavior is +** undefined. ** ^SQLite will use the memory provided by the first argument to satisfy its ** memory needs for the first N pages that it adds to cache. ^If additional ** page cache memory is needed beyond what is provided by this option, then -** SQLite goes to [sqlite3_malloc()] for the additional storage space. -** The pointer in the first argument must -** be aligned to an 8-byte boundary or subsequent behavior of SQLite -** will be undefined.
+** SQLite goes to [sqlite3_malloc()] for the additional storage space. ** ** [[SQLITE_CONFIG_HEAP]]
SQLITE_CONFIG_HEAP
-**
^This option specifies a static memory buffer that SQLite will use -** for all of its dynamic memory allocation needs beyond those provided -** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. -** There are three arguments: An 8-byte aligned pointer to the memory, +**
^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** that SQLite will use for all of its dynamic memory allocation needs +** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and +** [SQLITE_CONFIG_PAGECACHE]. +** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled +** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns +** [SQLITE_ERROR] if invoked otherwise. +** ^There are three arguments to SQLITE_CONFIG_HEAP: +** An 8-byte aligned pointer to the memory, ** the number of bytes in the memory buffer, and the minimum allocation size. ** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the -** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or -** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory +** memory pointer is not NULL then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. @@ -1714,11 +1777,11 @@ struct sqlite3_mem_methods { ** for the minimum allocation size are 2**5 through 2**8.
** ** [[SQLITE_CONFIG_MUTEX]]
SQLITE_CONFIG_MUTEX
-**
^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The argument specifies -** alternative low-level mutex routines to be used in place -** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the -** content of the [sqlite3_mutex_methods] structure before the call to +**
^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a +** pointer to an instance of the [sqlite3_mutex_methods] structure. +** The argument specifies alternative low-level mutex routines to be used +** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** the entire mutexing subsystem is omitted from the build and hence calls to @@ -1726,8 +1789,8 @@ struct sqlite3_mem_methods { ** return [SQLITE_ERROR].
** ** [[SQLITE_CONFIG_GETMUTEX]]
SQLITE_CONFIG_GETMUTEX
-**
^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The +**
^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which +** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The ** [sqlite3_mutex_methods] ** structure is filled with the currently defined mutex routines.)^ ** This option can be used to overload the default mutex allocation @@ -1739,25 +1802,25 @@ struct sqlite3_mem_methods { ** return [SQLITE_ERROR].
** ** [[SQLITE_CONFIG_LOOKASIDE]]
SQLITE_CONFIG_LOOKASIDE
-**
^(This option takes two arguments that determine the default -** memory allocation for the lookaside memory allocator on each -** [database connection]. The first argument is the +**
^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine +** the default size of lookaside memory on each [database connection]. +** The first argument is the ** size of each lookaside buffer slot and the second is the number of -** slots allocated to each database connection.)^ ^(This option sets the -** default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] -** verb to [sqlite3_db_config()] can be used to change the lookaside +** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE +** sets the default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** option to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^
** ** [[SQLITE_CONFIG_PCACHE2]]
SQLITE_CONFIG_PCACHE2
-**
^(This option takes a single argument which is a pointer to -** an [sqlite3_pcache_methods2] object. This object specifies the interface -** to a custom page cache implementation.)^ ^SQLite makes a copy of the -** object and uses it for page cache memory allocations.
+**
^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** a pointer to an [sqlite3_pcache_methods2] object. This object specifies +** the interface to a custom page cache implementation.)^ +** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
** ** [[SQLITE_CONFIG_GETPCACHE2]]
SQLITE_CONFIG_GETPCACHE2
-**
^(This option takes a single argument which is a pointer to an -** [sqlite3_pcache_methods2] object. SQLite copies of the current -** page cache implementation into that object.)^
+**
^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** the current page cache implementation into that object.)^
** ** [[SQLITE_CONFIG_LOG]]
SQLITE_CONFIG_LOG
**
The SQLITE_CONFIG_LOG option is used to configure the SQLite @@ -1780,10 +1843,11 @@ struct sqlite3_mem_methods { ** function must be threadsafe.
** ** [[SQLITE_CONFIG_URI]]
SQLITE_CONFIG_URI -**
^(This option takes a single argument of type int. If non-zero, then -** URI handling is globally enabled. If the parameter is zero, then URI handling -** is globally disabled.)^ ^If URI handling is globally enabled, all filenames -** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or +**
^(The SQLITE_CONFIG_URI option takes a single argument of type int. +** If non-zero, then URI handling is globally enabled. If the parameter is zero, +** then URI handling is globally disabled.)^ ^If URI handling is globally +** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()], +** [sqlite3_open16()] or ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database ** connection is opened. ^If it is globally disabled, filenames are @@ -1793,9 +1857,10 @@ struct sqlite3_mem_methods { ** [SQLITE_USE_URI] symbol defined.)^ ** ** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]]
SQLITE_CONFIG_COVERING_INDEX_SCAN -**
^This option takes a single integer argument which is interpreted as -** a boolean in order to enable or disable the use of covering indices for -** full table scans in the query optimizer. ^The default setting is determined +**
^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer +** argument which is interpreted as a boolean in order to enable or disable +** the use of covering indices for full table scans in the query optimizer. +** ^The default setting is determined ** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on" ** if that compile-time option is omitted. ** The ability to disable the use of covering indices for full table scans @@ -1835,19 +1900,39 @@ struct sqlite3_mem_methods { ** ^The default setting can be overridden by each database connection using ** either the [PRAGMA mmap_size] command, or by using the ** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size -** cannot be changed at run-time. Nor may the maximum allowed mmap size -** exceed the compile-time maximum mmap size set by the +** will be silently truncated if necessary so that it does not exceed the +** compile-time maximum mmap size set by the ** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^ ** ^If either argument to this option is negative, then that argument is ** changed to its compile-time default. ** ** [[SQLITE_CONFIG_WIN32_HEAPSIZE]] **
SQLITE_CONFIG_WIN32_HEAPSIZE -**
^This option is only available if SQLite is compiled for Windows -** with the [SQLITE_WIN32_MALLOC] pre-processor macro defined. -** SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value +**
^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is +** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro +** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value ** that specifies the maximum size of the created heap. ** +** +** [[SQLITE_CONFIG_PCACHE_HDRSZ]] +**
SQLITE_CONFIG_PCACHE_HDRSZ +**
^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which +** is a pointer to an integer and writes into that integer the number of extra +** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE]. +** The amount of extra space required can change depending on the compiler, +** target platform, and SQLite version. +** +** [[SQLITE_CONFIG_PMASZ]] +**
SQLITE_CONFIG_PMASZ +**
^The SQLITE_CONFIG_PMASZ option takes a single parameter which +** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded +** sorter to that integer. The default minimum PMA Size is set by the +** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched +** to help with sort operations when multithreaded sorting +** is enabled (using the [PRAGMA threads] command) and the amount of content +** to be sorted exceeds the page size times the minimum of the +** [PRAGMA cache_size] setting and this value. +** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ @@ -1872,6 +1957,8 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ #define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ #define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ /* ** CAPI3REF: Database Connection Configuration Options @@ -1999,47 +2086,45 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); /* ** CAPI3REF: Count The Number Of Rows Modified ** -** ^This function returns the number of database rows that were changed -** or inserted or deleted by the most recently completed SQL statement -** on the [database connection] specified by the first parameter. -** ^(Only changes that are directly specified by the [INSERT], [UPDATE], -** or [DELETE] statement are counted. Auxiliary changes caused by -** triggers or [foreign key actions] are not counted.)^ Use the -** [sqlite3_total_changes()] function to find the total number of changes -** including changes caused by triggers and foreign key actions. +** ^This function returns the number of rows modified, inserted or +** deleted by the most recently completed INSERT, UPDATE or DELETE +** statement on the database connection specified by the only parameter. +** ^Executing any other type of SQL statement does not modify the value +** returned by this function. ** -** ^Changes to a view that are simulated by an [INSTEAD OF trigger] -** are not counted. Only real table changes are counted. +** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** [foreign key actions] or [REPLACE] constraint resolution are not counted. +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real +** tables are counted. ** -** ^(A "row change" is a change to a single row of a single table -** caused by an INSERT, DELETE, or UPDATE statement. Rows that -** are changed as side effects of [REPLACE] constraint resolution, -** rollback, ABORT processing, [DROP TABLE], or by any other -** mechanisms do not count as direct row changes.)^ -** -** A "trigger context" is a scope of execution that begins and -** ends with the script of a [CREATE TRIGGER | trigger]. -** Most SQL statements are -** evaluated outside of any trigger. This is the "top level" -** trigger context. If a trigger fires from the top level, a -** new trigger context is entered for the duration of that one -** trigger. Subtriggers create subcontexts for their duration. -** -** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does -** not create a new trigger context. -** -** ^This function returns the number of direct row changes in the -** most recent INSERT, UPDATE, or DELETE statement within the same -** trigger context. -** -** ^Thus, when called from the top level, this function returns the -** number of changes in the most recent INSERT, UPDATE, or DELETE -** that also occurred at the top level. ^(Within the body of a trigger, -** the sqlite3_changes() interface can be called to find the number of -** changes in the most recently completed INSERT, UPDATE, or DELETE -** statement within the body of the same trigger. -** However, the number returned does not include changes -** caused by subtriggers since those have their own context.)^ +** Things are more complicated if the sqlite3_changes() function is +** executed while a trigger program is running. This may happen if the +** program uses the [changes() SQL function], or if some other callback +** function invokes sqlite3_changes() directly. Essentially: +** +** +** +** ^This means that if the changes() SQL function (or similar) is used +** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** returns the value as set when the calling statement began executing. +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the +** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** See also the [sqlite3_total_changes()] interface, the ** [count_changes pragma], and the [changes() SQL function]. @@ -2053,20 +2138,17 @@ SQLITE_API int sqlite3_changes(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** -** ^This function returns the number of row changes caused by [INSERT], -** [UPDATE] or [DELETE] statements since the [database connection] was opened. -** ^(The count returned by sqlite3_total_changes() includes all changes -** from all [CREATE TRIGGER | trigger] contexts and changes made by -** [foreign key actions]. However, -** the count does not include changes used to implement [REPLACE] constraints, -** do rollbacks or ABORT processing, or [DROP TABLE] processing. The -** count does not include rows of views that fire an [INSTEAD OF trigger], -** though if the INSTEAD OF trigger makes changes of its own, those changes -** are counted.)^ -** ^The sqlite3_total_changes() function counts the changes as soon as -** the statement that makes them is completed (when the statement handle -** is passed to [sqlite3_reset()] or [sqlite3_finalize()]). -** +** ^This function returns the total number of rows inserted, modified or +** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed +** since the database connection was opened, including those executed as +** part of trigger programs. ^Executing any other type of SQL statement +** does not affect the value returned by sqlite3_total_changes(). +** +** ^Changes made as part of [foreign key actions] are included in the +** count, but those made as part of REPLACE constraint resolution are +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** are not counted. +** ** See also the [sqlite3_changes()] interface, the ** [count_changes pragma], and the [total_changes() SQL function]. ** @@ -2153,6 +2235,7 @@ SQLITE_API int sqlite3_complete16(const void *sql); /* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +** KEYWORDS: {busy-handler callback} {busy handler} ** ** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X ** that might be invoked with argument P whenever @@ -2169,7 +2252,7 @@ SQLITE_API int sqlite3_complete16(const void *sql); ** ^The first argument to the busy handler is a copy of the void* pointer which ** is the third argument to sqlite3_busy_handler(). ^The second argument to ** the busy handler callback is the number of times that the busy handler has -** been invoked for the same locking event. ^If the +** been invoked previously for the same locking event. ^If the ** busy callback returns 0, then no additional attempts are made to ** access the database and [SQLITE_BUSY] is returned ** to the application. @@ -2544,13 +2627,14 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); ** applications to access the same PRNG for other purposes. ** ** ^A call to this routine stores N bytes of randomness into buffer P. -** ^If N is less than one, then P can be a NULL pointer. +** ^The P parameter can be a NULL pointer. ** ** ^If this routine has not been previously called or if the previous -** call had N less than one, then the PRNG is seeded using randomness -** obtained from the xRandomness method of the default [sqlite3_vfs] object. -** ^If the previous call to this routine had an N of 1 or more then -** the pseudo-randomness is generated +** call had N less than one or a NULL pointer for P, then the PRNG is +** seeded using randomness obtained from the xRandomness method of +** the default [sqlite3_vfs] object. +** ^If the previous call to this routine had an N of 1 or more and a +** non-NULL P then the pseudo-randomness is generated ** internally and without recourse to the [sqlite3_vfs] xRandomness ** method. */ @@ -4272,9 +4356,9 @@ SQLITE_API int sqlite3_create_function_v2( ** These constant define integer codes that represent the various ** text encodings supported by SQLite. */ -#define SQLITE_UTF8 1 -#define SQLITE_UTF16LE 2 -#define SQLITE_UTF16BE 3 +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ @@ -4623,7 +4707,8 @@ typedef void (*sqlite3_destructor_type)(void*); ** the [sqlite3_context] pointer, the results are undefined. */ SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,sqlite3_uint64,void(*)(void*)); +SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); SQLITE_API void sqlite3_result_double(sqlite3_context*, double); SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); @@ -5255,20 +5340,27 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); /* ** CAPI3REF: Extract Metadata About A Column Of A Table ** -** ^This routine returns metadata about a specific column of a specific -** database table accessible using the [database connection] handle -** passed as the first function argument. +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR and if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existance of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. ** ** ^The column is identified by the second, third and fourth parameters to -** this function. ^The second parameter is either the name of the database +** this function. ^(The second parameter is either the name of the database ** (i.e. "main", "temp", or an attached database) containing the specified -** table or NULL. ^If it is NULL, then all attached databases are searched +** table or NULL.)^ ^If it is NULL, then all attached databases are searched ** for the table using the same algorithm used by the database engine to ** resolve unqualified table references. ** ** ^The third and fourth parameters to this function are the table and column -** name of the desired column, respectively. Neither of these parameters -** may be NULL. +** name of the desired column, respectively. ** ** ^Metadata is returned by writing to the memory locations passed as the 5th ** and subsequent parameters to this function. ^Any of these arguments may be @@ -5287,16 +5379,17 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** )^ ** ** ^The memory pointed to by the character pointers returned for the -** declaration type and collation sequence is valid only until the next +** declaration type and collation sequence is valid until the next ** call to any SQLite API function. ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and an +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no -** explicitly declared [INTEGER PRIMARY KEY] column, then the output -** parameters are set as follows: +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: ** **
 **     data type: "INTEGER"
@@ -5306,13 +5399,9 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
 **     auto increment: 0
 ** 
)^ ** -** ^(This function may load one or more schemas from database files. If an -** error occurs during this process, or if the requested table or column -** cannot be found, an [error code] is returned and an error message left -** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^ -** -** ^This API is only available if the library was compiled with the -** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. */ SQLITE_API int sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ @@ -5765,26 +5854,42 @@ typedef struct sqlite3_blob sqlite3_blob; ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** )^ ** +** ^(Parameter zDb is not the filename that contains the database, but +** rather the symbolic name of the database. For attached databases, this is +** the name that appears after the AS keyword in the [ATTACH] statement. +** For the main database file, the database name is "main". For TEMP +** tables, the database name is "temp".)^ +** ** ^If the flags parameter is non-zero, then the BLOB is opened for read -** and write access. ^If it is zero, the BLOB is opened for read access. -** ^It is not possible to open a column that is part of an index or primary -** key for writing. ^If [foreign key constraints] are enabled, it is -** not possible to open a column that is part of a [child key] for writing. +** and write access. ^If the flags parameter is zero, the BLOB is opened for +** read-only access. ** -** ^Note that the database name is not the filename that contains -** the database but rather the symbolic name of the database that -** appears after the AS keyword when the database is connected using [ATTACH]. -** ^For the main database file, the database name is "main". -** ^For TEMP tables, the database name is "temp". +** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored +** in *ppBlob. Otherwise an [error code] is returned and, unless the error +** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function it returns. +** +** This function fails with SQLITE_ERROR if any of the following are true: +** +** +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** -** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written -** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set -** to be a null pointer.)^ -** ^This function sets the [database connection] error code and message -** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related -** functions. ^Note that the *ppBlob variable is always initialized in a -** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob -** regardless of the success or failure of this routine. ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects @@ -5802,13 +5907,9 @@ typedef struct sqlite3_blob sqlite3_blob; ** interface. Use the [UPDATE] SQL command to change the size of a ** blob. ** -** ^The [sqlite3_blob_open()] interface will fail for a [WITHOUT ROWID] -** table. Incremental BLOB I/O is not possible on [WITHOUT ROWID] tables. -** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function can be used, if desired, -** to create an empty, zero-filled blob in which to read or write using -** this interface. +** and the built-in [zeroblob] SQL function may be used to create a +** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. @@ -5850,24 +5951,22 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_i /* ** CAPI3REF: Close A BLOB Handle ** -** ^Closes an open [BLOB handle]. +** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed +** unconditionally. Even if this routine returns an error code, the +** handle is still closed.)^ ** -** ^Closing a BLOB shall cause the current transaction to commit -** if there are no other BLOBs, no pending prepared statements, and the -** database connection is in [autocommit mode]. -** ^If any writes were made to the BLOB, they might be held in cache -** until the close operation if they will fit. +** ^If the blob handle being closed was opened for read-write access, and if +** the database is in auto-commit mode and there are no other open read-write +** blob handles or active write statements, the current transaction is +** committed. ^If an error occurs while committing the transaction, an error +** code is returned and the transaction rolled back. ** -** ^(Closing the BLOB often forces the changes -** out to disk and so if any I/O errors occur, they will likely occur -** at the time when the BLOB is closed. Any errors that occur during -** closing are reported as a non-zero return value.)^ -** -** ^(The BLOB is closed unconditionally. Even if this routine returns -** an error code, the BLOB is still closed.)^ -** -** ^Calling this routine with a null pointer (such as would be returned -** by a failed call to [sqlite3_blob_open()]) is a harmless no-op. +** Calling this function with an argument that is not a NULL pointer or an +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to +** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function +** is passed a valid open blob handle, the values returned by the +** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); @@ -5917,21 +6016,27 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); /* ** CAPI3REF: Write Data Into A BLOB Incrementally ** -** ^This function is used to write data into an open [BLOB handle] from a -** caller-supplied buffer. ^N bytes of data are copied from the buffer Z -** into the open BLOB, starting at offset iOffset. +** ^(This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset.)^ +** +** ^(On success, sqlite3_blob_write() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), ** this function returns [SQLITE_READONLY]. ** -** ^This function may only modify the contents of the BLOB; it is +** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. ^If N is -** less than zero [SQLITE_ERROR] is returned and no data is written. -** The size of the BLOB (and hence the maximum value of N+iOffset) -** can be determined using the [sqlite3_blob_bytes()] interface. +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an ** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred @@ -5940,9 +6045,6 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** have been overwritten by the statement that expired the BLOB handle ** or by other independent statements. ** -** ^(On success, sqlite3_blob_write() returns SQLITE_OK. -** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in @@ -5995,34 +6097,34 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation -** is selected automatically at compile-time. ^(The following +** is selected automatically at compile-time. The following ** implementations are available in the SQLite core: ** ** )^ +** ** -** ^The SQLITE_MUTEX_NOOP implementation is a set of routines +** The SQLITE_MUTEX_NOOP implementation is a set of routines ** that does no real locking and is appropriate for use in -** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and +** a single-threaded application. The SQLITE_MUTEX_PTHREADS and ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** -** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor ** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex ** implementation is included with the library. In this case the ** application must supply a custom mutex implementation using the ** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function ** before calling sqlite3_initialize() or any other public sqlite3_ -** function that calls sqlite3_initialize().)^ +** function that calls sqlite3_initialize(). ** ** ^The sqlite3_mutex_alloc() routine allocates a new -** mutex and returns a pointer to it. ^If it returns NULL -** that means that a mutex could not be allocated. ^SQLite -** will unwind its stack and return an error. ^(The argument -** to sqlite3_mutex_alloc() is one of these integer constants: +** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() +** routine returns NULL if it is unable to allocate the requested +** mutex. The argument to sqlite3_mutex_alloc() must one of these +** integer constants: ** ** )^ +**
  • SQLITE_MUTEX_STATIC_APP3 +** ** ** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) ** cause sqlite3_mutex_alloc() to create @@ -6043,14 +6146,14 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. ** The mutex implementation does not need to make a distinction ** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does -** not want to. ^SQLite will only request a recursive mutex in -** cases where it really needs one. ^If a faster non-recursive mutex +** not want to. SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex ** implementation is available on the host platform, the mutex subsystem ** might return such a mutex in response to SQLITE_MUTEX_FAST. ** ** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other ** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return -** a pointer to a static preexisting mutex. ^Six static mutexes are +** a pointer to a static preexisting mutex. ^Nine static mutexes are ** used by the current version of SQLite. Future versions of SQLite ** may add additional static mutexes. Static mutexes are for internal ** use by SQLite only. Applications that use SQLite mutexes should @@ -6059,16 +6162,13 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. ^But for the static +** returns a different mutex on every call. ^For the static ** mutex types, the same mutex is returned on every call that has ** the same type number. ** ** ^The sqlite3_mutex_free() routine deallocates a previously -** allocated dynamic mutex. ^SQLite is careful to deallocate every -** dynamic mutex that it allocates. The dynamic mutexes must not be in -** use when they are deallocated. Attempting to deallocate a static -** mutex results in undefined behavior. ^SQLite never deallocates -** a static mutex. +** allocated dynamic mutex. Attempting to deallocate a static +** mutex results in undefined behavior. ** ** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. ^If another thread is already within the mutex, @@ -6076,23 +6176,21 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] ** upon successful entry. ^(Mutexes created using ** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. -** In such cases the, +** In such cases, the ** mutex must be exited an equal number of times before another thread -** can enter.)^ ^(If the same thread tries to enter any other -** kind of mutex more than once, the behavior is undefined. -** SQLite will never exhibit -** such behavior in its own use of mutexes.)^ +** can enter.)^ If the same thread tries to enter any mutex other +** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined. ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^ +** will always return SQLITE_BUSY. The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable +** behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was -** previously entered by the same thread. ^(The behavior +** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered by the -** calling thread or is not currently allocated. SQLite will -** never do either.)^ +** calling thread or is not currently allocated. ** ** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or ** sqlite3_mutex_leave() is a NULL pointer, then all three routines @@ -6113,9 +6211,9 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); ** used to allocate and use mutexes. ** ** Usually, the default mutex implementations provided by SQLite are -** sufficient, however the user has the option of substituting a custom +** sufficient, however the application has the option of substituting a custom ** implementation for specialized deployments or systems for which SQLite -** does not provide a suitable implementation. In this case, the user +** does not provide a suitable implementation. In this case, the application ** creates and populates an instance of this structure to pass ** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. ** Additionally, an instance of this structure can be used as an @@ -6156,13 +6254,13 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). ** -** The xMutexInit() method must be threadsafe. ^It must be harmless to +** The xMutexInit() method must be threadsafe. It must be harmless to ** invoke xMutexInit() multiple times within the same process and without ** intervening calls to xMutexEnd(). Second and subsequent calls to ** xMutexInit() must be no-ops. ** -** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] -** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory ** allocation for a static mutex. ^However xMutexAlloc() may use SQLite ** memory allocation for a fast or recursive mutex. ** @@ -6188,29 +6286,29 @@ struct sqlite3_mutex_methods { ** CAPI3REF: Mutex Verification Routines ** ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines -** are intended for use inside assert() statements. ^The SQLite core +** are intended for use inside assert() statements. The SQLite core ** never uses these routines except inside an assert() and applications -** are advised to follow the lead of the core. ^The SQLite core only +** are advised to follow the lead of the core. The SQLite core only ** provides implementations for these routines when it is compiled -** with the SQLITE_DEBUG flag. ^External mutex implementations +** with the SQLITE_DEBUG flag. External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** -** ^These routines should return true if the mutex in their argument +** These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** -** ^The implementation is not required to provide versions of these +** The implementation is not required to provide versions of these ** routines that actually work. If the implementation does not provide working ** versions of these routines, it should at least provide stubs that always ** return true so that one does not get spurious assertion failures. ** -** ^If the argument to sqlite3_mutex_held() is a NULL pointer then +** If the argument to sqlite3_mutex_held() is a NULL pointer then ** the routine should return 1. This seems counter-intuitive since ** clearly the mutex cannot be held if it does not exist. But ** the reason the mutex does not exist is because the build is not ** using mutexes. And we do not want the assert() containing the ** call to sqlite3_mutex_held() to fail, so a non-zero return is -** the appropriate thing to do. ^The sqlite3_mutex_notheld() +** the appropriate thing to do. The sqlite3_mutex_notheld() ** interface should also return 1 when given a NULL pointer. */ #ifndef NDEBUG @@ -6943,6 +7041,10 @@ typedef struct sqlite3_backup sqlite3_backup; ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** +** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if +** there is already a read or read-write transaction open on the +** destination database. +** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is ** returned and an error code and error message are stored in the ** destination [database connection] D. @@ -7266,12 +7368,10 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** CAPI3REF: Write-Ahead Log Commit Hook ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that -** will be invoked each time a database connection commits data to a -** [write-ahead log] (i.e. whenever a transaction is committed in -** [journal_mode | journal_mode=WAL mode]). +** is invoked each time data is committed to a database in wal mode. ** -** ^The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked @@ -7336,97 +7436,114 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); /* ** CAPI3REF: Checkpoint a database ** -** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X -** on [database connection] D to be [checkpointed]. ^If X is NULL or an -** empty string, then a checkpoint is run on all databases of -** connection D. ^If the database connection D is not in -** [WAL | write-ahead log mode] then this interface is a harmless no-op. -** ^The [sqlite3_wal_checkpoint(D,X)] interface initiates a -** [sqlite3_wal_checkpoint_v2|PASSIVE] checkpoint. -** Use the [sqlite3_wal_checkpoint_v2()] interface to get a FULL -** or RESET checkpoint. +** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to +** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ ** -** ^The [wal_checkpoint pragma] can be used to invoke this interface -** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] can be used to cause this interface to be -** run whenever the WAL reaches a certain size threshold. +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** [write-ahead log] for database X on [database connection] D to be +** transferred into the database file and for the write-ahead log to +** be reset. See the [checkpointing] documentation for addition +** information. ** -** See also: [sqlite3_wal_checkpoint_v2()] +** This interface used to be the only way to cause a checkpoint to +** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()] +** interface was added. This interface is retained for backwards +** compatibility and as a convenience for applications that need to manually +** start a callback but which do not need the full power (and corresponding +** complication) of [sqlite3_wal_checkpoint_v2()]. */ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); /* ** CAPI3REF: Checkpoint a database ** -** Run a checkpoint operation on WAL database zDb attached to database -** handle db. The specific operation is determined by the value of the -** eMode parameter: +** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint +** operation on database X of [database connection] D in mode M. Status +** information is written back into integers pointed to by L and C.)^ +** ^(The M parameter must be a valid [checkpoint mode]:)^ ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    -** Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish. Sync the db file if all frames in the log -** are checkpointed. This mode is the same as calling -** sqlite3_wal_checkpoint(). The [sqlite3_busy_handler|busy-handler callback] -** is never invoked. +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames +** in the log were checkpointed. ^The [busy-handler callback] +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** ^On the other hand, passive mode might leave the checkpoint unfinished +** if there are concurrent readers or writers. ** **
    SQLITE_CHECKPOINT_FULL
    -** This mode blocks (it invokes the +** ^This mode blocks (it invokes the ** [sqlite3_busy_handler|busy-handler callback]) until there is no ** database writer and all readers are reading from the most recent database -** snapshot. It then checkpoints all frames in the log file and syncs the -** database file. This call blocks database writers while it is running, -** but not database readers. +** snapshot. ^It then checkpoints all frames in the log file and syncs the +** database file. ^This mode blocks new database writers while it is pending, +** but new database readers are allowed to continue unimpeded. ** **
    SQLITE_CHECKPOINT_RESTART
    -** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after -** checkpointing the log file it blocks (calls the -** [sqlite3_busy_handler|busy-handler callback]) -** until all readers are reading from the database file only. This ensures -** that the next client to write to the database file restarts the log file -** from the beginning. This call blocks database writers while it is running, -** but not database readers. +** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition +** that after checkpointing the log file it blocks (calls the +** [busy-handler callback]) +** until all readers are reading from the database file only. ^This ensures +** that the next writer will restart the log file from the beginning. +** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new +** database writer attempts while it is pending, but does not impede readers. +** +**
    SQLITE_CHECKPOINT_TRUNCATE
    +** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the +** addition that it also truncates the log file to zero bytes just prior +** to a successful return. **
    ** -** If pnLog is not NULL, then *pnLog is set to the total number of frames in -** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to -** the total number of checkpointed frames (including any that were already -** checkpointed when this function is called). *pnLog and *pnCkpt may be -** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK. -** If no values are available because of an error, they are both set to -1 -** before returning to communicate this to the caller. +** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in +** the log file or to -1 if the checkpoint could not run because +** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not +** NULL,then *pnCkpt is set to the total number of checkpointed frames in the +** log file (including any that were already checkpointed before the function +** was called) or to -1 if the checkpoint could not run due to an error or +** because the database is not in WAL mode. ^Note that upon successful +** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been +** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. ** -** All calls obtain an exclusive "checkpoint" lock on the database file. If +** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If ** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a ** busy-handler configured, it will not be invoked in this case. ** -** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive -** "writer" lock on the database file. If the writer lock cannot be obtained -** immediately, and a busy-handler is configured, it is invoked and the writer -** lock retried until either the busy-handler returns 0 or the lock is -** successfully obtained. The busy-handler is also invoked while waiting for -** database readers as described above. If the busy-handler returns 0 before +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** exclusive "writer" lock on the database file. ^If the writer lock cannot be +** obtained immediately, and a busy-handler is configured, it is invoked and +** the writer lock retried until either the busy-handler returns 0 or the lock +** is successfully obtained. ^The busy-handler is also invoked while waiting for +** database readers as described above. ^If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the ** checkpoint operation proceeds from that point in the same way as ** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible -** without blocking any further. SQLITE_BUSY is returned in this case. +** without blocking any further. ^SQLITE_BUSY is returned in this case. ** -** If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. If +** ^If parameter zDb is NULL or points to a zero length string, then the +** specified operation is attempted on all WAL databases [attached] to +** [database connection] db. In this case the +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If ** an SQLITE_BUSY error is encountered when processing one or more of the ** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned to the caller. If any other +** attached databases and SQLITE_BUSY is returned at the end. ^If any other ** error occurs while processing an attached database, processing is abandoned -** and the error code returned to the caller immediately. If no error +** and the error code is returned to the caller immediately. ^If no error ** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** -** If database zDb is the name of an attached database that is not in WAL -** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If +** ^If database zDb is the name of an attached database that is not in WAL +** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If ** zDb is not NULL (or a zero length string) and is not the name of any ** attached database, SQLITE_ERROR is returned to the caller. +** +** ^Unless it returns SQLITE_MISUSE, +** the sqlite3_wal_checkpoint_v2() interface +** sets the error information that is queried by +** [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface +** from SQL. */ SQLITE_API int sqlite3_wal_checkpoint_v2( sqlite3 *db, /* Database handle */ @@ -7437,16 +7554,18 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ); /* -** CAPI3REF: Checkpoint operation parameters +** CAPI3REF: Checkpoint Mode Values +** KEYWORDS: {checkpoint mode} ** -** These constants can be used as the 3rd parameter to -** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()] -** documentation for additional information about the meaning and use of -** each of these values. +** These constants define all valid values for the "checkpoint mode" passed +** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. +** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the +** meaning of each of these checkpoint modes. */ -#define SQLITE_CHECKPOINT_PASSIVE 0 -#define SQLITE_CHECKPOINT_FULL 1 -#define SQLITE_CHECKPOINT_RESTART 2 +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* ** CAPI3REF: Virtual Table Interface Configuration @@ -7535,6 +7654,106 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); /* #define SQLITE_ABORT 4 // Also an error code */ #define SQLITE_REPLACE 5 +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +** KEYWORDS: {scanstatus options} +** +** The following constants can be used for the T parameter to the +** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a +** different metric for sqlite3_stmt_scanstatus() to return. +** +** When the value returned to V is a string, space to hold that string is +** managed by the prepared statement S and will be automatically freed when +** S is finalized. +** +**
    +** [[SQLITE_SCANSTAT_NLOOP]]
    SQLITE_SCANSTAT_NLOOP
    +**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be +** set to the total number of times that the X-th loop has run.
    +** +** [[SQLITE_SCANSTAT_NVISIT]]
    SQLITE_SCANSTAT_NVISIT
    +**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be set +** to the total number of rows examined by all iterations of the X-th loop.
    +** +** [[SQLITE_SCANSTAT_EST]]
    SQLITE_SCANSTAT_EST
    +**
    ^The "double" variable pointed to by the T parameter will be set to the +** query planner's estimate for the average number of rows output from each +** iteration of the X-th loop. If the query planner's estimates was accurate, +** then this value will approximate the quotient NVISIT/NLOOP and the +** product of this value for all prior loops with the same SELECTID will +** be the NLOOP value for the current loop. +** +** [[SQLITE_SCANSTAT_NAME]]
    SQLITE_SCANSTAT_NAME
    +**
    ^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the name of the index or table +** used for the X-th loop. +** +** [[SQLITE_SCANSTAT_EXPLAIN]]
    SQLITE_SCANSTAT_EXPLAIN
    +**
    ^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] +** description for the X-th loop. +** +** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECT
    +**
    ^The "int" variable pointed to by the T parameter will be set to the +** "select-id" for the X-th loop. The select-id identifies which query or +** subquery the loop is part of. The main query has a select-id of zero. +** The select-id is the same value as is output in the first column +** of an [EXPLAIN QUERY PLAN] query. +**
    +*/ +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_SELECTID 5 + +/* +** CAPI3REF: Prepared Statement Scan Status +** +** This interface returns information about the predicted and measured +** performance for pStmt. Advanced applications can use this +** interface to compare the predicted and the measured performance and +** issue warnings and/or rerun [ANALYZE] if discrepancies are found. +** +** Since this interface is expected to be rarely used, it is only +** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] +** compile-time option. +** +** The "iScanStatusOp" parameter determines which status information to return. +** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior +** of this interface is undefined. +** ^The requested measurement is written into a variable pointed to by +** the "pOut" parameter. +** Parameter "idx" identifies the specific loop to retrieve statistics for. +** Loops are numbered starting from zero. ^If idx is out of range - less than +** zero or greater than or equal to the total number of loops used to implement +** the statement - a non-zero value is returned and the variable that pOut +** points to is unchanged. +** +** ^Statistics might not be available for all loops in all statements. ^In cases +** where there exist loops with no available statistics, this function behaves +** as if the loop did not exist - it returns non-zero and leave the variable +** that pOut points to unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Zero Scan-Status Counters +** +** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. +** +** This API is only available if the library is built with pre-processor +** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); /* @@ -7980,10 +8199,9 @@ struct sqlite3_rtree_query_info { #endif /* -** The SQLITE_DEFAULT_MEMSTATUS macro must be defined as either 0 or 1. -** It determines whether or not the features related to -** SQLITE_CONFIG_MEMSTATUS are available by default or not. This value can -** be overridden at runtime using the sqlite3_config() API. +** EVIDENCE-OF: R-25715-37072 Memory allocation statistics are enabled by +** default unless SQLite is compiled with SQLITE_DEFAULT_MEMSTATUS=0 in +** which case memory allocation statistics are disabled by default. */ #if !defined(SQLITE_DEFAULT_MEMSTATUS) # define SQLITE_DEFAULT_MEMSTATUS 1 @@ -8613,7 +8831,7 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ ** gives a possible range of values of approximately 1.0e986 to 1e-986. ** But the allowed values are "grainy". Not every value is representable. ** For example, quantities 16 and 17 are both represented by a LogEst -** of 40. However, since LogEst quantaties are suppose to be estimates, +** of 40. However, since LogEst quantities are suppose to be estimates, ** not exact values, this imprecision is not a problem. ** ** "LogEst" is short for "Logarithmic Estimate". @@ -8949,7 +9167,7 @@ typedef struct With With; /* TODO: This definition is just included so other modules compile. It ** needs to be revisited. */ -#define SQLITE_N_BTREE_META 10 +#define SQLITE_N_BTREE_META 16 /* ** If defined as non-zero, auto-vacuum is enabled by default. Otherwise @@ -9064,6 +9282,11 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); ** For example, the free-page-count field is located at byte offset 36 of ** the database file header. The incr-vacuum-flag field is located at ** byte offset 64 (== 36+4*7). +** +** The BTREE_DATA_VERSION value is not really a value stored in the header. +** It is a read-only number computed by the pager. But we merge it with +** the header value access routines since its access pattern is the same. +** Call it a "virtual meta value". */ #define BTREE_FREE_PAGE_COUNT 0 #define BTREE_SCHEMA_VERSION 1 @@ -9074,6 +9297,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); #define BTREE_USER_VERSION 6 #define BTREE_INCR_VACUUM 7 #define BTREE_APPLICATION_ID 8 +#define BTREE_DATA_VERSION 15 /* A virtual meta-value */ /* ** Values that may be OR'd together to form the second argument of an @@ -9126,6 +9350,7 @@ SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); SQLITE_PRIVATE void sqlite3BtreeCursorHints(BtCursor *, unsigned int mask); SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt); +SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void); #ifndef NDEBUG SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*); @@ -9668,6 +9893,12 @@ SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe*,int); # define VDBE_OFFSET_LINENO(x) 0 #endif +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); +#else +# define sqlite3VdbeScanStatus(a,b,c,d,e) +#endif + #endif /************** End of vdbe.h ************************************************/ @@ -9848,6 +10079,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager); /* Functions used to query pager state and configuration. */ SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*); SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int); @@ -9864,6 +10096,8 @@ SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); /* Functions used to truncate the database file. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16); + #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) SQLITE_PRIVATE void *sqlite3PagerCodec(DbPage *); #endif @@ -10051,6 +10285,10 @@ SQLITE_PRIVATE void sqlite3PcacheStats(int*,int*,int*,int*); SQLITE_PRIVATE void sqlite3PCacheSetDefault(void); +/* Return the header size */ +SQLITE_PRIVATE int sqlite3HeaderSizePcache(void); +SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void); + #endif /* _PCACHE_H_ */ /************** End of pcache.h **********************************************/ @@ -10583,6 +10821,7 @@ struct sqlite3 { int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ u16 dbOptFlags; /* Flags to enable/disable optimizations */ + u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ @@ -10684,7 +10923,8 @@ struct sqlite3 { /* ** A macro to discover the encoding of a database. */ -#define ENC(db) ((db)->aDb[0].pSchema->enc) +#define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) +#define ENC(db) ((db)->enc) /* ** Possible values for the sqlite3.flags. @@ -10737,7 +10977,7 @@ struct sqlite3 { #define SQLITE_SubqCoroutine 0x0100 /* Evaluate subqueries as coroutines */ #define SQLITE_Transitive 0x0200 /* Transitive constraints */ #define SQLITE_OmitNoopJoin 0x0400 /* Omit unused tables in joins */ -#define SQLITE_Stat3 0x0800 /* Use the SQLITE_STAT3 table */ +#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */ #define SQLITE_AllOpts 0xffff /* All optimizations */ /* @@ -11308,7 +11548,6 @@ struct Index { u8 *aSortOrder; /* for each column: True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ Expr *pPartIdxWhere; /* WHERE clause for partial indices */ - KeyInfo *pKeyInfo; /* A KeyInfo object suitable for this index */ int tnum; /* DB Page containing root of this index */ LogEst szIdxRow; /* Estimated average row size in bytes */ u16 nKeyCol; /* Number of columns forming the key */ @@ -11319,12 +11558,14 @@ struct Index { unsigned uniqNotNull:1; /* True if UNIQUE and NOT NULL for all columns */ unsigned isResized:1; /* True if resizeIndexObject() has been called */ unsigned isCovering:1; /* True if this is a covering index */ + unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int nSample; /* Number of elements in aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */ IndexSample *aSample; /* Samples of the left-most key */ - tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this table */ + tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */ + tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */ #endif }; @@ -11522,7 +11763,7 @@ struct Expr { int iTable; /* TK_COLUMN: cursor number of table holding column ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old - ** EP_Unlikely: 1000 times likelihood */ + ** EP_Unlikely: 134217728 times likelihood */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. ** TK_VARIABLE: variable number (always >= 1). */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ @@ -11870,7 +12111,7 @@ struct Select { #define SF_HasTypeInfo 0x0020 /* FROM subqueries have Table metadata */ #define SF_Compound 0x0040 /* Part of a compound query */ #define SF_Values 0x0080 /* Synthesized from VALUES clause */ - /* 0x0100 NOT USED */ +#define SF_AllValues 0x0100 /* All terms of compound are VALUES */ #define SF_NestedFrom 0x0200 /* Part of a parenthesized FROM clause */ #define SF_MaybeConvert 0x0400 /* Need convertCompoundSelectToSubquery() */ #define SF_Recursive 0x0800 /* The recursive part of a recursive CTE */ @@ -12360,6 +12601,7 @@ struct Sqlite3Config { int nPage; /* Number of pages in pPage[] */ int mxParserStack; /* maximum depth of the parser stack */ int sharedCacheEnabled; /* true if shared-cache mode enabled */ + u32 szPma; /* Maximum Sorter PMA size */ /* The above might be initialized to non-zero. The following need to always ** initially be zero, however. */ int isInit; /* True after initialization has finished */ @@ -12415,9 +12657,11 @@ struct Walker { void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ Parse *pParse; /* Parser context. */ int walkerDepth; /* Number of subqueries */ + u8 eCode; /* A small processing code */ union { /* Extra data for callback */ NameContext *pNC; /* Naming context */ - int i; /* Integer value */ + int n; /* A counter */ + int iCur; /* A cursor number */ SrcList *pSrcList; /* FROM clause */ struct SrcCount *pSrcCount; /* Counting column references */ } u; @@ -12495,7 +12739,7 @@ SQLITE_PRIVATE int sqlite3CantopenError(int); ** the SQLITE_ENABLE_FTS4 macro to serve as an alias for SQLITE_ENABLE_FTS3. */ #if defined(SQLITE_ENABLE_FTS4) && !defined(SQLITE_ENABLE_FTS3) -# define SQLITE_ENABLE_FTS3 +# define SQLITE_ENABLE_FTS3 1 #endif /* @@ -12818,6 +13062,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*); SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); +SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); @@ -13279,7 +13524,7 @@ SQLITE_PRIVATE void sqlite3ParserTrace(FILE*, char *); #ifdef SQLITE_ENABLE_IOTRACE # define IOTRACE(A) if( sqlite3IoTrace ){ sqlite3IoTrace A; } SQLITE_PRIVATE void sqlite3VdbeIOTraceSql(Vdbe*); -SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*,...); +void (*sqlite3IoTrace)(const char*,...); #else # define IOTRACE(A) # define sqlite3VdbeIOTraceSql(X) @@ -13475,15 +13720,30 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { ** ** EVIDENCE-OF: R-38799-08373 URI filenames can be enabled or disabled ** using the SQLITE_USE_URI=1 or SQLITE_USE_URI=0 compile-time options. +** +** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally +** disabled. The default value may be changed by compiling with the +** SQLITE_USE_URI symbol defined. */ #ifndef SQLITE_USE_URI # define SQLITE_USE_URI 0 #endif +/* EVIDENCE-OF: R-38720-18127 The default setting is determined by the +** SQLITE_ALLOW_COVERING_INDEX_SCAN compile-time option, or is "on" if +** that compile-time option is omitted. +*/ #ifndef SQLITE_ALLOW_COVERING_INDEX_SCAN # define SQLITE_ALLOW_COVERING_INDEX_SCAN 1 #endif +/* The minimum PMA size is set to this value multiplied by the database +** page size in bytes. +*/ +#ifndef SQLITE_SORTER_PMASZ +# define SQLITE_SORTER_PMASZ 250 +#endif + /* ** The following singleton contains the global configuration for ** the SQLite library. @@ -13514,6 +13774,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* nPage */ 0, /* mxParserStack */ 0, /* sharedCacheEnabled */ + SQLITE_SORTER_PMASZ, /* szPma */ /* All the rest should always be initialized to zero */ 0, /* isInit */ 0, /* inProgress */ @@ -13569,8 +13830,8 @@ SQLITE_PRIVATE const Token sqlite3IntTokens[] = { ** ** IMPORTANT: Changing the pending byte to any value other than ** 0x40000000 results in an incompatible database file format! -** Changing the pending byte during operating results in undefined -** and dileterious behavior. +** Changing the pending byte during operation will result in undefined +** and incorrect behavior. */ #ifndef SQLITE_OMIT_WSD SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000; @@ -13620,88 +13881,91 @@ static const char * const azCompileOpt[] = { #define CTIMEOPT_VAL_(opt) #opt #define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) -#ifdef SQLITE_32BIT_ROWID +#if SQLITE_32BIT_ROWID "32BIT_ROWID", #endif -#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC +#if SQLITE_4_BYTE_ALIGNED_MALLOC "4_BYTE_ALIGNED_MALLOC", #endif -#ifdef SQLITE_CASE_SENSITIVE_LIKE +#if SQLITE_CASE_SENSITIVE_LIKE "CASE_SENSITIVE_LIKE", #endif -#ifdef SQLITE_CHECK_PAGES +#if SQLITE_CHECK_PAGES "CHECK_PAGES", #endif -#ifdef SQLITE_COVERAGE_TEST +#if SQLITE_COVERAGE_TEST "COVERAGE_TEST", #endif -#ifdef SQLITE_DEBUG +#if SQLITE_DEBUG "DEBUG", #endif -#ifdef SQLITE_DEFAULT_LOCKING_MODE +#if SQLITE_DEFAULT_LOCKING_MODE "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), #endif #if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc) "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), #endif -#ifdef SQLITE_DISABLE_DIRSYNC +#if SQLITE_DISABLE_DIRSYNC "DISABLE_DIRSYNC", #endif -#ifdef SQLITE_DISABLE_LFS +#if SQLITE_DISABLE_LFS "DISABLE_LFS", #endif -#ifdef SQLITE_ENABLE_ATOMIC_WRITE +#if SQLITE_ENABLE_API_ARMOR + "ENABLE_API_ARMOR", +#endif +#if SQLITE_ENABLE_ATOMIC_WRITE "ENABLE_ATOMIC_WRITE", #endif -#ifdef SQLITE_ENABLE_CEROD +#if SQLITE_ENABLE_CEROD "ENABLE_CEROD", #endif -#ifdef SQLITE_ENABLE_COLUMN_METADATA +#if SQLITE_ENABLE_COLUMN_METADATA "ENABLE_COLUMN_METADATA", #endif -#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT +#if SQLITE_ENABLE_EXPENSIVE_ASSERT "ENABLE_EXPENSIVE_ASSERT", #endif -#ifdef SQLITE_ENABLE_FTS1 +#if SQLITE_ENABLE_FTS1 "ENABLE_FTS1", #endif -#ifdef SQLITE_ENABLE_FTS2 +#if SQLITE_ENABLE_FTS2 "ENABLE_FTS2", #endif -#ifdef SQLITE_ENABLE_FTS3 +#if SQLITE_ENABLE_FTS3 "ENABLE_FTS3", #endif -#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS +#if SQLITE_ENABLE_FTS3_PARENTHESIS "ENABLE_FTS3_PARENTHESIS", #endif -#ifdef SQLITE_ENABLE_FTS4 +#if SQLITE_ENABLE_FTS4 "ENABLE_FTS4", #endif -#ifdef SQLITE_ENABLE_ICU +#if SQLITE_ENABLE_ICU "ENABLE_ICU", #endif -#ifdef SQLITE_ENABLE_IOTRACE +#if SQLITE_ENABLE_IOTRACE "ENABLE_IOTRACE", #endif -#ifdef SQLITE_ENABLE_LOAD_EXTENSION +#if SQLITE_ENABLE_LOAD_EXTENSION "ENABLE_LOAD_EXTENSION", #endif -#ifdef SQLITE_ENABLE_LOCKING_STYLE +#if SQLITE_ENABLE_LOCKING_STYLE "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), #endif -#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT +#if SQLITE_ENABLE_MEMORY_MANAGEMENT "ENABLE_MEMORY_MANAGEMENT", #endif -#ifdef SQLITE_ENABLE_MEMSYS3 +#if SQLITE_ENABLE_MEMSYS3 "ENABLE_MEMSYS3", #endif -#ifdef SQLITE_ENABLE_MEMSYS5 +#if SQLITE_ENABLE_MEMSYS5 "ENABLE_MEMSYS5", #endif -#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK +#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK "ENABLE_OVERSIZE_CELL_CHECK", #endif -#ifdef SQLITE_ENABLE_RTREE +#if SQLITE_ENABLE_RTREE "ENABLE_RTREE", #endif #if defined(SQLITE_ENABLE_STAT4) @@ -13709,31 +13973,31 @@ static const char * const azCompileOpt[] = { #elif defined(SQLITE_ENABLE_STAT3) "ENABLE_STAT3", #endif -#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY +#if SQLITE_ENABLE_UNLOCK_NOTIFY "ENABLE_UNLOCK_NOTIFY", #endif -#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT +#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT "ENABLE_UPDATE_DELETE_LIMIT", #endif -#ifdef SQLITE_HAS_CODEC +#if SQLITE_HAS_CODEC "HAS_CODEC", #endif -#ifdef SQLITE_HAVE_ISNAN +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN "HAVE_ISNAN", #endif -#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX +#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX "HOMEGROWN_RECURSIVE_MUTEX", #endif -#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS +#if SQLITE_IGNORE_AFP_LOCK_ERRORS "IGNORE_AFP_LOCK_ERRORS", #endif -#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS +#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS "IGNORE_FLOCK_LOCK_ERRORS", #endif #ifdef SQLITE_INT64_TYPE "INT64_TYPE", #endif -#ifdef SQLITE_LOCK_TRACE +#if SQLITE_LOCK_TRACE "LOCK_TRACE", #endif #if defined(SQLITE_MAX_MMAP_SIZE) && !defined(SQLITE_MAX_MMAP_SIZE_xc) @@ -13742,226 +14006,226 @@ static const char * const azCompileOpt[] = { #ifdef SQLITE_MAX_SCHEMA_RETRY "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), #endif -#ifdef SQLITE_MEMDEBUG +#if SQLITE_MEMDEBUG "MEMDEBUG", #endif -#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT "MIXED_ENDIAN_64BIT_FLOAT", #endif -#ifdef SQLITE_NO_SYNC +#if SQLITE_NO_SYNC "NO_SYNC", #endif -#ifdef SQLITE_OMIT_ALTERTABLE +#if SQLITE_OMIT_ALTERTABLE "OMIT_ALTERTABLE", #endif -#ifdef SQLITE_OMIT_ANALYZE +#if SQLITE_OMIT_ANALYZE "OMIT_ANALYZE", #endif -#ifdef SQLITE_OMIT_ATTACH +#if SQLITE_OMIT_ATTACH "OMIT_ATTACH", #endif -#ifdef SQLITE_OMIT_AUTHORIZATION +#if SQLITE_OMIT_AUTHORIZATION "OMIT_AUTHORIZATION", #endif -#ifdef SQLITE_OMIT_AUTOINCREMENT +#if SQLITE_OMIT_AUTOINCREMENT "OMIT_AUTOINCREMENT", #endif -#ifdef SQLITE_OMIT_AUTOINIT +#if SQLITE_OMIT_AUTOINIT "OMIT_AUTOINIT", #endif -#ifdef SQLITE_OMIT_AUTOMATIC_INDEX +#if SQLITE_OMIT_AUTOMATIC_INDEX "OMIT_AUTOMATIC_INDEX", #endif -#ifdef SQLITE_OMIT_AUTORESET +#if SQLITE_OMIT_AUTORESET "OMIT_AUTORESET", #endif -#ifdef SQLITE_OMIT_AUTOVACUUM +#if SQLITE_OMIT_AUTOVACUUM "OMIT_AUTOVACUUM", #endif -#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION +#if SQLITE_OMIT_BETWEEN_OPTIMIZATION "OMIT_BETWEEN_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_BLOB_LITERAL +#if SQLITE_OMIT_BLOB_LITERAL "OMIT_BLOB_LITERAL", #endif -#ifdef SQLITE_OMIT_BTREECOUNT +#if SQLITE_OMIT_BTREECOUNT "OMIT_BTREECOUNT", #endif -#ifdef SQLITE_OMIT_BUILTIN_TEST +#if SQLITE_OMIT_BUILTIN_TEST "OMIT_BUILTIN_TEST", #endif -#ifdef SQLITE_OMIT_CAST +#if SQLITE_OMIT_CAST "OMIT_CAST", #endif -#ifdef SQLITE_OMIT_CHECK +#if SQLITE_OMIT_CHECK "OMIT_CHECK", #endif -#ifdef SQLITE_OMIT_COMPLETE +#if SQLITE_OMIT_COMPLETE "OMIT_COMPLETE", #endif -#ifdef SQLITE_OMIT_COMPOUND_SELECT +#if SQLITE_OMIT_COMPOUND_SELECT "OMIT_COMPOUND_SELECT", #endif -#ifdef SQLITE_OMIT_CTE +#if SQLITE_OMIT_CTE "OMIT_CTE", #endif -#ifdef SQLITE_OMIT_DATETIME_FUNCS +#if SQLITE_OMIT_DATETIME_FUNCS "OMIT_DATETIME_FUNCS", #endif -#ifdef SQLITE_OMIT_DECLTYPE +#if SQLITE_OMIT_DECLTYPE "OMIT_DECLTYPE", #endif -#ifdef SQLITE_OMIT_DEPRECATED +#if SQLITE_OMIT_DEPRECATED "OMIT_DEPRECATED", #endif -#ifdef SQLITE_OMIT_DISKIO +#if SQLITE_OMIT_DISKIO "OMIT_DISKIO", #endif -#ifdef SQLITE_OMIT_EXPLAIN +#if SQLITE_OMIT_EXPLAIN "OMIT_EXPLAIN", #endif -#ifdef SQLITE_OMIT_FLAG_PRAGMAS +#if SQLITE_OMIT_FLAG_PRAGMAS "OMIT_FLAG_PRAGMAS", #endif -#ifdef SQLITE_OMIT_FLOATING_POINT +#if SQLITE_OMIT_FLOATING_POINT "OMIT_FLOATING_POINT", #endif -#ifdef SQLITE_OMIT_FOREIGN_KEY +#if SQLITE_OMIT_FOREIGN_KEY "OMIT_FOREIGN_KEY", #endif -#ifdef SQLITE_OMIT_GET_TABLE +#if SQLITE_OMIT_GET_TABLE "OMIT_GET_TABLE", #endif -#ifdef SQLITE_OMIT_INCRBLOB +#if SQLITE_OMIT_INCRBLOB "OMIT_INCRBLOB", #endif -#ifdef SQLITE_OMIT_INTEGRITY_CHECK +#if SQLITE_OMIT_INTEGRITY_CHECK "OMIT_INTEGRITY_CHECK", #endif -#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION +#if SQLITE_OMIT_LIKE_OPTIMIZATION "OMIT_LIKE_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_LOAD_EXTENSION +#if SQLITE_OMIT_LOAD_EXTENSION "OMIT_LOAD_EXTENSION", #endif -#ifdef SQLITE_OMIT_LOCALTIME +#if SQLITE_OMIT_LOCALTIME "OMIT_LOCALTIME", #endif -#ifdef SQLITE_OMIT_LOOKASIDE +#if SQLITE_OMIT_LOOKASIDE "OMIT_LOOKASIDE", #endif -#ifdef SQLITE_OMIT_MEMORYDB +#if SQLITE_OMIT_MEMORYDB "OMIT_MEMORYDB", #endif -#ifdef SQLITE_OMIT_OR_OPTIMIZATION +#if SQLITE_OMIT_OR_OPTIMIZATION "OMIT_OR_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_PAGER_PRAGMAS +#if SQLITE_OMIT_PAGER_PRAGMAS "OMIT_PAGER_PRAGMAS", #endif -#ifdef SQLITE_OMIT_PRAGMA +#if SQLITE_OMIT_PRAGMA "OMIT_PRAGMA", #endif -#ifdef SQLITE_OMIT_PROGRESS_CALLBACK +#if SQLITE_OMIT_PROGRESS_CALLBACK "OMIT_PROGRESS_CALLBACK", #endif -#ifdef SQLITE_OMIT_QUICKBALANCE +#if SQLITE_OMIT_QUICKBALANCE "OMIT_QUICKBALANCE", #endif -#ifdef SQLITE_OMIT_REINDEX +#if SQLITE_OMIT_REINDEX "OMIT_REINDEX", #endif -#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS +#if SQLITE_OMIT_SCHEMA_PRAGMAS "OMIT_SCHEMA_PRAGMAS", #endif -#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS +#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS "OMIT_SCHEMA_VERSION_PRAGMAS", #endif -#ifdef SQLITE_OMIT_SHARED_CACHE +#if SQLITE_OMIT_SHARED_CACHE "OMIT_SHARED_CACHE", #endif -#ifdef SQLITE_OMIT_SUBQUERY +#if SQLITE_OMIT_SUBQUERY "OMIT_SUBQUERY", #endif -#ifdef SQLITE_OMIT_TCL_VARIABLE +#if SQLITE_OMIT_TCL_VARIABLE "OMIT_TCL_VARIABLE", #endif -#ifdef SQLITE_OMIT_TEMPDB +#if SQLITE_OMIT_TEMPDB "OMIT_TEMPDB", #endif -#ifdef SQLITE_OMIT_TRACE +#if SQLITE_OMIT_TRACE "OMIT_TRACE", #endif -#ifdef SQLITE_OMIT_TRIGGER +#if SQLITE_OMIT_TRIGGER "OMIT_TRIGGER", #endif -#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION +#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION "OMIT_TRUNCATE_OPTIMIZATION", #endif -#ifdef SQLITE_OMIT_UTF16 +#if SQLITE_OMIT_UTF16 "OMIT_UTF16", #endif -#ifdef SQLITE_OMIT_VACUUM +#if SQLITE_OMIT_VACUUM "OMIT_VACUUM", #endif -#ifdef SQLITE_OMIT_VIEW +#if SQLITE_OMIT_VIEW "OMIT_VIEW", #endif -#ifdef SQLITE_OMIT_VIRTUALTABLE +#if SQLITE_OMIT_VIRTUALTABLE "OMIT_VIRTUALTABLE", #endif -#ifdef SQLITE_OMIT_WAL +#if SQLITE_OMIT_WAL "OMIT_WAL", #endif -#ifdef SQLITE_OMIT_WSD +#if SQLITE_OMIT_WSD "OMIT_WSD", #endif -#ifdef SQLITE_OMIT_XFER_OPT +#if SQLITE_OMIT_XFER_OPT "OMIT_XFER_OPT", #endif -#ifdef SQLITE_PERFORMANCE_TRACE +#if SQLITE_PERFORMANCE_TRACE "PERFORMANCE_TRACE", #endif -#ifdef SQLITE_PROXY_DEBUG +#if SQLITE_PROXY_DEBUG "PROXY_DEBUG", #endif -#ifdef SQLITE_RTREE_INT_ONLY +#if SQLITE_RTREE_INT_ONLY "RTREE_INT_ONLY", #endif -#ifdef SQLITE_SECURE_DELETE +#if SQLITE_SECURE_DELETE "SECURE_DELETE", #endif -#ifdef SQLITE_SMALL_STACK +#if SQLITE_SMALL_STACK "SMALL_STACK", #endif -#ifdef SQLITE_SOUNDEX +#if SQLITE_SOUNDEX "SOUNDEX", #endif -#ifdef SQLITE_SYSTEM_MALLOC +#if SQLITE_SYSTEM_MALLOC "SYSTEM_MALLOC", #endif -#ifdef SQLITE_TCL +#if SQLITE_TCL "TCL", #endif #if defined(SQLITE_TEMP_STORE) && !defined(SQLITE_TEMP_STORE_xc) "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), #endif -#ifdef SQLITE_TEST +#if SQLITE_TEST "TEST", #endif #if defined(SQLITE_THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), #endif -#ifdef SQLITE_USE_ALLOCA +#if SQLITE_USE_ALLOCA "USE_ALLOCA", #endif -#ifdef SQLITE_USER_AUTHENTICATION +#if SQLITE_USER_AUTHENTICATION "USER_AUTHENTICATION", #endif -#ifdef SQLITE_WIN32_MALLOC +#if SQLITE_WIN32_MALLOC "WIN32_MALLOC", #endif -#ifdef SQLITE_ZERO_MALLOC +#if SQLITE_ZERO_MALLOC "ZERO_MALLOC" #endif }; @@ -13975,6 +14239,13 @@ static const char * const azCompileOpt[] = { */ SQLITE_API int sqlite3_compileoption_used(const char *zOptName){ int i, n; + +#if SQLITE_ENABLE_API_ARMOR + if( zOptName==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7; n = sqlite3Strlen30(zOptName); @@ -14156,6 +14427,7 @@ struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ Op *aOp; /* Program instructions for parent frame */ + i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ u8 *aOnceFlag; /* Array of OP_Once flags for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ @@ -14168,7 +14440,8 @@ struct VdbeFrame { int nOnceFlag; /* Number of entries in aOnceFlag */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ - int nChange; /* Statement changes (Vdbe.nChanges) */ + int nChange; /* Statement changes (Vdbe.nChange) */ + int nDbChange; /* Value of db->nChange */ }; #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) @@ -14319,6 +14592,16 @@ struct Explain { */ typedef unsigned bft; /* Bit Field Type */ +typedef struct ScanStatus ScanStatus; +struct ScanStatus { + int addrExplain; /* OP_Explain for loop */ + int addrLoop; /* Address of "loops" counter */ + int addrVisit; /* Address of "rows visited" counter */ + int iSelectID; /* The "Select-ID" for this loop */ + LogEst nEst; /* Estimated output rows per loop */ + char *zName; /* Name of table or index */ +}; + /* ** An instance of the virtual machine. This structure contains the complete ** state of the virtual machine. @@ -14391,6 +14674,11 @@ struct Vdbe { int nOnceFlag; /* Size of array aOnceFlag[] */ u8 *aOnceFlag; /* Flags for OP_Once */ AuxData *pAuxData; /* Linked list of auxdata allocations */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + i64 *anExec; /* Number of times each op has been executed */ + int nScan; /* Entries in aScan[] */ + ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */ +#endif }; /* @@ -14580,6 +14868,9 @@ SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetF if( op<0 || op>=ArraySize(wsdStat.nowValue) ){ return SQLITE_MISUSE_BKPT; } +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCurrent==0 || pHighwater==0 ) return SQLITE_MISUSE_BKPT; +#endif *pCurrent = wsdStat.nowValue[op]; *pHighwater = wsdStat.mxValue[op]; if( resetFlag ){ @@ -14599,6 +14890,11 @@ SQLITE_API int sqlite3_db_status( int resetFlag /* Reset high-water mark if true */ ){ int rc = SQLITE_OK; /* Return code */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); switch( op ){ case SQLITE_DBSTATUS_LOOKASIDE_USED: { @@ -14777,7 +15073,7 @@ SQLITE_API int sqlite3_db_status( ** sqlite3RegisterDateTimeFunctions() found at the bottom of the file. ** All other code has file scope. ** -** SQLite processes all times and dates as Julian Day numbers. The +** SQLite processes all times and dates as julian day numbers. The ** dates and times are stored as the number of days since noon ** in Greenwich on November 24, 4714 B.C. according to the Gregorian ** calendar system. @@ -14792,7 +15088,7 @@ SQLITE_API int sqlite3_db_status( ** ** The Gregorian calendar system is used for all dates and times, ** even those that predate the Gregorian calendar. Historians usually -** use the Julian calendar for dates prior to 1582-10-15 and for some +** use the julian calendar for dates prior to 1582-10-15 and for some ** dates afterwards, depending on locale. Beware of this difference. ** ** The conversion algorithms are implemented based on descriptions @@ -15064,7 +15360,7 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ } /* -** Attempt to parse the given string into a Julian Day Number. Return +** Attempt to parse the given string into a julian day number. Return ** the number of errors. ** ** The following are acceptable forms for the input string: @@ -15172,8 +15468,9 @@ static void clearYMD_HMS_TZ(DateTime *p){ ** already, check for an MSVC build environment that provides ** localtime_s(). */ -#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE_LOCALTIME_S) && \ - defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE) +#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S \ + && defined(_MSC_VER) && defined(_CRT_INSECURE_DEPRECATE) +#undef HAVE_LOCALTIME_S #define HAVE_LOCALTIME_S 1 #endif @@ -15193,8 +15490,7 @@ static void clearYMD_HMS_TZ(DateTime *p){ */ static int osLocaltime(time_t *t, struct tm *pTm){ int rc; -#if (!defined(HAVE_LOCALTIME_R) || !HAVE_LOCALTIME_R) \ - && (!defined(HAVE_LOCALTIME_S) || !HAVE_LOCALTIME_S) +#if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S struct tm *pX; #if SQLITE_THREADSAFE>0 sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); @@ -15211,7 +15507,7 @@ static int osLocaltime(time_t *t, struct tm *pTm){ #ifndef SQLITE_OMIT_BUILTIN_TEST if( sqlite3GlobalConfig.bLocaltimeFault ) return 1; #endif -#if defined(HAVE_LOCALTIME_R) && HAVE_LOCALTIME_R +#if HAVE_LOCALTIME_R rc = localtime_r(t, pTm)==0; #else rc = localtime_s(pTm, t); @@ -15635,7 +15931,7 @@ static void dateFunc( ** %f ** fractional seconds SS.SSS ** %H hour 00-24 ** %j day of year 000-366 -** %J ** Julian day number +** %J ** julian day number ** %m month 01-12 ** %M minute 00-59 ** %s seconds since 1970-01-01 @@ -15655,8 +15951,10 @@ static void strftimeFunc( size_t i,j; char *z; sqlite3 *db; - const char *zFmt = (const char*)sqlite3_value_text(argv[0]); + const char *zFmt; char zBuf[100]; + if( argc==0 ) return; + zFmt = (const char*)sqlite3_value_text(argv[0]); if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return; db = sqlite3_context_db_handle(context); for(i=0, n=1; zFmt[i]; i++, n++){ @@ -15850,7 +16148,7 @@ static void currentTimeFunc( iT = sqlite3StmtCurrentTime(context); if( iT<=0 ) return; t = iT/1000 - 10000*(sqlite3_int64)21086676; -#ifdef HAVE_GMTIME_R +#if HAVE_GMTIME_R pTm = gmtime_r(&t, &sNow); #else sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); @@ -16260,6 +16558,10 @@ SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ int rc = sqlite3_initialize(); if( rc ) return rc; #endif +#ifdef SQLITE_ENABLE_API_ARMOR + if( pVfs==0 ) return SQLITE_MISUSE_BKPT; +#endif + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); @@ -16520,9 +16822,9 @@ static malloc_zone_t* _sqliteZone_; ** The malloc.h header file is needed for malloc_usable_size() function ** on some systems (e.g. Linux). */ -#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLOC_USABLE_SIZE) -# define SQLITE_USE_MALLOC_H -# define SQLITE_USE_MALLOC_USABLE_SIZE +#if HAVE_MALLOC_H && HAVE_MALLOC_USABLE_SIZE +# define SQLITE_USE_MALLOC_H 1 +# define SQLITE_USE_MALLOC_USABLE_SIZE 1 /* ** The MSVCRT has malloc_usable_size(), but it is called _msize(). The ** use of _msize() is automatic, but can be disabled by compiling with @@ -18617,6 +18919,7 @@ SQLITE_PRIVATE int sqlite3MutexEnd(void){ SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int id){ #ifndef SQLITE_OMIT_AUTOINIT if( id<=SQLITE_MUTEX_RECURSIVE && sqlite3_initialize() ) return 0; + if( id>SQLITE_MUTEX_RECURSIVE && sqlite3MutexInit() ) return 0; #endif return sqlite3GlobalConfig.mutex.xMutexAlloc(id); } @@ -19073,8 +19376,12 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){ break; } default: { - assert( iType-2 >= 0 ); - assert( iType-2 < ArraySize(staticMutexes) ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2<0 || iType-2>=ArraySize(staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif p = &staticMutexes[iType-2]; #if SQLITE_MUTEX_NREF p->id = iType; @@ -19755,6 +20062,12 @@ static sqlite3_mutex *winMutexAlloc(int iType){ break; } default: { +#ifdef SQLITE_ENABLE_API_ARMOR + if( iType-2<0 || iType-2>=ArraySize(winMutex_staticMutexes) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif assert( iType-2 >= 0 ); assert( iType-2 < ArraySize(winMutex_staticMutexes) ); assert( winMutex_isInit==1 ); @@ -20296,11 +20609,12 @@ SQLITE_PRIVATE void *sqlite3ScratchMalloc(int n){ #if SQLITE_THREADSAFE==0 && !defined(NDEBUG) - /* Verify that no more than two scratch allocations per thread - ** are outstanding at one time. (This is only checked in the - ** single-threaded case since checking in the multi-threaded case - ** would be much more complicated.) */ - assert( scratchAllocOut<=1 ); + /* EVIDENCE-OF: R-12970-05880 SQLite will not use more than one scratch + ** buffers per thread. + ** + ** This can only be checked in single-threaded mode. + */ + assert( scratchAllocOut==0 ); if( p ) scratchAllocOut++; #endif @@ -20750,17 +21064,6 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ ** SQLlite. */ -/* -** If the strchrnul() library function is available, then set -** HAVE_STRCHRNUL. If that routine is not available, this module -** will supply its own. The built-in version is slower than -** the glibc version so the glibc version is definitely preferred. -*/ -#if !defined(HAVE_STRCHRNUL) -# define HAVE_STRCHRNUL 0 -#endif - - /* ** Conversion types fall into various categories as defined by the ** following enumeration. @@ -20959,6 +21262,13 @@ SQLITE_PRIVATE void sqlite3VXPrintf( PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ char buf[etBUFSIZE]; /* Conversion buffer */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( ap==0 ){ + (void)SQLITE_MISUSE_BKPT; + sqlite3StrAccumReset(pAccum); + return; + } +#endif bufpt = 0; if( bFlags ){ if( (bArgList = (bFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){ @@ -21499,6 +21809,11 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ char *zOld = (p->zText==p->zBase ? 0 : p->zText); i64 szNew = p->nChar; szNew += N + 1; + if( szNew+p->nChar<=p->mxAlloc ){ + /* Force exponential buffer size growth as long as it does not overflow, + ** to avoid having to call this routine too often */ + szNew += p->nChar; + } if( szNew > p->mxAlloc ){ sqlite3StrAccumReset(p); setStrAccumError(p, STRACCUM_TOOBIG); @@ -21515,6 +21830,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ assert( p->zText!=0 || p->nChar==0 ); if( zOld==0 && p->nChar>0 ) memcpy(zNew, p->zText, p->nChar); p->zText = zNew; + p->nAlloc = sqlite3DbMallocSize(p->db, zNew); }else{ sqlite3StrAccumReset(p); setStrAccumError(p, STRACCUM_NOMEM); @@ -21684,6 +22000,13 @@ SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){ char *z; char zBase[SQLITE_PRINT_BUF_SIZE]; StrAccum acc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( zFormat==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif #ifndef SQLITE_OMIT_AUTOINIT if( sqlite3_initialize() ) return 0; #endif @@ -21726,6 +22049,13 @@ SQLITE_API char *sqlite3_mprintf(const char *zFormat, ...){ SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_list ap){ StrAccum acc; if( n<=0 ) return zBuf; +#ifdef SQLITE_ENABLE_API_ARMOR + if( zBuf==0 || zFormat==0 ) { + (void)SQLITE_MISUSE_BKPT; + if( zBuf && n>0 ) zBuf[0] = 0; + return zBuf; + } +#endif sqlite3StrAccumInit(&acc, zBuf, n, 0); acc.useMalloc = 0; sqlite3VXPrintf(&acc, 0, zFormat, ap); @@ -21917,11 +22247,19 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){ #endif #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG); - sqlite3_mutex_enter(mutex); + sqlite3_mutex *mutex; #endif - if( N<=0 ){ +#ifndef SQLITE_OMIT_AUTOINIT + if( sqlite3_initialize() ) return; +#endif + +#if SQLITE_THREADSAFE + mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_PRNG); +#endif + + sqlite3_mutex_enter(mutex); + if( N<=0 || pBuf==0 ){ wsdPrng.isInit = 0; sqlite3_mutex_leave(mutex); return; @@ -22023,6 +22361,8 @@ SQLITE_PRIVATE void sqlite3PrngRestoreState(void){ ** of multiple cores can do so, while also allowing applications to stay ** single-threaded if desired. */ +#if SQLITE_OS_WIN +#endif #if SQLITE_MAX_WORKER_THREADS>0 @@ -22809,7 +23149,7 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){ ** */ /* #include */ -#ifdef SQLITE_HAVE_ISNAN +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN # include #endif @@ -22850,7 +23190,7 @@ SQLITE_PRIVATE int sqlite3FaultSim(int iTest){ */ SQLITE_PRIVATE int sqlite3IsNaN(double x){ int rc; /* The value return */ -#if !defined(SQLITE_HAVE_ISNAN) +#if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN /* ** Systems that support the isnan() library function should probably ** make use of it by compiling with -DSQLITE_HAVE_ISNAN. But we have @@ -22880,9 +23220,9 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){ volatile double y = x; volatile double z = y; rc = (y!=z); -#else /* if defined(SQLITE_HAVE_ISNAN) */ +#else /* if HAVE_ISNAN */ rc = isnan(x); -#endif /* SQLITE_HAVE_ISNAN */ +#endif /* HAVE_ISNAN */ testcase( rc ); return rc; } @@ -23043,6 +23383,11 @@ SQLITE_PRIVATE int sqlite3Dequote(char *z){ */ SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){ register unsigned char *a, *b; + if( zLeft==0 ){ + return zRight ? -1 : 0; + }else if( zRight==0 ){ + return 1; + } a = (unsigned char *)zLeft; b = (unsigned char *)zRight; while( *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } @@ -23050,6 +23395,11 @@ SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){ } SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ register unsigned char *a, *b; + if( zLeft==0 ){ + return zRight ? -1 : 0; + }else if( zRight==0 ){ + return 1; + } a = (unsigned char *)zLeft; b = (unsigned char *)zRight; while( N-- > 0 && *a!=0 && UpperToLower[*a]==UpperToLower[*b]){ a++; b++; } @@ -28193,9 +28543,9 @@ SQLITE_API int sqlite3_fullsync_count = 0; ** We do not trust systems to provide a working fdatasync(). Some do. ** Others do no. To be safe, we will stick with the (slightly slower) ** fsync(). If you know that your system does support fdatasync() correctly, -** then simply compile with -Dfdatasync=fdatasync +** then simply compile with -Dfdatasync=fdatasync or -DHAVE_FDATASYNC */ -#if !defined(fdatasync) +#if !defined(fdatasync) && !HAVE_FDATASYNC # define fdatasync fsync #endif @@ -28516,24 +28866,28 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ }while( err==EINTR ); if( err ) return SQLITE_IOERR_WRITE; #else - /* If the OS does not have posix_fallocate(), fake it. First use - ** ftruncate() to set the file size, then write a single byte to - ** the last byte in each block within the extended region. This - ** is the same technique used by glibc to implement posix_fallocate() - ** on systems that do not have a real fallocate() system call. + /* If the OS does not have posix_fallocate(), fake it. Write a + ** single byte to the last byte in each block that falls entirely + ** within the extended region. Then, if required, a single byte + ** at offset (nSize-1), to set the size of the file correctly. + ** This is a similar technique to that used by glibc on systems + ** that do not have a real fallocate() call. */ int nBlk = buf.st_blksize; /* File-system block size */ + int nWrite = 0; /* Number of bytes written by seekAndWrite */ i64 iWrite; /* Next offset to write to */ - if( robust_ftruncate(pFile->h, nSize) ){ - pFile->lastErrno = errno; - return unixLogError(SQLITE_IOERR_TRUNCATE, "ftruncate", pFile->zPath); - } iWrite = ((buf.st_size + 2*nBlk - 1)/nBlk)*nBlk-1; - while( iWrite=buf.st_size ); + assert( (iWrite/nBlk)==((buf.st_size+nBlk-1)/nBlk) ); + assert( ((iWrite+1)%nBlk)==0 ); + for(/*no-op*/; iWrite0 +# error "Memory mapped files require support from the Windows NT kernel,\ + compile with SQLITE_MAX_MMAP_SIZE=0." +#endif + /* ** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions ** based on the sub-platform)? @@ -32711,10 +33070,11 @@ SQLITE_API int sqlite3_open_file_count = 0; /* ** Do we need to manually define the Win32 file mapping APIs for use with WAL -** mode (e.g. these APIs are available in the Windows CE SDK; however, they -** are not present in the header file)? +** mode or memory mapped files (e.g. these APIs are available in the Windows +** CE SDK; however, they are not present in the header file)? */ -#if SQLITE_WIN32_FILEMAPPING_API && !defined(SQLITE_OMIT_WAL) +#if SQLITE_WIN32_FILEMAPPING_API && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) /* ** Two of the file mapping APIs are different under WinRT. Figure out which ** set we need. @@ -32742,7 +33102,7 @@ WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); ** This file mapping API is common to both Win32 and WinRT. */ WINBASEAPI BOOL WINAPI UnmapViewOfFile(LPCVOID); -#endif /* SQLITE_WIN32_FILEMAPPING_API && !defined(SQLITE_OMIT_WAL) */ +#endif /* SQLITE_WIN32_FILEMAPPING_API */ /* ** Some Microsoft compilers lack this definition. @@ -33035,7 +33395,7 @@ static struct win_syscall { LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) #if (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ - !defined(SQLITE_OMIT_WAL)) + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, #else { "CreateFileMappingA", (SYSCALL)0, 0 }, @@ -33045,7 +33405,7 @@ static struct win_syscall { DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) #if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ - !defined(SQLITE_OMIT_WAL)) + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, #else { "CreateFileMappingW", (SYSCALL)0, 0 }, @@ -33385,7 +33745,8 @@ static struct win_syscall { LPOVERLAPPED))aSyscall[48].pCurrent) #endif -#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL)) +#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, #else { "MapViewOfFile", (SYSCALL)0, 0 }, @@ -33455,7 +33816,7 @@ static struct win_syscall { #define osUnlockFileEx ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ LPOVERLAPPED))aSyscall[58].pCurrent) -#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) +#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 { "UnmapViewOfFile", (SYSCALL)UnmapViewOfFile, 0 }, #else { "UnmapViewOfFile", (SYSCALL)0, 0 }, @@ -33518,7 +33879,7 @@ static struct win_syscall { #define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) -#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL) +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, #else { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, @@ -33582,7 +33943,7 @@ static struct win_syscall { #define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) -#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_WAL) +#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, #else { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, @@ -33744,8 +34105,8 @@ SQLITE_API int sqlite3_win32_reset_heap(){ int rc; MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */ - MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) - MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); ) + MUTEX_LOGIC( pMaster = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( pMem = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM); ) sqlite3_mutex_enter(pMaster); sqlite3_mutex_enter(pMem); winMemAssertMagic(); @@ -35020,7 +35381,7 @@ static int winRead( int amt, /* Number of bytes to read */ sqlite3_int64 offset /* Begin reading at this offset */ ){ -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) OVERLAPPED overlapped; /* The offset for ReadFile. */ #endif winFile *pFile = (winFile*)id; /* file handle */ @@ -35052,7 +35413,7 @@ static int winRead( } #endif -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) if( winSeekFile(pFile, offset) ){ OSTRACE(("READ file=%p, rc=SQLITE_FULL\n", pFile->h)); return SQLITE_FULL; @@ -35124,13 +35485,13 @@ static int winWrite( } #endif -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) rc = winSeekFile(pFile, offset); if( rc==0 ){ #else { #endif -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) OVERLAPPED overlapped; /* The offset for WriteFile. */ #endif u8 *aRem = (u8 *)pBuf; /* Data yet to be written */ @@ -35138,14 +35499,14 @@ static int winWrite( DWORD nWrite; /* Bytes written by each WriteFile() call */ DWORD lastErrno = NO_ERROR; /* Value returned by GetLastError() */ -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) memset(&overlapped, 0, sizeof(OVERLAPPED)); overlapped.Offset = (LONG)(offset & 0xffffffff); overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); #endif while( nRem>0 ){ -#if SQLITE_OS_WINCE +#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED) if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, 0) ){ #else if( !osWriteFile(pFile->h, aRem, nRem, &nWrite, &overlapped) ){ @@ -35158,7 +35519,7 @@ static int winWrite( lastErrno = osGetLastError(); break; } -#if !SQLITE_OS_WINCE +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED) offset += nWrite; overlapped.Offset = (LONG)(offset & 0xffffffff); overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff); @@ -38539,18 +38900,6 @@ struct PCache { PgHdr *pPage1; /* Reference to page 1 */ }; -/* -** Some of the assert() macros in this code are too expensive to run -** even during normal debugging. Use them only rarely on long-running -** tests. Enable the expensive asserts using the -** -DSQLITE_ENABLE_EXPENSIVE_ASSERT=1 compile-time option. -*/ -#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT -# define expensive_assert(X) assert(X) -#else -# define expensive_assert(X) -#endif - /********************************** Linked List Management ********************/ /* Allowed values for second argument to pcacheManageDirtyList() */ @@ -38704,7 +39053,8 @@ SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *pCache, int szPage){ if( pCache->szPage ){ sqlite3_pcache *pNew; pNew = sqlite3GlobalConfig.pcache2.xCreate( - szPage, pCache->szExtra + sizeof(PgHdr), pCache->bPurgeable + szPage, pCache->szExtra + ROUND8(sizeof(PgHdr)), + pCache->bPurgeable ); if( pNew==0 ) return SQLITE_NOMEM; sqlite3GlobalConfig.pcache2.xCachesize(pNew, numberOfCachePages(pCache)); @@ -39159,6 +39509,13 @@ SQLITE_PRIVATE void sqlite3PcacheShrink(PCache *pCache){ sqlite3GlobalConfig.pcache2.xShrink(pCache->pCache); } +/* +** Return the size of the header added by this middleware layer +** in the page-cache hierarchy. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); } + + #if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) /* ** For all dirty pages currently in the cache, invoke the specified @@ -39472,7 +39829,7 @@ static PgHdr1 *pcache1AllocPage(PCache1 *pCache){ pPg = 0; } #else - pPg = pcache1Alloc(sizeof(PgHdr1) + pCache->szPage + pCache->szExtra); + pPg = pcache1Alloc(ROUND8(sizeof(PgHdr1)) + pCache->szPage + pCache->szExtra); p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage]; #endif pcache1EnterMutex(pCache->pGroup); @@ -40157,6 +40514,11 @@ SQLITE_PRIVATE void sqlite3PCacheSetDefault(void){ sqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods); } +/* +** Return the size of the header on each page of this PCACHE implementation. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizePcache1(void){ return ROUND8(sizeof(PgHdr1)); } + #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT /* ** This function is called to free superfluous dynamically allocated memory @@ -41513,6 +41875,8 @@ struct Pager { u8 setMaster; /* True if a m-j name has been written to jrnl */ u8 doNotSpill; /* Do not spill the cache when non-zero */ u8 subjInMemory; /* True to use in-memory sub-journals */ + u8 bUseFetch; /* True to use xFetch() */ + u8 hasBeenUsed; /* True if any content previously read from this pager*/ Pgno dbSize; /* Number of pages in the database */ Pgno dbOrigSize; /* dbSize before the current transaction */ Pgno dbFileSize; /* Number of pages in the database file */ @@ -41530,9 +41894,9 @@ struct Pager { sqlite3_backup *pBackup; /* Pointer to list of ongoing backup processes */ PagerSavepoint *aSavepoint; /* Array of active savepoints */ int nSavepoint; /* Number of elements in aSavepoint[] */ + u32 iDataVersion; /* Changes whenever database content changes */ char dbFileVers[16]; /* Changes whenever database file changes */ - u8 bUseFetch; /* True to use xFetch() */ int nMmapOut; /* Number of mmap pages currently outstanding */ sqlite3_int64 szMmap; /* Desired maximum mmap size */ PgHdr *pMmapFreelist; /* List of free mmap page headers (pDirty) */ @@ -42548,10 +42912,19 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){ ** Discard the entire contents of the in-memory page-cache. */ static void pager_reset(Pager *pPager){ + pPager->iDataVersion++; sqlite3BackupRestart(pPager->pBackup); sqlite3PcacheClear(pPager->pPCache); } +/* +** Return the pPager->iDataVersion value +*/ +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager *pPager){ + assert( pPager->eState>PAGER_OPEN ); + return pPager->iDataVersion; +} + /* ** Free all structures in the Pager.aSavepoint[] array and set both ** Pager.aSavepoint and Pager.nSavepoint to zero. Close the sub-journal @@ -43766,7 +44139,7 @@ static int readDbPage(PgHdr *pPg, u32 iFrame){ ** ** For an encrypted database, the situation is more complex: bytes ** 24..39 of the database are white noise. But the probability of - ** white noising equaling 16 bytes of 0xff is vanishingly small so + ** white noise equaling 16 bytes of 0xff is vanishingly small so ** we should still be ok. */ memset(pPager->dbFileVers, 0xff, sizeof(pPager->dbFileVers)); @@ -44754,7 +45127,7 @@ static int pagerAcquireMapPage( PgHdr **ppPage /* OUT: Acquired page object */ ){ PgHdr *p; /* Memory mapped page to return */ - + if( pPager->pMmapFreelist ){ *ppPage = p = pPager->pMmapFreelist; pPager->pMmapFreelist = p->pDirty; @@ -45985,16 +46358,12 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ); } - if( !pPager->tempFile && ( - pPager->pBackup - || sqlite3PcachePagecount(pPager->pPCache)>0 - || USEFETCH(pPager) - )){ - /* The shared-lock has just been acquired on the database file - ** and there are already pages in the cache (from a previous - ** read or write transaction). Check to see if the database - ** has been modified. If the database has changed, flush the - ** cache. + if( !pPager->tempFile && pPager->hasBeenUsed ){ + /* The shared-lock has just been acquired then check to + ** see if the database has been modified. If the database has changed, + ** flush the cache. The pPager->hasBeenUsed flag prevents this from + ** occurring on the very first access to a file, in order to save a + ** single unnecessary sqlite3OsRead() call at the start-up. ** ** Database changes is detected by looking at 15 bytes beginning ** at offset 24 into the file. The first 4 of these 16 bytes are @@ -46159,6 +46528,7 @@ SQLITE_PRIVATE int sqlite3PagerAcquire( if( pgno==0 ){ return SQLITE_CORRUPT_BKPT; } + pPager->hasBeenUsed = 1; /* If the pager is in the error state, return an error immediately. ** Otherwise, request the page from the PCache layer. */ @@ -46308,6 +46678,7 @@ SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ assert( pgno!=0 ); assert( pPager->pPCache!=0 ); pPage = sqlite3PcacheFetch(pPager->pPCache, pgno, 0); + assert( pPage==0 || pPager->hasBeenUsed ); return sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pPage); } @@ -47174,6 +47545,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){ } PAGERTRACE(("COMMIT %d\n", PAGERID(pPager))); + pPager->iDataVersion++; rc = pager_end_transaction(pPager, pPager->setMaster, 1); return pager_error(pPager, rc); } @@ -47714,6 +48086,18 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i } #endif +/* +** The page handle passed as the first argument refers to a dirty page +** with a page number other than iNew. This function changes the page's +** page number to iNew and sets the value of the PgHdr.flags field to +** the value passed as the third parameter. +*/ +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){ + assert( pPg->pgno!=iNew ); + pPg->flags = flags; + sqlite3PcacheMove(pPg, iNew); +} + /* ** Return a pointer to the data for the specified page. */ @@ -47930,7 +48314,8 @@ SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int rc = SQLITE_OK; if( pPager->pWal ){ rc = sqlite3WalCheckpoint(pPager->pWal, eMode, - pPager->xBusyHandler, pPager->pBusyHandlerArg, + (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), + pPager->pBusyHandlerArg, pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, pnLog, pnCkpt ); @@ -48112,6 +48497,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ } #endif + #endif /* SQLITE_OMIT_DISKIO */ /************** End of pager.c ***********************************************/ @@ -49621,7 +50007,7 @@ static void walMergesort( ** Free an iterator allocated by walIteratorInit(). */ static void walIteratorFree(WalIterator *p){ - sqlite3ScratchFree(p); + sqlite3_free(p); } /* @@ -49656,7 +50042,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){ nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + iLast*sizeof(ht_slot); - p = (WalIterator *)sqlite3ScratchMalloc(nByte); + p = (WalIterator *)sqlite3_malloc(nByte); if( !p ){ return SQLITE_NOMEM; } @@ -49666,7 +50052,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){ /* Allocate temporary space used by the merge-sort routine. This block ** of memory will be freed before this function returns. */ - aTmp = (ht_slot *)sqlite3ScratchMalloc( + aTmp = (ht_slot *)sqlite3_malloc( sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) ); if( !aTmp ){ @@ -49703,7 +50089,7 @@ static int walIteratorInit(Wal *pWal, WalIterator **pp){ p->aSegment[i].aPgno = (u32 *)aPgno; } } - sqlite3ScratchFree(aTmp); + sqlite3_free(aTmp); if( rc!=SQLITE_OK ){ walIteratorFree(p); @@ -49740,6 +50126,38 @@ static int walPagesize(Wal *pWal){ return (pWal->hdr.szPage&0xfe00) + ((pWal->hdr.szPage&0x0001)<<16); } +/* +** The following is guaranteed when this function is called: +** +** a) the WRITER lock is held, +** b) the entire log file has been checkpointed, and +** c) any existing readers are reading exclusively from the database +** file - there are no readers that may attempt to read a frame from +** the log file. +** +** This function updates the shared-memory structures so that the next +** client to write to the database (which may be this one) does so by +** writing frames into the start of the log file. +** +** The value of parameter salt1 is used as the aSalt[1] value in the +** new wal-index header. It should be passed a pseudo-random value (i.e. +** one obtained from sqlite3_randomness()). +*/ +static void walRestartHdr(Wal *pWal, u32 salt1){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + int i; /* Loop counter */ + u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */ + pWal->nCkpt++; + pWal->hdr.mxFrame = 0; + sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0])); + memcpy(&pWal->hdr.aSalt[1], &salt1, 4); + walIndexWriteHdr(pWal); + pInfo->nBackfill = 0; + pInfo->aReadMark[1] = 0; + for(i=2; iaReadMark[i] = READMARK_NOT_USED; + assert( pInfo->aReadMark[0]==0 ); +} + /* ** Copy as much content as we can from the WAL back into the database file ** in response to an sqlite3_wal_checkpoint() request or the equivalent. @@ -49774,7 +50192,7 @@ static int walPagesize(Wal *pWal){ static int walCheckpoint( Wal *pWal, /* Wal connection */ int eMode, /* One of PASSIVE, FULL or RESTART */ - int (*xBusyCall)(void*), /* Function to call when busy */ + int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ int sync_flags, /* Flags for OsSync() (or 0) */ u8 *zBuf /* Temporary buffer to use */ @@ -49788,7 +50206,6 @@ static int walCheckpoint( u32 mxPage; /* Max database page to write */ int i; /* Loop counter */ volatile WalCkptInfo *pInfo; /* The checkpoint status information */ - int (*xBusy)(void*) = 0; /* Function to call when waiting for locks */ szPage = walPagesize(pWal); testcase( szPage<=32768 ); @@ -49803,7 +50220,9 @@ static int walCheckpoint( } assert( pIter ); - if( eMode!=SQLITE_CHECKPOINT_PASSIVE ) xBusy = xBusyCall; + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); /* Compute in mxSafeFrame the index of the last frame of the WAL that is ** safe to write into the database. Frames beyond mxSafeFrame might @@ -49892,19 +50311,38 @@ static int walCheckpoint( rc = SQLITE_OK; } - /* If this is an SQLITE_CHECKPOINT_RESTART operation, and the entire wal - ** file has been copied into the database file, then block until all - ** readers have finished using the wal file. This ensures that the next - ** process to write to the database restarts the wal file. + /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the + ** entire wal file has been copied into the database file, then block + ** until all readers have finished using the wal file. This ensures that + ** the next process to write to the database restarts the wal file. */ if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ assert( pWal->writeLock ); if( pInfo->nBackfillhdr.mxFrame ){ rc = SQLITE_BUSY; - }else if( eMode==SQLITE_CHECKPOINT_RESTART ){ + }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ + u32 salt1; + sqlite3_randomness(4, &salt1); assert( mxSafeFrame==pWal->hdr.mxFrame ); rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(1), WAL_NREADER-1); if( rc==SQLITE_OK ){ + if( eMode==SQLITE_CHECKPOINT_TRUNCATE ){ + /* IMPLEMENTATION-OF: R-44699-57140 This mode works the same way as + ** SQLITE_CHECKPOINT_RESTART with the addition that it also + ** truncates the log file to zero bytes just prior to a + ** successful return. + ** + ** In theory, it might be safe to do this without updating the + ** wal-index header in shared memory, as all subsequent reader or + ** writer clients should see that the entire log file has been + ** checkpointed and behave accordingly. This seems unsafe though, + ** as it would leave the system in a state where the contents of + ** the wal-index header do not match the contents of the + ** file-system. To avoid this, update the wal-index header to + ** indicate that the log file contains zero valid frames. */ + walRestartHdr(pWal, salt1); + rc = sqlite3OsTruncate(pWal->pWalFd, 0); + } walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); } } @@ -50477,7 +50915,7 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( for(iKey=walHash(pgno); aHash[iKey]; iKey=walNextHash(iKey)){ u32 iFrame = aHash[iKey] + iZero; if( iFrame<=iLast && aPgno[aHash[iKey]]==pgno ){ - /* assert( iFrame>iRead ); -- not true if there is corruption */ + assert( iFrame>iRead || CORRUPT_DB ); iRead = iFrame; } if( (nCollide--)==0 ){ @@ -50690,7 +51128,6 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ return rc; } - /* ** This function is called just before writing a set of frames to the log ** file (see sqlite3WalFrames()). It checks to see if, instead of appending @@ -50723,20 +51160,8 @@ static int walRestartLog(Wal *pWal){ ** In theory it would be Ok to update the cache of the header only ** at this point. But updating the actual wal-index header is also ** safe and means there is no special case for sqlite3WalUndo() - ** to handle if this transaction is rolled back. - */ - int i; /* Loop counter */ - u32 *aSalt = pWal->hdr.aSalt; /* Big-endian salt values */ - - pWal->nCkpt++; - pWal->hdr.mxFrame = 0; - sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0])); - aSalt[1] = salt1; - walIndexWriteHdr(pWal); - pInfo->nBackfill = 0; - pInfo->aReadMark[1] = 0; - for(i=2; iaReadMark[i] = READMARK_NOT_USED; - assert( pInfo->aReadMark[0]==0 ); + ** to handle if this transaction is rolled back. */ + walRestartHdr(pWal, salt1); walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); }else if( rc!=SQLITE_BUSY ){ return rc; @@ -51024,7 +51449,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( */ SQLITE_PRIVATE int sqlite3WalCheckpoint( Wal *pWal, /* Wal connection */ - int eMode, /* PASSIVE, FULL or RESTART */ + int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ int sync_flags, /* Flags to sync db file with (or 0) */ @@ -51036,29 +51461,42 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( int rc; /* Return code */ int isChanged = 0; /* True if a new wal-index header is loaded */ int eMode2 = eMode; /* Mode to pass to walCheckpoint() */ + int (*xBusy2)(void*) = xBusy; /* Busy handler for eMode2 */ assert( pWal->ckptLock==0 ); assert( pWal->writeLock==0 ); + /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked + ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ + assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + if( pWal->readOnly ) return SQLITE_READONLY; WALTRACE(("WAL%p: checkpoint begins\n", pWal)); + + /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive + ** "checkpoint" lock on the database file. */ rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc ){ - /* Usually this is SQLITE_BUSY meaning that another thread or process - ** is already running a checkpoint, or maybe a recovery. But it might - ** also be SQLITE_IOERR. */ + /* EVIDENCE-OF: R-10421-19736 If any other process is running a + ** checkpoint operation at the same time, the lock cannot be obtained and + ** SQLITE_BUSY is returned. + ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, + ** it will not be invoked in this case. + */ + testcase( rc==SQLITE_BUSY ); + testcase( xBusy!=0 ); return rc; } pWal->ckptLock = 1; - /* If this is a blocking-checkpoint, then obtain the write-lock as well - ** to prevent any writers from running while the checkpoint is underway. - ** This has to be done before the call to walIndexReadHdr() below. + /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and + ** TRUNCATE modes also obtain the exclusive "writer" lock on the database + ** file. ** - ** If the writer lock cannot be obtained, then a passive checkpoint is - ** run instead. Since the checkpointer is not holding the writer lock, - ** there is no point in blocking waiting for any readers. Assuming no - ** other error occurs, this function will return SQLITE_BUSY to the caller. + ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + ** immediately, and a busy-handler is configured, it is invoked and the + ** writer lock retried until either the busy-handler returns 0 or the + ** lock is successfully obtained. */ if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1); @@ -51066,6 +51504,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( pWal->writeLock = 1; }else if( rc==SQLITE_BUSY ){ eMode2 = SQLITE_CHECKPOINT_PASSIVE; + xBusy2 = 0; rc = SQLITE_OK; } } @@ -51083,7 +51522,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; }else{ - rc = walCheckpoint(pWal, eMode2, xBusy, pBusyArg, sync_flags, zBuf); + rc = walCheckpoint(pWal, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); } /* If no error occurred, set the output variables. */ @@ -51582,6 +52021,7 @@ struct Btree { u8 locked; /* True if db currently has pBt locked */ int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */ int nBackup; /* Number of backup operations reading this btree */ + u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */ Btree *pNext; /* List of other sharable Btrees from the same db */ Btree *pPrev; /* Back pointer of the same list */ #ifndef SQLITE_OMIT_SHARED_CACHE @@ -53334,6 +53774,11 @@ static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){ ** end of the page and all free space is collected into one ** big FreeBlk that occurs in between the header and cell ** pointer array and the cell content area. +** +** EVIDENCE-OF: R-44582-60138 SQLite may from time to time reorganize a +** b-tree page so that there are no freeblocks or fragment bytes, all +** unused bytes are contained in the unallocated space region, and all +** cells are packed tightly at the end of the page. */ static int defragmentPage(MemPage *pPage){ int i; /* Loop counter */ @@ -53346,6 +53791,7 @@ static int defragmentPage(MemPage *pPage){ int nCell; /* Number of cells on the page */ unsigned char *data; /* The page data */ unsigned char *temp; /* Temp area for cell content */ + unsigned char *src; /* Source of content */ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ @@ -53355,15 +53801,13 @@ static int defragmentPage(MemPage *pPage){ assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); assert( pPage->nOverflow==0 ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - data = pPage->aData; + temp = 0; + src = data = pPage->aData; hdr = pPage->hdrOffset; cellOffset = pPage->cellOffset; nCell = pPage->nCell; assert( nCell==get2byte(&data[hdr+3]) ); usableSize = pPage->pBt->usableSize; - cbrk = get2byte(&data[hdr+5]); - memcpy(&temp[cbrk], &data[cbrk], usableSize - cbrk); cbrk = usableSize; iCellFirst = cellOffset + 2*nCell; iCellLast = usableSize - 4; @@ -53382,7 +53826,7 @@ static int defragmentPage(MemPage *pPage){ } #endif assert( pc>=iCellFirst && pc<=iCellLast ); - size = cellSizePtr(pPage, &temp[pc]); + size = cellSizePtr(pPage, &src[pc]); cbrk -= size; #if defined(SQLITE_ENABLE_OVERSIZE_CELL_CHECK) if( cbrk=iCellFirst ); testcase( cbrk+size==usableSize ); testcase( pc+size==usableSize ); - memcpy(&data[cbrk], &temp[pc], size); put2byte(pAddr, cbrk); + if( temp==0 ){ + int x; + if( cbrk==pc ) continue; + temp = sqlite3PagerTempSpace(pPage->pBt->pPager); + x = get2byte(&data[hdr+5]); + memcpy(&temp[x], &data[x], (cbrk+size) - x); + src = temp; + } + memcpy(&data[cbrk], &src[pc], size); } assert( cbrk>=iCellFirst ); put2byte(&data[hdr+5], cbrk); @@ -53412,6 +53864,69 @@ static int defragmentPage(MemPage *pPage){ return SQLITE_OK; } +/* +** Search the free-list on page pPg for space to store a cell nByte bytes in +** size. If one can be found, return a pointer to the space and remove it +** from the free-list. +** +** If no suitable space can be found on the free-list, return NULL. +** +** This function may detect corruption within pPg. If corruption is +** detected then *pRc is set to SQLITE_CORRUPT and NULL is returned. +** +** If a slot of at least nByte bytes is found but cannot be used because +** there are already at least 60 fragmented bytes on the page, return NULL. +** In this case, if pbDefrag parameter is not NULL, set *pbDefrag to true. +*/ +static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc, int *pbDefrag){ + const int hdr = pPg->hdrOffset; + u8 * const aData = pPg->aData; + int iAddr; + int pc; + int usableSize = pPg->pBt->usableSize; + + for(iAddr=hdr+1; (pc = get2byte(&aData[iAddr]))>0; iAddr=pc){ + int size; /* Size of the free slot */ + /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of + ** increasing offset. */ + if( pc>usableSize-4 || pc=nByte ){ + int x = size - nByte; + testcase( x==4 ); + testcase( x==3 ); + if( x<4 ){ + /* EVIDENCE-OF: R-11498-58022 In a well-formed b-tree page, the total + ** number of bytes in fragments may not exceed 60. */ + if( aData[hdr+7]>=60 ){ + if( pbDefrag ) *pbDefrag = 1; + return 0; + } + /* Remove the slot from the free-list. Update the number of + ** fragmented bytes within the page. */ + memcpy(&aData[iAddr], &aData[pc], 2); + aData[hdr+7] += (u8)x; + }else if( size+pc > usableSize ){ + *pRc = SQLITE_CORRUPT_BKPT; + return 0; + }else{ + /* The slot remains on the free-list. Reduce its size to account + ** for the portion used by the new allocation. */ + put2byte(&aData[pc+2], x); + } + return &aData[pc + x]; + } + } + + return 0; +} + /* ** Allocate nByte bytes of space from within the B-Tree page passed ** as the first argument. Write into *pIdx the index into pPage->aData[] @@ -53429,9 +53944,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ const int hdr = pPage->hdrOffset; /* Local cache of pPage->hdrOffset */ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ int top; /* First byte of cell content area */ + int rc = SQLITE_OK; /* Integer return code */ int gap; /* First byte of gap between cell pointers and cell content */ - int rc; /* Integer return code */ - int usableSize; /* Usable size of the page */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt ); @@ -53439,20 +53953,18 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ assert( nByte>=0 ); /* Minimum cell size is 4 */ assert( pPage->nFree>=nByte ); assert( pPage->nOverflow==0 ); - usableSize = pPage->pBt->usableSize; - assert( nByte < usableSize-8 ); + assert( nByte < (int)(pPage->pBt->usableSize-8) ); assert( pPage->cellOffset == hdr + 12 - 4*pPage->leaf ); gap = pPage->cellOffset + 2*pPage->nCell; assert( gap<=65536 ); - top = get2byte(&data[hdr+5]); - if( gap>top ){ - if( top==0 ){ - top = 65536; - }else{ - return SQLITE_CORRUPT_BKPT; - } - } + /* EVIDENCE-OF: R-29356-02391 If the database uses a 65536-byte page size + ** and the reserved space is zero (the usual value for reserved space) + ** then the cell content offset of an empty page wants to be 65536. + ** However, that integer is too large to be stored in a 2-byte unsigned + ** integer, so a value of 0 is used in its place. */ + top = get2byteNotZero(&data[hdr+5]); + if( gap>top ) return SQLITE_CORRUPT_BKPT; /* If there is enough space between gap and top for one more cell pointer ** array entry offset, and if the freelist is not empty, then search the @@ -53462,33 +53974,14 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ testcase( gap+1==top ); testcase( gap==top ); if( gap+2<=top && (data[hdr+1] || data[hdr+2]) ){ - int pc, addr; - for(addr=hdr+1; (pc = get2byte(&data[addr]))>0; addr=pc){ - int size; /* Size of the free slot */ - if( pc>usableSize-4 || pc=nByte ){ - int x = size - nByte; - testcase( x==4 ); - testcase( x==3 ); - if( x<4 ){ - if( data[hdr+7]>=60 ) goto defragment_page; - /* Remove the slot from the free-list. Update the number of - ** fragmented bytes within the page. */ - memcpy(&data[addr], &data[pc], 2); - data[hdr+7] += (u8)x; - }else if( size+pc > usableSize ){ - return SQLITE_CORRUPT_BKPT; - }else{ - /* The slot remains on the free-list. Reduce its size to account - ** for the portion used by the new allocation. */ - put2byte(&data[pc+2], x); - } - *pIdx = pc + x; - return SQLITE_OK; - } + int bDefrag = 0; + u8 *pSpace = pageFindSlot(pPage, nByte, &rc, &bDefrag); + if( rc ) return rc; + if( bDefrag ) goto defragment_page; + if( pSpace ){ + assert( pSpace>=data && (pSpace - data)<65536 ); + *pIdx = (int)(pSpace - data); + return SQLITE_OK; } } @@ -53497,8 +53990,8 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ */ testcase( gap+2+nByte==top ); if( gap+2+nByte>top ){ -defragment_page: - testcase( pPage->nCell==0 ); + defragment_page: + assert( pPage->nCell>0 || CORRUPT_DB ); rc = defragmentPage(pPage); if( rc ) return rc; top = get2byteNotZero(&data[hdr+5]); @@ -53545,7 +54038,7 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ assert( pPage->pBt!=0 ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( iStart>=pPage->hdrOffset+6+pPage->childPtrSize ); - assert( iEnd <= pPage->pBt->usableSize ); + assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( iSize>=4 ); /* Minimum cell size is 4 */ assert( iStart<=iLast ); @@ -53640,18 +54133,32 @@ static int decodeFlags(MemPage *pPage, int flagByte){ pPage->childPtrSize = 4-4*pPage->leaf; pBt = pPage->pBt; if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ + /* EVIDENCE-OF: R-03640-13415 A value of 5 means the page is an interior + ** table b-tree page. */ + assert( (PTF_LEAFDATA|PTF_INTKEY)==5 ); + /* EVIDENCE-OF: R-20501-61796 A value of 13 means the page is a leaf + ** table b-tree page. */ + assert( (PTF_LEAFDATA|PTF_INTKEY|PTF_LEAF)==13 ); pPage->intKey = 1; pPage->intKeyLeaf = pPage->leaf; pPage->noPayload = !pPage->leaf; pPage->maxLocal = pBt->maxLeaf; pPage->minLocal = pBt->minLeaf; }else if( flagByte==PTF_ZERODATA ){ + /* EVIDENCE-OF: R-27225-53936 A value of 2 means the page is an interior + ** index b-tree page. */ + assert( (PTF_ZERODATA)==2 ); + /* EVIDENCE-OF: R-16571-11615 A value of 10 means the page is a leaf + ** index b-tree page. */ + assert( (PTF_ZERODATA|PTF_LEAF)==10 ); pPage->intKey = 0; pPage->intKeyLeaf = 0; pPage->noPayload = 0; pPage->maxLocal = pBt->maxLocal; pPage->minLocal = pBt->minLocal; }else{ + /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is + ** an error. */ return SQLITE_CORRUPT_BKPT; } pPage->max1bytePayload = pBt->max1bytePayload; @@ -53691,21 +54198,33 @@ static int btreeInitPage(MemPage *pPage){ hdr = pPage->hdrOffset; data = pPage->aData; + /* EVIDENCE-OF: R-28594-02890 The one-byte flag at offset 0 indicating + ** the b-tree page type. */ if( decodeFlags(pPage, data[hdr]) ) return SQLITE_CORRUPT_BKPT; assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); pPage->maskPage = (u16)(pBt->pageSize - 1); pPage->nOverflow = 0; usableSize = pBt->usableSize; - pPage->cellOffset = cellOffset = hdr + 12 - 4*pPage->leaf; + pPage->cellOffset = cellOffset = hdr + 8 + pPage->childPtrSize; pPage->aDataEnd = &data[usableSize]; pPage->aCellIdx = &data[cellOffset]; + /* EVIDENCE-OF: R-58015-48175 The two-byte integer at offset 5 designates + ** the start of the cell content area. A zero value for this integer is + ** interpreted as 65536. */ top = get2byteNotZero(&data[hdr+5]); + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ pPage->nCell = get2byte(&data[hdr+3]); if( pPage->nCell>MX_CELL(pBt) ){ /* To many cells for a single page. The page must be corrupt */ return SQLITE_CORRUPT_BKPT; } testcase( pPage->nCell==MX_CELL(pBt) ); + /* EVIDENCE-OF: R-24089-57979 If a page contains no cells (which is only + ** possible for a root page of a table that contains no rows) then the + ** offset to the cell content area will equal the page size minus the + ** bytes of reserved space. */ + assert( pPage->nCell>0 || top==usableSize || CORRUPT_DB ); /* A malformed database page might cause us to read past the end ** of page when parsing a cell. @@ -53739,13 +54258,20 @@ static int btreeInitPage(MemPage *pPage){ } #endif - /* Compute the total free space on the page */ + /* Compute the total free space on the page + ** EVIDENCE-OF: R-23588-34450 The two-byte integer at offset 1 gives the + ** start of the first freeblock on the page, or is zero if there are no + ** freeblocks. */ pc = get2byte(&data[hdr+1]); - nFree = data[hdr+7] + top; + nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ while( pc>0 ){ u16 next, size; if( pciCellLast ){ - /* Start of free block is off the page */ + /* EVIDENCE-OF: R-55530-52930 In a well-formed b-tree page, there will + ** always be at least one cell before the first freeblock. + ** + ** Or, the freeblock is off the end of the page + */ return SQLITE_CORRUPT_BKPT; } next = get2byte(&data[pc]); @@ -54151,6 +54677,9 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( #ifdef SQLITE_SECURE_DELETE pBt->btsFlags |= BTS_SECURE_DELETE; #endif + /* EVIDENCE-OF: R-51873-39618 The page size for a database file is + ** determined by the 2-byte integer located at an offset of 16 bytes from + ** the beginning of the database file. */ pBt->pageSize = (zDbHeader[16]<<8) | (zDbHeader[17]<<16); if( pBt->pageSize<512 || pBt->pageSize>SQLITE_MAX_PAGE_SIZE || ((pBt->pageSize-1)&pBt->pageSize)!=0 ){ @@ -54169,6 +54698,9 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( #endif nReserve = 0; }else{ + /* EVIDENCE-OF: R-37497-42412 The size of the reserved region is + ** determined by the one-byte unsigned integer found at an offset of 20 + ** into the database file header. */ nReserve = zDbHeader[20]; pBt->btsFlags |= BTS_PAGESIZE_FIXED; #ifndef SQLITE_OMIT_AUTOVACUUM @@ -54678,6 +55210,9 @@ static int lockBtree(BtShared *pBt){ u32 usableSize; u8 *page1 = pPage1->aData; rc = SQLITE_NOTADB; + /* EVIDENCE-OF: R-43737-39999 Every valid SQLite database file begins + ** with the following 16 bytes (in hex): 53 51 4c 69 74 65 20 66 6f 72 6d + ** 61 74 20 33 00. */ if( memcmp(page1, zMagicHeader, 16)!=0 ){ goto page1_init_failed; } @@ -54718,15 +55253,21 @@ static int lockBtree(BtShared *pBt){ } #endif - /* The maximum embedded fraction must be exactly 25%. And the minimum - ** embedded fraction must be 12.5% for both leaf-data and non-leaf-data. + /* EVIDENCE-OF: R-15465-20813 The maximum and minimum embedded payload + ** fractions and the leaf payload fraction values must be 64, 32, and 32. + ** ** The original design allowed these amounts to vary, but as of ** version 3.6.0, we require them to be fixed. */ if( memcmp(&page1[21], "\100\040\040",3)!=0 ){ goto page1_init_failed; } + /* EVIDENCE-OF: R-51873-39618 The page size for a database file is + ** determined by the 2-byte integer located at an offset of 16 bytes from + ** the beginning of the database file. */ pageSize = (page1[16]<<8) | (page1[17]<<16); + /* EVIDENCE-OF: R-25008-21688 The size of a page is a power of two + ** between 512 and 65536 inclusive. */ if( ((pageSize-1)&pageSize)!=0 || pageSize>SQLITE_MAX_PAGE_SIZE || pageSize<=256 @@ -54734,6 +55275,13 @@ static int lockBtree(BtShared *pBt){ goto page1_init_failed; } assert( (pageSize & 7)==0 ); + /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte + ** integer at offset 20 is the number of bytes of space at the end of + ** each page to reserve for extensions. + ** + ** EVIDENCE-OF: R-37497-42412 The size of the reserved region is + ** determined by the one-byte unsigned integer found at an offset of 20 + ** into the database file header. */ usableSize = pageSize - page1[20]; if( (u32)pageSize!=pBt->pageSize ){ /* After reading the first page of the database assuming a page size @@ -54754,6 +55302,9 @@ static int lockBtree(BtShared *pBt){ rc = SQLITE_CORRUPT_BKPT; goto page1_init_failed; } + /* EVIDENCE-OF: R-28312-64704 However, the usable size is not allowed to + ** be less than 480. In other words, if the page size is 512, then the + ** reserved space size cannot exceed 32. */ if( usableSize<480 ){ goto page1_init_failed; } @@ -55634,6 +56185,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ sqlite3BtreeLeave(p); return rc; } + p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */ pBt->inTransaction = TRANS_READ; btreeClearHasContent(pBt); } @@ -55997,7 +56549,7 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){ releasePage(pCur->apPage[i]); } unlockBtreeIfUnused(pBt); - sqlite3DbFree(pBtree->db, pCur->aOverflow); + sqlite3_free(pCur->aOverflow); /* sqlite3_free(pCur); */ sqlite3BtreeLeave(pBtree); } @@ -56292,6 +56844,7 @@ static int accessPayload( offset -= pCur->info.nLocal; } + if( rc==SQLITE_OK && amt>0 ){ const u32 ovflSize = pBt->usableSize - 4; /* Bytes content per ovfl page */ Pgno nextPage; @@ -56309,8 +56862,8 @@ static int accessPayload( if( eOp!=2 && (pCur->curFlags & BTCF_ValidOvfl)==0 ){ int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; if( nOvfl>pCur->nOvflAlloc ){ - Pgno *aNew = (Pgno*)sqlite3DbRealloc( - pCur->pBtree->db, pCur->aOverflow, nOvfl*2*sizeof(Pgno) + Pgno *aNew = (Pgno*)sqlite3Realloc( + pCur->aOverflow, nOvfl*2*sizeof(Pgno) ); if( aNew==0 ){ rc = SQLITE_NOMEM; @@ -56357,6 +56910,7 @@ static int accessPayload( */ assert( eOp!=2 ); assert( pCur->curFlags & BTCF_ValidOvfl ); + assert( pCur->pBtree->db==pBt->db ); if( pCur->aOverflow[iIdx+1] ){ nextPage = pCur->aOverflow[iIdx+1]; }else{ @@ -57331,6 +57885,8 @@ static int allocateBtreePage( assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) ); pPage1 = pBt->pPage1; mxPage = btreePagecount(pBt); + /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36 + ** stores stores the total number of pages on the freelist. */ n = get4byte(&pPage1->aData[36]); testcase( n==mxPage-1 ); if( n>=mxPage ){ @@ -57377,8 +57933,14 @@ static int allocateBtreePage( do { pPrevTrunk = pTrunk; if( pPrevTrunk ){ + /* EVIDENCE-OF: R-01506-11053 The first integer on a freelist trunk page + ** is the page number of the next freelist trunk page in the list or + ** zero if this is the last freelist trunk page. */ iTrunk = get4byte(&pPrevTrunk->aData[0]); }else{ + /* EVIDENCE-OF: R-59841-13798 The 4-byte big-endian integer at offset 32 + ** stores the page number of the first page of the freelist, or zero if + ** the freelist is empty. */ iTrunk = get4byte(&pPage1->aData[32]); } testcase( iTrunk==mxPage ); @@ -57393,8 +57955,9 @@ static int allocateBtreePage( } assert( pTrunk!=0 ); assert( pTrunk->aData!=0 ); - - k = get4byte(&pTrunk->aData[4]); /* # of leaves on this trunk page */ + /* EVIDENCE-OF: R-13523-04394 The second integer on a freelist trunk page + ** is the number of leaf page pointers to follow. */ + k = get4byte(&pTrunk->aData[4]); if( k==0 && !searchList ){ /* The trunk has no leaves and the list is not being searched. ** So extract the trunk page itself and use it as the newly @@ -57712,6 +58275,11 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ ** for now. At some point in the future (once everyone has upgraded ** to 3.6.0 or later) we should consider fixing the conditional above ** to read "usableSize/4-2" instead of "usableSize/4-8". + ** + ** EVIDENCE-OF: R-19920-11576 However, newer versions of SQLite still + ** avoid using the last six entries in the freelist trunk page array in + ** order that database files created by newer versions of SQLite can be + ** read by older versions of SQLite. */ rc = sqlite3PagerWrite(pTrunk->pDbPage); if( rc==SQLITE_OK ){ @@ -58063,9 +58631,17 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ return; } pPage->nCell--; - memmove(ptr, ptr+2, 2*(pPage->nCell - idx)); - put2byte(&data[hdr+3], pPage->nCell); - pPage->nFree += 2; + if( pPage->nCell==0 ){ + memset(&data[hdr+1], 0, 4); + data[hdr+7] = 0; + put2byte(&data[hdr+5], pPage->pBt->usableSize); + pPage->nFree = pPage->pBt->usableSize - pPage->hdrOffset + - pPage->childPtrSize - 8; + }else{ + memmove(ptr, ptr+2, 2*(pPage->nCell - idx)); + put2byte(&data[hdr+3], pPage->nCell); + pPage->nFree += 2; + } } /* @@ -58160,45 +58736,271 @@ static void insertCell( } /* -** Add a list of cells to a page. The page should be initially empty. -** The cells are guaranteed to fit on the page. +** Array apCell[] contains pointers to nCell b-tree page cells. The +** szCell[] array contains the size in bytes of each cell. This function +** replaces the current contents of page pPg with the contents of the cell +** array. +** +** Some of the cells in apCell[] may currently be stored in pPg. This +** function works around problems caused by this by making a copy of any +** such cells before overwriting the page data. +** +** The MemPage.nFree field is invalidated by this function. It is the +** responsibility of the caller to set it correctly. */ -static void assemblePage( - MemPage *pPage, /* The page to be assembled */ - int nCell, /* The number of cells to add to this page */ - u8 **apCell, /* Pointers to cell bodies */ - u16 *aSize /* Sizes of the cells */ +static void rebuildPage( + MemPage *pPg, /* Edit this page */ + int nCell, /* Final number of cells on page */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ ){ - int i; /* Loop counter */ - u8 *pCellptr; /* Address of next cell pointer */ - int cellbody; /* Address of next cell body */ - u8 * const data = pPage->aData; /* Pointer to data for pPage */ - const int hdr = pPage->hdrOffset; /* Offset of header on pPage */ - const int nUsable = pPage->pBt->usableSize; /* Usable size of page */ + const int hdr = pPg->hdrOffset; /* Offset of header on pPg */ + u8 * const aData = pPg->aData; /* Pointer to data for pPg */ + const int usableSize = pPg->pBt->usableSize; + u8 * const pEnd = &aData[usableSize]; + int i; + u8 *pCellptr = pPg->aCellIdx; + u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager); + u8 *pData; - assert( pPage->nOverflow==0 ); - assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - assert( nCell>=0 && nCell<=(int)MX_CELL(pPage->pBt) - && (int)MX_CELL(pPage->pBt)<=10921); - assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + i = get2byte(&aData[hdr+5]); + memcpy(&pTmp[i], &aData[i], usableSize - i); - /* Check that the page has just been zeroed by zeroPage() */ - assert( pPage->nCell==0 ); - assert( get2byteNotZero(&data[hdr+5])==nUsable ); - - pCellptr = &pPage->aCellIdx[nCell*2]; - cellbody = nUsable; - for(i=nCell-1; i>=0; i--){ - u16 sz = aSize[i]; - pCellptr -= 2; - cellbody -= sz; - put2byte(pCellptr, cellbody); - memcpy(&data[cellbody], apCell[i], sz); + pData = pEnd; + for(i=0; iaData && pCellnFree -= (nCell*2 + nUsable - cellbody); - pPage->nCell = (u16)nCell; + + /* The pPg->nFree field is now set incorrectly. The caller will fix it. */ + pPg->nCell = nCell; + pPg->nOverflow = 0; + + put2byte(&aData[hdr+1], 0); + put2byte(&aData[hdr+3], pPg->nCell); + put2byte(&aData[hdr+5], pData - aData); + aData[hdr+7] = 0x00; +} + +/* +** Array apCell[] contains nCell pointers to b-tree cells. Array szCell +** contains the size in bytes of each such cell. This function attempts to +** add the cells stored in the array to page pPg. If it cannot (because +** the page needs to be defragmented before the cells will fit), non-zero +** is returned. Otherwise, if the cells are added successfully, zero is +** returned. +** +** Argument pCellptr points to the first entry in the cell-pointer array +** (part of page pPg) to populate. After cell apCell[0] is written to the +** page body, a 16-bit offset is written to pCellptr. And so on, for each +** cell in the array. It is the responsibility of the caller to ensure +** that it is safe to overwrite this part of the cell-pointer array. +** +** When this function is called, *ppData points to the start of the +** content area on page pPg. If the size of the content area is extended, +** *ppData is updated to point to the new start of the content area +** before returning. +** +** Finally, argument pBegin points to the byte immediately following the +** end of the space required by this page for the cell-pointer area (for +** all cells - not just those inserted by the current call). If the content +** area must be extended to before this point in order to accomodate all +** cells in apCell[], then the cells do not fit and non-zero is returned. +*/ +static int pageInsertArray( + MemPage *pPg, /* Page to add cells to */ + u8 *pBegin, /* End of cell-pointer array */ + u8 **ppData, /* IN/OUT: Page content -area pointer */ + u8 *pCellptr, /* Pointer to cell-pointer area */ + int nCell, /* Number of cells to add to pPg */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ +){ + int i; + u8 *aData = pPg->aData; + u8 *pData = *ppData; + const int bFreelist = aData[1] || aData[2]; + assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */ + for(i=0; iaData; + u8 * const pEnd = &aData[pPg->pBt->usableSize]; + u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; + int nRet = 0; + int i; + u8 *pFree = 0; + int szFree = 0; + + for(i=0; i=pStart && pCellaData && (pFree - aData)<65536 ); + freeSpace(pPg, (u16)(pFree - aData), szFree); + } + pFree = pCell; + szFree = sz; + if( pFree+sz>pEnd ) return 0; + }else{ + pFree = pCell; + szFree += sz; + } + nRet++; + } + } + if( pFree ){ + assert( pFree>aData && (pFree - aData)<65536 ); + freeSpace(pPg, (u16)(pFree - aData), szFree); + } + return nRet; +} + +/* +** apCell[] and szCell[] contains pointers to and sizes of all cells in the +** pages being balanced. The current page, pPg, has pPg->nCell cells starting +** with apCell[iOld]. After balancing, this page should hold nNew cells +** starting at apCell[iNew]. +** +** This routine makes the necessary adjustments to pPg so that it contains +** the correct cells after being balanced. +** +** The pPg->nFree field is invalid when this function returns. It is the +** responsibility of the caller to set it correctly. +*/ +static void editPage( + MemPage *pPg, /* Edit this page */ + int iOld, /* Index of first cell currently on page */ + int iNew, /* Index of new first cell on page */ + int nNew, /* Final number of cells on page */ + u8 **apCell, /* Array of cells */ + u16 *szCell /* Array of cell sizes */ +){ + u8 * const aData = pPg->aData; + const int hdr = pPg->hdrOffset; + u8 *pBegin = &pPg->aCellIdx[nNew * 2]; + int nCell = pPg->nCell; /* Cells stored on pPg */ + u8 *pData; + u8 *pCellptr; + int i; + int iOldEnd = iOld + pPg->nCell + pPg->nOverflow; + int iNewEnd = iNew + nNew; + +#ifdef SQLITE_DEBUG + u8 *pTmp = sqlite3PagerTempSpace(pPg->pBt->pPager); + memcpy(pTmp, aData, pPg->pBt->usableSize); +#endif + + /* Remove cells from the start and end of the page */ + if( iOldaCellIdx, &pPg->aCellIdx[nShift*2], nCell*2); + nCell -= nShift; + } + if( iNewEnd < iOldEnd ){ + nCell -= pageFreeArray( + pPg, iOldEnd-iNewEnd, &apCell[iNewEnd], &szCell[iNewEnd] + ); + } + + pData = &aData[get2byteNotZero(&aData[hdr+5])]; + if( pDataaCellIdx; + memmove(&pCellptr[nAdd*2], pCellptr, nCell*2); + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + nAdd, &apCell[iNew], &szCell[iNew] + ) ) goto editpage_fail; + nCell += nAdd; + } + + /* Add any overflow cells */ + for(i=0; inOverflow; i++){ + int iCell = (iOld + pPg->aiOvfl[i]) - iNew; + if( iCell>=0 && iCellaCellIdx[iCell * 2]; + memmove(&pCellptr[2], pCellptr, (nCell - iCell) * 2); + nCell++; + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + 1, &apCell[iCell + iNew], &szCell[iCell + iNew] + ) ) goto editpage_fail; + } + } + + /* Append cells to the end of the page */ + pCellptr = &pPg->aCellIdx[nCell*2]; + if( pageInsertArray( + pPg, pBegin, &pData, pCellptr, + nNew-nCell, &apCell[iNew+nCell], &szCell[iNew+nCell] + ) ) goto editpage_fail; + + pPg->nCell = nNew; + pPg->nOverflow = 0; + + put2byte(&aData[hdr+3], pPg->nCell); + put2byte(&aData[hdr+5], pData - aData); + +#ifdef SQLITE_DEBUG + for(i=0; iaCellIdx[i*2]); + if( pCell>=aData && pCell<&aData[pPg->pBt->usableSize] ){ + pCell = &pTmp[pCell - aData]; + } + assert( 0==memcmp(pCell, &aData[iOff], szCell[i+iNew]) ); + } +#endif + + return; + editpage_fail: + /* Unable to edit this page. Rebuild it from scratch instead. */ + rebuildPage(pPg, nNew, &apCell[iNew], &szCell[iNew]); } /* @@ -58252,7 +59054,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ assert( pPage->nOverflow==1 ); /* This error condition is now caught prior to reaching this function */ - if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT; + if( NEVER(pPage->nCell==0) ) return SQLITE_CORRUPT_BKPT; /* Allocate a new page. This page will become the right-sibling of ** pPage. Make the parent page writable, so that the new divider cell @@ -58270,7 +59072,8 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ assert( sqlite3PagerIswriteable(pNew->pDbPage) ); assert( pPage->aData[0]==(PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF) ); zeroPage(pNew, PTF_INTKEY|PTF_LEAFDATA|PTF_LEAF); - assemblePage(pNew, 1, &pCell, &szCell); + rebuildPage(pNew, 1, &pCell, &szCell); + pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell; /* If this is an auto-vacuum database, update the pointer map ** with entries for the new page, and any pointer from the @@ -58489,17 +59292,22 @@ static int balance_nonroot( int iOvflSpace = 0; /* First unused byte of aOvflSpace[] */ int szScratch; /* Size of scratch memory requested */ MemPage *apOld[NB]; /* pPage and up to two siblings */ - MemPage *apCopy[NB]; /* Private copies of apOld[] pages */ MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */ u8 *pRight; /* Location in parent of right-sibling pointer */ u8 *apDiv[NB-1]; /* Divider cells in pParent */ int cntNew[NB+2]; /* Index in aCell[] of cell after i-th page */ - int szNew[NB+2]; /* Combined size of cells place on i-th page */ + int cntOld[NB+2]; /* Old index in aCell[] after i-th page */ + int szNew[NB+2]; /* Combined size of cells placed on i-th page */ u8 **apCell = 0; /* All cells begin balanced */ u16 *szCell; /* Local size of all cells in apCell[] */ u8 *aSpace1; /* Space for copies of dividers cells */ Pgno pgno; /* Temp var to store a page number in */ + u8 abDone[NB+2]; /* True after i'th new page is populated */ + Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */ + Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */ + u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */ + memset(abDone, 0, sizeof(abDone)); pBt = pParent->pBt; assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); @@ -58608,12 +59416,14 @@ static int balance_nonroot( /* ** Allocate space for memory structures */ - k = pBt->pageSize + ROUND8(sizeof(MemPage)); szScratch = nMaxCells*sizeof(u8*) /* apCell */ + nMaxCells*sizeof(u16) /* szCell */ - + pBt->pageSize /* aSpace1 */ - + k*nOld; /* Page copies (apCopy) */ + + pBt->pageSize; /* aSpace1 */ + + /* EVIDENCE-OF: R-28375-38319 SQLite will never request a scratch buffer + ** that is more than 6 times the database page size. */ + assert( szScratch<=6*(int)pBt->pageSize ); apCell = sqlite3ScratchMalloc( szScratch ); if( apCell==0 ){ rc = SQLITE_NOMEM; @@ -58626,8 +59436,8 @@ static int balance_nonroot( /* ** Load pointers to all cells on sibling pages and the divider cells ** into the local apCell[] array. Make copies of the divider cells - ** into space obtained from aSpace1[] and remove the divider cells - ** from pParent. + ** into space obtained from aSpace1[]. The divider cells have already + ** been removed from pParent. ** ** If the siblings are on leaf pages, then the child pointers of the ** divider cells are stripped from the cells before they are copied @@ -58643,15 +59453,7 @@ static int balance_nonroot( leafData = apOld[0]->intKeyLeaf; for(i=0; ipageSize + k*i]; - memcpy(pOld, apOld[i], sizeof(MemPage)); - pOld->aData = (void*)&pOld[1]; - memcpy(pOld->aData, apOld[i]->aData, pBt->pageSize); + MemPage *pOld = apOld[i]; limit = pOld->nCell+pOld->nOverflow; if( pOld->nOverflow>0 ){ @@ -58672,6 +59474,7 @@ static int balance_nonroot( nCell++; } } + cntOld[i] = nCell; if( i usableSpace ){ - szNew[k] = subtotal - szCell[i]; + szNew[k] = subtotal - szCell[i] - 2; cntNew[k] = i; if( leafData ){ i--; } subtotal = 0; @@ -58737,9 +59544,10 @@ static int balance_nonroot( /* ** The packing computed by the previous block is biased toward the siblings - ** on the left side. The left siblings are always nearly full, while the - ** right-most sibling might be nearly empty. This block of code attempts - ** to adjust the packing of siblings to get a better balance. + ** on the left side (siblings with smaller keys). The left siblings are + ** always nearly full, while the right-most sibling might be nearly empty. + ** The next block of code attempts to adjust the packing of siblings to + ** get a better balance. ** ** This adjustment is more than an optimization. The packing above might ** be so out of balance as to be illegal. For example, the right-most @@ -58768,22 +59576,18 @@ static int balance_nonroot( szNew[i-1] = szLeft; } - /* Either we found one or more cells (cntnew[0])>0) or pPage is - ** a virtual root page. A virtual root page is when the real root - ** page is page 1 and we are the only child of that page. - ** - ** UPDATE: The assert() below is not necessarily true if the database - ** file is corrupt. The corruption will be detected and reported later - ** in this procedure so there is no need to act upon it now. + /* Sanity check: For a non-corrupt database file one of the follwing + ** must be true: + ** (1) We found one or more cells (cntNew[0])>0), or + ** (2) pPage is a virtual root page. A virtual root page is when + ** the real root page is page 1 and we are the only child of + ** that page. */ -#if 0 - assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) ); -#endif - - TRACE(("BALANCE: old: %d %d %d ", - apOld[0]->pgno, - nOld>=2 ? apOld[1]->pgno : 0, - nOld>=3 ? apOld[2]->pgno : 0 + assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) || CORRUPT_DB); + TRACE(("BALANCE: old: %d(nc=%d) %d(nc=%d) %d(nc=%d)\n", + apOld[0]->pgno, apOld[0]->nCell, + nOld>=2 ? apOld[1]->pgno : 0, nOld>=2 ? apOld[1]->nCell : 0, + nOld>=3 ? apOld[2]->pgno : 0, nOld>=3 ? apOld[2]->nCell : 0 )); /* @@ -58806,8 +59610,10 @@ static int balance_nonroot( assert( i>0 ); rc = allocateBtreePage(pBt, &pNew, &pgno, (bBulk ? 1 : pgno), 0); if( rc ) goto balance_cleanup; + zeroPage(pNew, pageFlags); apNew[i] = pNew; nNew++; + cntOld[i] = nCell; /* Set the pointer-map entry for the new sibling page. */ if( ISAUTOVACUUM ){ @@ -58819,135 +59625,247 @@ static int balance_nonroot( } } - /* Free any old pages that were not reused as new pages. - */ - while( ipgno; - int minI = i; - for(j=i+1; jpgno<(unsigned)minV ){ - minI = j; - minV = apNew[j]->pgno; + for(i=0; ipgno; + aPgFlags[i] = apNew[i]->pDbPage->flags; + for(j=0; ji ){ - MemPage *pT; - pT = apNew[i]; - apNew[i] = apNew[minI]; - apNew[minI] = pT; + } + for(i=0; ii ){ + sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0); + } + sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]); + apNew[i]->pgno = pgno; } } - TRACE(("new: %d(%d) %d(%d) %d(%d) %d(%d) %d(%d)\n", - apNew[0]->pgno, szNew[0], + + TRACE(("BALANCE: new: %d(%d nc=%d) %d(%d nc=%d) %d(%d nc=%d) " + "%d(%d nc=%d) %d(%d nc=%d)\n", + apNew[0]->pgno, szNew[0], cntNew[0], nNew>=2 ? apNew[1]->pgno : 0, nNew>=2 ? szNew[1] : 0, + nNew>=2 ? cntNew[1] - cntNew[0] - !leafData : 0, nNew>=3 ? apNew[2]->pgno : 0, nNew>=3 ? szNew[2] : 0, + nNew>=3 ? cntNew[2] - cntNew[1] - !leafData : 0, nNew>=4 ? apNew[3]->pgno : 0, nNew>=4 ? szNew[3] : 0, - nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0)); + nNew>=4 ? cntNew[3] - cntNew[2] - !leafData : 0, + nNew>=5 ? apNew[4]->pgno : 0, nNew>=5 ? szNew[4] : 0, + nNew>=5 ? cntNew[4] - cntNew[3] - !leafData : 0 + )); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); put4byte(pRight, apNew[nNew-1]->pgno); - /* - ** Evenly distribute the data in apCell[] across the new pages. - ** Insert divider cells into pParent as necessary. + /* If the sibling pages are not leaves, ensure that the right-child pointer + ** of the right-most new sibling page is set to the value that was + ** originally in the same field of the right-most old sibling page. */ + if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){ + MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1]; + memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4); + } + + /* Make any required updates to pointer map entries associated with + ** cells stored on sibling pages following the balance operation. Pointer + ** map entries associated with divider cells are set by the insertCell() + ** routine. The associated pointer map entries are: + ** + ** a) if the cell contains a reference to an overflow chain, the + ** entry associated with the first page in the overflow chain, and + ** + ** b) if the sibling pages are not leaves, the child page associated + ** with the cell. + ** + ** If the sibling pages are not leaves, then the pointer map entry + ** associated with the right-child of each sibling may also need to be + ** updated. This happens below, after the sibling pages have been + ** populated, not here. */ - j = 0; - for(i=0; inCell>0 || (nNew==1 && cntNew[0]==0) ); - assert( pNew->nOverflow==0 ); + if( ISAUTOVACUUM ){ + MemPage *pNew = apNew[0]; + u8 *aOld = pNew->aData; + int cntOldNext = pNew->nCell + pNew->nOverflow; + int usableSize = pBt->usableSize; + int iNew = 0; + int iOld = 0; - j = cntNew[i]; + for(i=0; inCell + pOld->nOverflow + !leafData; + aOld = pOld->aData; + } + if( i==cntNew[iNew] ){ + pNew = apNew[++iNew]; + if( !leafData ) continue; + } - /* If the sibling page assembled above was not the right-most sibling, - ** insert a divider cell into the parent page. - */ - assert( ileaf ){ - memcpy(&pNew->aData[8], pCell, 4); - }else if( leafData ){ - /* If the tree is a leaf-data tree, and the siblings are leaves, - ** then there is no divider cell in apCell[]. Instead, the divider - ** cell consists of the integer key for the right-most cell of - ** the sibling-page assembled above only. - */ - CellInfo info; - j--; - btreeParseCellPtr(pNew, apCell[j], &info); - pCell = pTemp; - sz = 4 + putVarint(&pCell[4], info.nKey); - pTemp = 0; - }else{ - pCell -= 4; - /* Obscure case for non-leaf-data trees: If the cell at pCell was - ** previously stored on a leaf node, and its reported size was 4 - ** bytes, then it may actually be smaller than this - ** (see btreeParseCellPtr(), 4 bytes is the minimum size of - ** any cell). But it is important to pass the correct size to - ** insertCell(), so reparse the cell now. - ** - ** Note that this can never happen in an SQLite data file, as all - ** cells are at least 4 bytes. It only happens in b-trees used - ** to evaluate "IN (SELECT ...)" and similar clauses. - */ - if( szCell[j]==4 ){ - assert(leafCorrection==4); - sz = cellSizePtr(pParent, pCell); + /* Cell pCell is destined for new sibling page pNew. Originally, it + ** was either part of sibling page iOld (possibly an overflow cell), + ** or else the divider cell to the left of sibling page iOld. So, + ** if sibling page iOld had the same page number as pNew, and if + ** pCell really was a part of sibling page iOld (not a divider or + ** overflow cell), we can skip updating the pointer map entries. */ + if( iOld>=nNew + || pNew->pgno!=aPgno[iOld] + || pCell=&aOld[usableSize] + ){ + if( !leafCorrection ){ + ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc); + } + if( szCell[i]>pNew->minLocal ){ + ptrmapPutOvflPtr(pNew, pCell, &rc); } } - iOvflSpace += sz; - assert( sz<=pBt->maxLocal+23 ); - assert( iOvflSpace <= (int)pBt->pageSize ); - insertCell(pParent, nxDiv, pCell, sz, pTemp, pNew->pgno, &rc); - if( rc!=SQLITE_OK ) goto balance_cleanup; - assert( sqlite3PagerIswriteable(pParent->pDbPage) ); - - j++; - nxDiv++; } } - assert( j==nCell ); + + /* Insert new divider cells into pParent. */ + for(i=0; ileaf ){ + memcpy(&pNew->aData[8], pCell, 4); + }else if( leafData ){ + /* If the tree is a leaf-data tree, and the siblings are leaves, + ** then there is no divider cell in apCell[]. Instead, the divider + ** cell consists of the integer key for the right-most cell of + ** the sibling-page assembled above only. + */ + CellInfo info; + j--; + btreeParseCellPtr(pNew, apCell[j], &info); + pCell = pTemp; + sz = 4 + putVarint(&pCell[4], info.nKey); + pTemp = 0; + }else{ + pCell -= 4; + /* Obscure case for non-leaf-data trees: If the cell at pCell was + ** previously stored on a leaf node, and its reported size was 4 + ** bytes, then it may actually be smaller than this + ** (see btreeParseCellPtr(), 4 bytes is the minimum size of + ** any cell). But it is important to pass the correct size to + ** insertCell(), so reparse the cell now. + ** + ** Note that this can never happen in an SQLite data file, as all + ** cells are at least 4 bytes. It only happens in b-trees used + ** to evaluate "IN (SELECT ...)" and similar clauses. + */ + if( szCell[j]==4 ){ + assert(leafCorrection==4); + sz = cellSizePtr(pParent, pCell); + } + } + iOvflSpace += sz; + assert( sz<=pBt->maxLocal+23 ); + assert( iOvflSpace <= (int)pBt->pageSize ); + insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc); + if( rc!=SQLITE_OK ) goto balance_cleanup; + assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + } + + /* Now update the actual sibling pages. The order in which they are updated + ** is important, as this code needs to avoid disrupting any page from which + ** cells may still to be read. In practice, this means: + ** + ** (1) If cells are moving left (from apNew[iPg] to apNew[iPg-1]) + ** then it is not safe to update page apNew[iPg] until after + ** the left-hand sibling apNew[iPg-1] has been updated. + ** + ** (2) If cells are moving right (from apNew[iPg] to apNew[iPg+1]) + ** then it is not safe to update page apNew[iPg] until after + ** the right-hand sibling apNew[iPg+1] has been updated. + ** + ** If neither of the above apply, the page is safe to update. + ** + ** The iPg value in the following loop starts at nNew-1 goes down + ** to 0, then back up to nNew-1 again, thus making two passes over + ** the pages. On the initial downward pass, only condition (1) above + ** needs to be tested because (2) will always be true from the previous + ** step. On the upward pass, both conditions are always true, so the + ** upwards pass simply processes pages that were missed on the downward + ** pass. + */ + for(i=1-nNew; i=0 && iPg=0 /* On the upwards pass, or... */ + || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */ + ){ + int iNew; + int iOld; + int nNewCell; + + /* Verify condition (1): If cells are moving left, update iPg + ** only after iPg-1 has already been updated. */ + assert( iPg==0 || cntOld[iPg-1]>=cntNew[iPg-1] || abDone[iPg-1] ); + + /* Verify condition (2): If cells are moving right, update iPg + ** only after iPg+1 has already been updated. */ + assert( cntNew[iPg]>=cntOld[iPg] || abDone[iPg+1] ); + + if( iPg==0 ){ + iNew = iOld = 0; + nNewCell = cntNew[0]; + }else{ + iOld = iPgnFree = usableSpace-szNew[iPg]; + assert( apNew[iPg]->nOverflow==0 ); + assert( apNew[iPg]->nCell==nNewCell ); + } + } + + /* All pages have been processed exactly once */ + assert( memcmp(abDone, "\01\01\01\01\01", nNew)==0 ); + assert( nOld>0 ); assert( nNew>0 ); - if( (pageFlags & PTF_LEAF)==0 ){ - u8 *zChild = &apCopy[nOld-1]->aData[8]; - memcpy(&apNew[nNew-1]->aData[8], zChild, 4); - } if( isRoot && pParent->nCell==0 && pParent->hdrOffset<=apNew[0]->nFree ){ /* The root page of the b-tree now contains no cells. The only sibling @@ -58960,126 +59878,50 @@ static int balance_nonroot( ** sets all pointer-map entries corresponding to database image pages ** for which the pointer is stored within the content being copied. ** - ** The second assert below verifies that the child page is defragmented - ** (it must be, as it was just reconstructed using assemblePage()). This - ** is important if the parent page happens to be page 1 of the database - ** image. */ + ** It is critical that the child page be defragmented before being + ** copied into the parent, because if the parent is page 1 then it will + ** by smaller than the child due to the database header, and so all the + ** free space needs to be up front. + */ assert( nNew==1 ); + rc = defragmentPage(apNew[0]); + testcase( rc!=SQLITE_OK ); assert( apNew[0]->nFree == - (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) + (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) + || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); - }else if( ISAUTOVACUUM ){ - /* Fix the pointer-map entries for all the cells that were shifted around. - ** There are several different types of pointer-map entries that need to - ** be dealt with by this routine. Some of these have been set already, but - ** many have not. The following is a summary: - ** - ** 1) The entries associated with new sibling pages that were not - ** siblings when this function was called. These have already - ** been set. We don't need to worry about old siblings that were - ** moved to the free-list - the freePage() code has taken care - ** of those. - ** - ** 2) The pointer-map entries associated with the first overflow - ** page in any overflow chains used by new divider cells. These - ** have also already been taken care of by the insertCell() code. - ** - ** 3) If the sibling pages are not leaves, then the child pages of - ** cells stored on the sibling pages may need to be updated. - ** - ** 4) If the sibling pages are not internal intkey nodes, then any - ** overflow pages used by these cells may need to be updated - ** (internal intkey nodes never contain pointers to overflow pages). - ** - ** 5) If the sibling pages are not leaves, then the pointer-map - ** entries for the right-child pages of each sibling may need - ** to be updated. - ** - ** Cases 1 and 2 are dealt with above by other code. The next - ** block deals with cases 3 and 4 and the one after that, case 5. Since - ** setting a pointer map entry is a relatively expensive operation, this - ** code only sets pointer map entries for child or overflow pages that have - ** actually moved between pages. */ - MemPage *pNew = apNew[0]; - MemPage *pOld = apCopy[0]; - int nOverflow = pOld->nOverflow; - int iNextOld = pOld->nCell + nOverflow; - int iOverflow = (nOverflow ? pOld->aiOvfl[0] : -1); - j = 0; /* Current 'old' sibling page */ - k = 0; /* Current 'new' sibling page */ - for(i=0; inCell + pOld->nOverflow; - if( pOld->nOverflow ){ - nOverflow = pOld->nOverflow; - iOverflow = i + !leafData + pOld->aiOvfl[0]; - } - isDivider = !leafData; - } - - assert(nOverflow>0 || iOverflowaiOvfl[0]==pOld->aiOvfl[1]-1); - assert(nOverflow<3 || pOld->aiOvfl[1]==pOld->aiOvfl[2]-1); - if( i==iOverflow ){ - isDivider = 1; - if( (--nOverflow)>0 ){ - iOverflow++; - } - } - - if( i==cntNew[k] ){ - /* Cell i is the cell immediately following the last cell on new - ** sibling page k. If the siblings are not leaf pages of an - ** intkey b-tree, then cell i is a divider cell. */ - pNew = apNew[++k]; - if( !leafData ) continue; - } - assert( jpgno!=pNew->pgno ){ - if( !leafCorrection ){ - ptrmapPut(pBt, get4byte(apCell[i]), PTRMAP_BTREE, pNew->pgno, &rc); - } - if( szCell[i]>pNew->minLocal ){ - ptrmapPutOvflPtr(pNew, apCell[i], &rc); - } - } + }else if( ISAUTOVACUUM && !leafCorrection ){ + /* Fix the pointer map entries associated with the right-child of each + ** sibling page. All other pointer map entries have already been taken + ** care of. */ + for(i=0; iaData[8]); + ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); } + } - if( !leafCorrection ){ - for(i=0; iaData[8]); - ptrmapPut(pBt, key, PTRMAP_BTREE, apNew[i]->pgno, &rc); - } - } + assert( pParent->isInit ); + TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", + nOld, nNew, nCell)); + + /* Free any old pages that were not reused as new pages. + */ + for(i=nNew; iisInit ){ /* The ptrmapCheckPages() contains assert() statements that verify that ** all pointer map pages are set correctly. This is helpful while ** debugging. This is usually disabled because a corrupt database may ** cause an assert() statement to fail. */ ptrmapCheckPages(apNew, nNew); ptrmapCheckPages(&pParent, 1); -#endif } - - assert( pParent->isInit ); - TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", - nOld, nNew, nCell)); +#endif /* ** Cleanup before returning. @@ -59971,6 +60813,13 @@ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ ** The schema layer numbers meta values differently. At the schema ** layer (and the SetCookie and ReadCookie opcodes) the number of ** free pages is not visible. So Cookie[0] is the same as Meta[1]. +** +** This routine treats Meta[BTREE_DATA_VERSION] as a special case. Instead +** of reading the value out of the header, it instead loads the "DataVersion" +** from the pager. The BTREE_DATA_VERSION value is not actually stored in the +** database file. It is a number computed by the pager. But its access +** pattern is the same as header meta values, and so it is convenient to +** read it from this routine. */ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){ BtShared *pBt = p->pBt; @@ -59981,7 +60830,11 @@ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){ assert( pBt->pPage1 ); assert( idx>=0 && idx<=15 ); - *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); + if( idx==BTREE_DATA_VERSION ){ + *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion; + }else{ + *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); + } /* If auto-vacuum is disabled in this build and this is an auto-vacuum ** database, mark the database as read-only. */ @@ -60072,7 +60925,7 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ if( pCur->iPage==0 ){ /* All pages of the b-tree have been visited. Return successfully. */ *pnEntry = nEntry; - return SQLITE_OK; + return moveToRoot(pCur); } moveToParent(pCur); }while ( pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell ); @@ -60464,8 +61317,14 @@ static int checkTreePage( assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */ memset(hit+contentOffset, 0, usableSize-contentOffset); memset(hit, 1, contentOffset); + /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the + ** number of cells on the page. */ nCell = get2byte(&data[hdr+3]); + /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page + ** immediately follows the b-tree page header. */ cellStart = hdr + 12 - 4*pPage->leaf; + /* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte + ** integer offsets to the cell contents. */ for(i=0; i=pc; j--) hit[j]++; } } + /* EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header + ** is the offset of the first freeblock, or zero if there are no + ** freeblocks on the page. */ i = get2byte(&data[hdr+1]); while( i>0 ){ int size, j; @@ -60488,7 +61350,13 @@ static int checkTreePage( size = get2byte(&data[i+2]); assert( i+size<=usableSize ); /* Enforced by btreeInitPage() */ for(j=i+size-1; j>=i; j--) hit[j]++; + /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a + ** big-endian integer which is the offset in the b-tree page of the next + ** freeblock in the chain, or zero if the freeblock is the last on the + ** chain. */ j = get2byte(&data[i]); + /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of + ** increasing offset. */ assert( j==0 || j>i+size ); /* Enforced by btreeInitPage() */ assert( j<=usableSize-4 ); /* Enforced by btreeInitPage() */ i = j; @@ -60502,6 +61370,11 @@ static int checkTreePage( break; } } + /* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments + ** is stored in the fifth field of the b-tree page header. + ** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the + ** number of fragmented free bytes within the cell content area. + */ if( cnt!=data[hdr+7] ){ checkAppendMsg(pCheck, "Fragmentation of %d bytes reported as %d on page %d", @@ -60905,6 +61778,11 @@ SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){ return (p->pBt->btsFlags & BTS_READ_ONLY)!=0; } +/* +** Return the size of the header added to each page by this module. +*/ +SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } + /************** End of btree.c ***********************************************/ /************** Begin file backup.c ******************************************/ /* @@ -61029,6 +61907,20 @@ static int setDestPgsz(sqlite3_backup *p){ return rc; } +/* +** Check that there is no open read-transaction on the b-tree passed as the +** second argument. If there is not, return SQLITE_OK. Otherwise, if there +** is an open read-transaction, return SQLITE_ERROR and leave an error +** message in database handle db. +*/ +static int checkReadTransaction(sqlite3 *db, Btree *p){ + if( sqlite3BtreeIsInReadTrans(p) ){ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use"); + return SQLITE_ERROR; + } + return SQLITE_OK; +} + /* ** Create an sqlite3_backup process to copy the contents of zSrcDb from ** connection handle pSrcDb to zDestDb in pDestDb. If successful, return @@ -61045,6 +61937,13 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( ){ sqlite3_backup *p; /* Value to return */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(pSrcDb)||!sqlite3SafetyCheckOk(pDestDb) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + /* Lock the source database handle. The destination database ** handle is not locked in this routine, but it is locked in ** sqlite3_backup_step(). The user is required to ensure that no @@ -61081,12 +61980,15 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( p->iNext = 1; p->isAttached = 0; - if( 0==p->pSrc || 0==p->pDest || setDestPgsz(p)==SQLITE_NOMEM ){ + if( 0==p->pSrc || 0==p->pDest + || setDestPgsz(p)==SQLITE_NOMEM + || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK + ){ /* One (or both) of the named databases did not exist or an OOM - ** error was hit. The error has already been written into the - ** pDestDb handle. All that is left to do here is free the - ** sqlite3_backup structure. - */ + ** error was hit. Or there is a transaction open on the destination + ** database. The error has already been written into the pDestDb + ** handle. All that is left to do here is free the sqlite3_backup + ** structure. */ sqlite3_free(p); p = 0; } @@ -61241,6 +62143,9 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ int pgszSrc = 0; /* Source page size */ int pgszDest = 0; /* Destination page size */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(p->pSrcDb->mutex); sqlite3BtreeEnter(p->pSrc); if( p->pDestDb ){ @@ -61530,6 +62435,12 @@ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ ** call to sqlite3_backup_step(). */ SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return p->nRemaining; } @@ -61538,6 +62449,12 @@ SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){ ** recent call to sqlite3_backup_step(). */ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return p->nPagecount; } @@ -63628,6 +64545,7 @@ static Op *opIterNext(VdbeOpIter *p){ */ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ int hasAbort = 0; + int hasFkCounter = 0; Op *pOp; VdbeOpIter sIter; memset(&sIter, 0, sizeof(sIter)); @@ -63636,15 +64554,17 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ while( (pOp = opIterNext(&sIter))!=0 ){ int opcode = pOp->opcode; if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename -#ifndef SQLITE_OMIT_FOREIGN_KEY - || (opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1) -#endif || ((opcode==OP_Halt || opcode==OP_HaltIfNull) && ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort)) ){ hasAbort = 1; break; } +#ifndef SQLITE_OMIT_FOREIGN_KEY + if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){ + hasFkCounter = 1; + } +#endif } sqlite3DbFree(v->db, sIter.apSub); @@ -63653,7 +64573,7 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ ** through all opcodes and hasAbort may be set incorrectly. Return ** true for this case to prevent the assert() in the callers frame ** from failing. */ - return ( v->db->mallocFailed || hasAbort==mayAbort ); + return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter ); } #endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */ @@ -63829,6 +64749,34 @@ SQLITE_PRIVATE int sqlite3VdbeAddOpList(Vdbe *p, int nOp, VdbeOpList const *aOp, return addr; } +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) +/* +** Add an entry to the array of counters managed by sqlite3_stmt_scanstatus(). +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatus( + Vdbe *p, /* VM to add scanstatus() to */ + int addrExplain, /* Address of OP_Explain (or 0) */ + int addrLoop, /* Address of loop counter */ + int addrVisit, /* Address of rows visited counter */ + LogEst nEst, /* Estimated number of output rows */ + const char *zName /* Name of table or index being scanned */ +){ + int nByte = (p->nScan+1) * sizeof(ScanStatus); + ScanStatus *aNew; + aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); + if( aNew ){ + ScanStatus *pNew = &aNew[p->nScan++]; + pNew->addrExplain = addrExplain; + pNew->addrLoop = addrLoop; + pNew->addrVisit = addrVisit; + pNew->nEst = nEst; + pNew->zName = sqlite3DbStrDup(p->db, zName); + p->aScan = aNew; + } +} +#endif + + /* ** Change the value of the P1 operand for a specific instruction. ** This routine is useful when a large program is loaded from a @@ -64927,6 +65875,9 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->apCsr = allocSpace(p->apCsr, nCursor*sizeof(VdbeCursor*), &zCsr, zEnd, &nByte); p->aOnceFlag = allocSpace(p->aOnceFlag, nOnce, &zCsr, zEnd, &nByte); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + p->anExec = allocSpace(p->anExec, p->nOp*sizeof(i64), &zCsr, zEnd, &nByte); +#endif if( nByte ){ p->pFree = sqlite3DbMallocZero(db, nByte); } @@ -64943,7 +65894,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->aVar[n].db = db; } } - if( p->azVar ){ + if( p->azVar && pParse->nzVar>0 ){ p->nzVar = pParse->nzVar; memcpy(p->azVar, pParse->azVar, p->nzVar*sizeof(p->azVar[0])); memset(pParse->azVar, 0, pParse->nzVar*sizeof(pParse->azVar[0])); @@ -64994,6 +65945,9 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ */ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ Vdbe *v = pFrame->v; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + v->anExec = pFrame->anExec; +#endif v->aOnceFlag = pFrame->aOnceFlag; v->nOnceFlag = pFrame->nOnceFlag; v->aOp = pFrame->aOp; @@ -65004,6 +65958,7 @@ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ v->nCursor = pFrame->nCursor; v->db->lastRowid = pFrame->lastRowid; v->nChange = pFrame->nChange; + v->db->nChange = pFrame->nDbChange; return pFrame->pc; } @@ -65571,6 +66526,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + p->nChange = 0; } } } @@ -65611,6 +66567,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ }else if( rc!=SQLITE_OK ){ p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); + p->nChange = 0; }else{ db->nDeferredCons = 0; db->nDeferredImmCons = 0; @@ -65619,6 +66576,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ } }else{ sqlite3RollbackAll(db, SQLITE_OK); + p->nChange = 0; } db->nStatement = 0; }else if( eStatementOp==0 ){ @@ -65630,6 +66588,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + p->nChange = 0; } } @@ -65650,6 +66609,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3RollbackAll(db, SQLITE_ABORT_ROLLBACK); sqlite3CloseSavepoints(db); db->autoCommit = 1; + p->nChange = 0; } } @@ -65911,6 +66871,12 @@ SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); sqlite3DbFree(db, p->pFree); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + for(i=0; inScan; i++){ + sqlite3DbFree(db, p->aScan[i].zName); + } + sqlite3DbFree(db, p->aScan); +#endif } /* @@ -66069,9 +67035,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format){ i64 i = pMem->u.i; u64 u; if( i<0 ){ - if( i<(-MAX_6BYTE) ) return 6; - /* Previous test prevents: u = -(-9223372036854775808) */ - u = -i; + u = ~i; }else{ u = i; } @@ -66237,10 +67201,14 @@ static u32 SQLITE_NOINLINE serialGet( u32 y = FOUR_BYTE_UINT(buf+4); x = (x<<32) + y; if( serial_type==6 ){ + /* EVIDENCE-OF: R-29851-52272 Value is a big-endian 64-bit + ** twos-complement integer. */ pMem->u.i = *(i64*)&x; pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); }else{ + /* EVIDENCE-OF: R-57343-49114 Value is a big-endian IEEE 754-2008 64-bit + ** floating point number. */ #if !defined(NDEBUG) && !defined(SQLITE_OMIT_FLOATING_POINT) /* Verify that integers and floating point values use the same ** byte order. Or, that if SQLITE_MIXED_ENDIAN_64BIT_FLOAT is @@ -66268,35 +67236,46 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( switch( serial_type ){ case 10: /* Reserved for future use */ case 11: /* Reserved for future use */ - case 0: { /* NULL */ + case 0: { /* Null */ + /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ pMem->flags = MEM_Null; break; } - case 1: { /* 1-byte signed integer */ + case 1: { + /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement + ** integer. */ pMem->u.i = ONE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); return 1; } case 2: { /* 2-byte signed integer */ + /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit + ** twos-complement integer. */ pMem->u.i = TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); return 2; } case 3: { /* 3-byte signed integer */ + /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit + ** twos-complement integer. */ pMem->u.i = THREE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); return 3; } case 4: { /* 4-byte signed integer */ + /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit + ** twos-complement integer. */ pMem->u.i = FOUR_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); return 4; } case 5: { /* 6-byte signed integer */ + /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit + ** twos-complement integer. */ pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); @@ -66310,11 +67289,17 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( } case 8: /* Integer 0 */ case 9: { /* Integer 1 */ + /* EVIDENCE-OF: R-12976-22893 Value is the integer 0. */ + /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */ pMem->u.i = serial_type-8; pMem->flags = MEM_Int; return 0; } default: { + /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in + ** length. + ** EVIDENCE-OF: R-28401-00140 Value is a string in the text encoding and + ** (N-13)/2 bytes in length. */ static const u16 aFlag[] = { MEM_Blob|MEM_Ephem, MEM_Str|MEM_Ephem }; pMem->z = (char *)buf; pMem->n = (serial_type-12)/2; @@ -66513,6 +67498,41 @@ debugCompareEnd: } #endif +#if SQLITE_DEBUG +/* +** Count the number of fields (a.k.a. columns) in the record given by +** pKey,nKey. The verify that this count is less than or equal to the +** limit given by pKeyInfo->nField + pKeyInfo->nXField. +** +** If this constraint is not satisfied, it means that the high-speed +** vdbeRecordCompareInt() and vdbeRecordCompareString() routines will +** not work correctly. If this assert() ever fires, it probably means +** that the KeyInfo.nField or KeyInfo.nXField values were computed +** incorrectly. +*/ +static void vdbeAssertFieldCountWithinLimits( + int nKey, const void *pKey, /* The record to verify */ + const KeyInfo *pKeyInfo /* Compare size with this KeyInfo */ +){ + int nField = 0; + u32 szHdr; + u32 idx; + u32 notUsed; + const unsigned char *aKey = (const unsigned char*)pKey; + + if( CORRUPT_DB ) return; + idx = getVarint32(aKey, szHdr); + assert( szHdr<=nKey ); + while( idxnField+pKeyInfo->nXField ); +} +#else +# define vdbeAssertFieldCountWithinLimits(A,B,C) +#endif + /* ** Both *pMem1 and *pMem2 contain string values. Compare the two values ** using the collation sequence pColl. As usual, return a negative , zero @@ -66924,6 +67944,7 @@ static int vdbeRecordCompareInt( i64 v = pPKey2->aMem[0].u.i; i64 lhs; + vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); assert( (*(u8*)pKey1)<=0x3F || CORRUPT_DB ); switch( serial_type ){ case 1: { /* 1-byte signed integer */ @@ -67011,6 +68032,7 @@ static int vdbeRecordCompareString( int serial_type; int res; + vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); getVarint32(&aKey1[1], serial_type); if( serial_type<12 ){ res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ @@ -67712,7 +68734,10 @@ static int doWalCallbacks(sqlite3 *db){ for(i=0; inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ - int nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + int nEntry; + sqlite3BtreeEnter(pBt); + nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + sqlite3BtreeLeave(pBt); if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){ rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zName, nEntry); } @@ -67892,7 +68917,6 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ ** sqlite3_errmsg() and sqlite3_errcode(). */ const char *zErr = (const char *)sqlite3_value_text(db->pErr); - assert( zErr!=0 || db->mallocFailed ); sqlite3DbFree(db, v->zErrMsg); if( !db->mallocFailed ){ v->zErrMsg = sqlite3DbStrDup(db, zErr); @@ -68278,11 +69302,19 @@ static const void *columnName( const void *(*xFunc)(Mem*), int useType ){ - const void *ret = 0; - Vdbe *p = (Vdbe *)pStmt; + const void *ret; + Vdbe *p; int n; - sqlite3 *db = p->db; - + sqlite3 *db; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + ret = 0; + p = (Vdbe *)pStmt; + db = p->db; assert( db!=0 ); n = sqlite3_column_count(pStmt); if( N=0 ){ @@ -68747,6 +69779,12 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ */ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ sqlite3_stmt *pNext; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(pDb) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(pDb->mutex); if( pStmt==0 ){ pNext = (sqlite3_stmt*)pDb->pVdbe; @@ -68762,11 +69800,87 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ */ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ Vdbe *pVdbe = (Vdbe*)pStmt; - u32 v = pVdbe->aCounter[op]; + u32 v; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !pStmt ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + v = pVdbe->aCounter[op]; if( resetFlag ) pVdbe->aCounter[op] = 0; return (int)v; } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Return status data for a single loop within query pStmt. +*/ +SQLITE_API int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement being queried */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Which metric to return */ + void *pOut /* OUT: Write the answer here */ +){ + Vdbe *p = (Vdbe*)pStmt; + ScanStatus *pScan; + if( idx<0 || idx>=p->nScan ) return 1; + pScan = &p->aScan[idx]; + switch( iScanStatusOp ){ + case SQLITE_SCANSTAT_NLOOP: { + *(sqlite3_int64*)pOut = p->anExec[pScan->addrLoop]; + break; + } + case SQLITE_SCANSTAT_NVISIT: { + *(sqlite3_int64*)pOut = p->anExec[pScan->addrVisit]; + break; + } + case SQLITE_SCANSTAT_EST: { + double r = 1.0; + LogEst x = pScan->nEst; + while( x<100 ){ + x += 10; + r *= 0.5; + } + *(double*)pOut = r*sqlite3LogEstToInt(x); + break; + } + case SQLITE_SCANSTAT_NAME: { + *(const char**)pOut = pScan->zName; + break; + } + case SQLITE_SCANSTAT_EXPLAIN: { + if( pScan->addrExplain ){ + *(const char**)pOut = p->aOp[ pScan->addrExplain ].p4.z; + }else{ + *(const char**)pOut = 0; + } + break; + } + case SQLITE_SCANSTAT_SELECTID: { + if( pScan->addrExplain ){ + *(int*)pOut = p->aOp[ pScan->addrExplain ].p1; + }else{ + *(int*)pOut = -1; + } + break; + } + default: { + return 1; + } + } + return 0; +} + +/* +** Zero all counters associated with the sqlite3_stmt_scanstatus() data. +*/ +SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ + Vdbe *p = (Vdbe*)pStmt; + memset(p->anExec, 0, p->nOp * sizeof(i64)); +} +#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ + /************** End of vdbeapi.c *********************************************/ /************** Begin file vdbetrace.c ***************************************/ /* @@ -69652,6 +70766,9 @@ SQLITE_PRIVATE int sqlite3VdbeExec( #endif nVmStep++; pOp = &aOp[pc]; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + if( p->anExec ) p->anExec[pc]++; +#endif /* Only allow tracing if SQLITE_DEBUG is defined. */ @@ -71677,7 +72794,10 @@ case OP_MakeRecord: { nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type); }while( (--pRec)>=pData0 ); - /* Add the initial header varint and total the size */ + /* EVIDENCE-OF: R-22564-11647 The header begins with a single varint + ** which determines the total number of bytes in the header. The varint + ** value is the size of the header in bytes including the size varint + ** itself. */ testcase( nHdr==126 ); testcase( nHdr==127 ); if( nHdr<=126 ){ @@ -71711,7 +72831,11 @@ case OP_MakeRecord: { pRec = pData0; do{ serial_type = pRec->uTemp; + /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more + ** additional varints, one per column. */ i += putVarint32(&zNewRecord[i], serial_type); /* serial type */ + /* EVIDENCE-OF: R-64536-51728 The values for each column in the record + ** immediately follow the header. */ j += sqlite3VdbeSerialPut(&zNewRecord[j], pRec, serial_type); /* content */ }while( (++pRec)<=pLast ); assert( i==nHdr ); @@ -72846,10 +73970,10 @@ case OP_Found: { /* jump, in3 */ }else{ pIdxKey = sqlite3VdbeAllocUnpackedRecord( pC->pKeyInfo, aTempRec, sizeof(aTempRec), &pFree - ); + ); if( pIdxKey==0 ) goto no_mem; assert( pIn3->flags & MEM_Blob ); - assert( (pIn3->flags & MEM_Zero)==0 ); /* zeroblobs already expanded */ + ExpandBlob(pIn3); sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); } pIdxKey->default_rc = 0; @@ -72857,8 +73981,8 @@ case OP_Found: { /* jump, in3 */ /* For the OP_NoConflict opcode, take the jump if any of the ** input fields are NULL, since any key with a NULL will not ** conflict */ - for(ii=0; iinField; ii++){ + if( pIdxKey->aMem[ii].flags & MEM_Null ){ pc = pOp->p2 - 1; VdbeBranchTaken(1,2); break; } @@ -73543,9 +74667,9 @@ case OP_Sort: { /* jump */ ** ** The next use of the Rowid or Column or Next instruction for P1 ** will refer to the first entry in the database table or index. -** If the table or index is empty and P2>0, then jump immediately to P2. -** If P2 is 0 or if the table or index is not empty, fall through -** to the following instruction. +** If the table or index is empty, jump immediately to P2. +** If the table or index is not empty, fall through to the following +** instruction. ** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is @@ -74461,6 +75585,9 @@ case OP_Program: { /* jump */ pFrame->token = pProgram->token; pFrame->aOnceFlag = p->aOnceFlag; pFrame->nOnceFlag = p->nOnceFlag; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pFrame->anExec = p->anExec; +#endif pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem]; for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){ @@ -74478,6 +75605,7 @@ case OP_Program: { /* jump */ pFrame->pParent = p->pFrame; pFrame->lastRowid = lastRowid; pFrame->nChange = p->nChange; + pFrame->nDbChange = p->db->nChange; p->nChange = 0; p->pFrame = pFrame; p->aMem = aMem = &VdbeFrameMem(pFrame)[-1]; @@ -74488,6 +75616,9 @@ case OP_Program: { /* jump */ p->nOp = pProgram->nOp; p->aOnceFlag = (u8 *)&p->apCsr[p->nCursor]; p->nOnceFlag = pProgram->nOnce; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + p->anExec = 0; +#endif pc = -1; memset(p->aOnceFlag, 0, p->nOnceFlag); @@ -74732,8 +75863,8 @@ case OP_AggFinal: { /* Opcode: Checkpoint P1 P2 P3 * * ** ** Checkpoint database P1. This is a no-op if P1 is not currently in -** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL -** or RESTART. Write 1 or 0 into mem[P3] if the checkpoint returns +** WAL mode. Parameter P2 is one of SQLITE_CHECKPOINT_PASSIVE, FULL, +** RESTART, or TRUNCATE. Write 1 or 0 into mem[P3] if the checkpoint returns ** SQLITE_BUSY or not, respectively. Write the number of pages in the ** WAL after the checkpoint into mem[P3+1] and the number of pages ** in the WAL that have been checkpointed after the checkpoint @@ -74751,6 +75882,7 @@ case OP_Checkpoint: { assert( pOp->p2==SQLITE_CHECKPOINT_PASSIVE || pOp->p2==SQLITE_CHECKPOINT_FULL || pOp->p2==SQLITE_CHECKPOINT_RESTART + || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE ); rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); if( rc==SQLITE_BUSY ){ @@ -75676,6 +76808,11 @@ SQLITE_API int sqlite3_blob_open( Parse *pParse = 0; Incrblob *pBlob = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || ppBlob==0 || zTable==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif flags = !!flags; /* flags = (flags ? 1 : 0); */ *ppBlob = 0; @@ -75894,7 +77031,6 @@ static int blobReadWrite( if( n<0 || iOffset<0 || (iOffset+n)>p->nByte ){ /* Request is out of range. Return a transient error. */ rc = SQLITE_ERROR; - sqlite3Error(db, SQLITE_ERROR); }else if( v==0 ){ /* If there is no statement handle, then the blob-handle has ** already been invalidated. Return SQLITE_ABORT in this case. @@ -75912,10 +77048,10 @@ static int blobReadWrite( sqlite3VdbeFinalize(v); p->pStmt = 0; }else{ - db->errCode = rc; v->rc = rc; } } + sqlite3Error(db, rc); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; @@ -76092,7 +77228,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** The sorter is running in multi-threaded mode if (a) the library was built ** with pre-processor symbol SQLITE_MAX_WORKER_THREADS set to a value greater ** than zero, and (b) worker threads have been enabled at runtime by calling -** sqlite3_config(SQLITE_CONFIG_WORKER_THREADS, ...). +** "PRAGMA threads=N" with some value of N greater than 0. ** ** When Rewind() is called, any data remaining in memory is flushed to a ** final PMA. So at this point the data is stored in some number of sorted @@ -76137,6 +77273,13 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ # define SQLITE_DEBUG_SORTER_THREADS 1 #endif +/* +** Hard-coded maximum amount of data to accumulate in memory before flushing +** to a level 0 PMA. The purpose of this limit is to prevent various integer +** overflows. 512MiB. +*/ +#define SQLITE_MAX_PMASZ (1<<29) + /* ** Private objects used by the sorter */ @@ -76431,9 +77574,6 @@ struct SorterRecord { */ #define SRVAL(p) ((void*)((SorterRecord*)(p) + 1)) -/* The minimum PMA size is set to this value multiplied by the database -** page size in bytes. */ -#define SORTER_MIN_WORKING 10 /* Maximum number of PMAs that a single MergeEngine can merge */ #define SORTER_MAX_MERGE_COUNT 16 @@ -76832,16 +77972,15 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( } if( !sqlite3TempInMemory(db) ){ - pSorter->mnPmaSize = SORTER_MIN_WORKING * pgsz; + u32 szPma = sqlite3GlobalConfig.szPma; + pSorter->mnPmaSize = szPma * pgsz; mxCache = db->aDb[0].pSchema->cache_size; - if( mxCachemxPmaSize = mxCache * pgsz; + if( mxCache<(int)szPma ) mxCache = (int)szPma; + pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ); - /* If the application has not configure scratch memory using - ** SQLITE_CONFIG_SCRATCH then we assume it is OK to do large memory - ** allocations. If scratch memory has been configured, then assume - ** large memory allocations should be avoided to prevent heap - ** fragmentation. + /* EVIDENCE-OF: R-26747-61719 When the application provides any amount of + ** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary + ** large heap allocations. */ if( sqlite3GlobalConfig.pScratch==0 ){ assert( pSorter->iMemory==0 ); @@ -77115,12 +78254,12 @@ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ */ static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){ if( nByte<=(i64)(db->nMaxSorterMmap) && pFd->pMethods->iVersion>=3 ){ - int rc = sqlite3OsTruncate(pFd, nByte); - if( rc==SQLITE_OK ){ - void *p = 0; - sqlite3OsFetch(pFd, 0, (int)nByte, &p); - sqlite3OsUnfetch(pFd, 0, p); - } + void *p = 0; + int chunksize = 4*1024; + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize); + sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte); + sqlite3OsFetch(pFd, 0, (int)nByte, &p); + sqlite3OsUnfetch(pFd, 0, p); } } #else @@ -78401,6 +79540,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr, in }else #endif /*if( !pSorter->bUseThreads )*/ { + assert( pSorter->pMerger!=0 ); assert( pSorter->pMerger->pTask==(&pSorter->aTask[0]) ); rc = vdbeMergeEngineStep(pSorter->pMerger, pbEof); } @@ -79213,7 +80353,7 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ ** is a helper function - a callback for the tree walker. */ static int incrAggDepth(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.i; + if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.n; return WRC_Continue; } static void incrAggFunctionDepth(Expr *pExpr, int N){ @@ -79221,7 +80361,7 @@ static void incrAggFunctionDepth(Expr *pExpr, int N){ Walker w; memset(&w, 0, sizeof(w)); w.xExprCallback = incrAggDepth; - w.u.i = N; + w.u.n = N; sqlite3WalkExpr(&w, pExpr); } } @@ -79773,7 +80913,7 @@ static int exprProbability(Expr *p){ sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); assert( r>=0.0 ); if( r>1.0 ) return -1; - return (int)(r*1000.0); + return (int)(r*134217728.0); } /* @@ -79905,7 +81045,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** EVIDENCE-OF: R-53436-40973 The likely(X) function is equivalent to ** likelihood(X,0.9375). */ /* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */ - pExpr->iTable = pDef->zName[0]=='u' ? 62 : 938; + pExpr->iTable = pDef->zName[0]=='u' ? 8388608 : 125829120; } } #ifndef SQLITE_OMIT_AUTHORIZATION @@ -81167,7 +82307,7 @@ SQLITE_PRIVATE Expr *sqlite3PExpr( const Token *pToken /* Argument token */ ){ Expr *p; - if( op==TK_AND && pLeft && pRight ){ + if( op==TK_AND && pLeft && pRight && pParse->nErr==0 ){ /* Take advantage of short-circuit false optimization for AND */ p = sqlite3ExprAnd(pParse->db, pLeft, pRight); }else{ @@ -81862,20 +83002,24 @@ SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ } /* -** These routines are Walker callbacks. Walker.u.pi is a pointer -** to an integer. These routines are checking an expression to see -** if it is a constant. Set *Walker.u.i to 0 if the expression is -** not constant. +** These routines are Walker callbacks used to check expressions to +** see if they are "constant" for some definition of constant. The +** Walker.eCode value determines the type of "constant" we are looking +** for. ** ** These callback routines are used to implement the following: ** -** sqlite3ExprIsConstant() pWalker->u.i==1 -** sqlite3ExprIsConstantNotJoin() pWalker->u.i==2 -** sqlite3ExprIsConstantOrFunction() pWalker->u.i==3 or 4 +** sqlite3ExprIsConstant() pWalker->eCode==1 +** sqlite3ExprIsConstantNotJoin() pWalker->eCode==2 +** sqlite3ExprRefOneTableOnly() pWalker->eCode==3 +** sqlite3ExprIsConstantOrFunction() pWalker->eCode==4 or 5 +** +** In all cases, the callbacks set Walker.eCode=0 and abort if the expression +** is found to not be a constant. ** ** The sqlite3ExprIsConstantOrFunction() is used for evaluating expressions -** in a CREATE TABLE statement. The Walker.u.i value is 4 when parsing -** an existing schema and 3 when processing a new statement. A bound +** in a CREATE TABLE statement. The Walker.eCode value is 5 when parsing +** an existing schema and 4 when processing a new statement. A bound ** parameter raises an error for new statements, but is silently converted ** to NULL for existing schemas. This allows sqlite_master tables that ** contain a bound parameter because they were generated by older versions @@ -81884,23 +83028,25 @@ SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ */ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ - /* If pWalker->u.i is 2 then any term of the expression that comes from - ** the ON or USING clauses of a join disqualifies the expression + /* If pWalker->eCode is 2 then any term of the expression that comes from + ** the ON or USING clauses of a left join disqualifies the expression ** from being considered constant. */ - if( pWalker->u.i==2 && ExprHasProperty(pExpr, EP_FromJoin) ){ - pWalker->u.i = 0; + if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){ + pWalker->eCode = 0; return WRC_Abort; } switch( pExpr->op ){ /* Consider functions to be constant if all their arguments are constant - ** and either pWalker->u.i==3 or 4 or the function as the SQLITE_FUNC_CONST - ** flag. */ + ** and either pWalker->eCode==4 or 5 or the function has the + ** SQLITE_FUNC_CONST flag. */ case TK_FUNCTION: - if( pWalker->u.i>=3 || ExprHasProperty(pExpr,EP_Constant) ){ + if( pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_Constant) ){ return WRC_Continue; + }else{ + pWalker->eCode = 0; + return WRC_Abort; } - /* Fall through */ case TK_ID: case TK_COLUMN: case TK_AGG_FUNCTION: @@ -81909,18 +83055,22 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); testcase( pExpr->op==TK_AGG_COLUMN ); - pWalker->u.i = 0; - return WRC_Abort; + if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){ + return WRC_Continue; + }else{ + pWalker->eCode = 0; + return WRC_Abort; + } case TK_VARIABLE: - if( pWalker->u.i==4 ){ + if( pWalker->eCode==5 ){ /* Silently convert bound parameters that appear inside of CREATE ** statements into a NULL when parsing the CREATE statement text out ** of the sqlite_master table */ pExpr->op = TK_NULL; - }else if( pWalker->u.i==3 ){ + }else if( pWalker->eCode==4 ){ /* A bound parameter in a CREATE statement that originates from ** sqlite3_prepare() causes an error */ - pWalker->u.i = 0; + pWalker->eCode = 0; return WRC_Abort; } /* Fall through */ @@ -81932,21 +83082,22 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ } static int selectNodeIsConstant(Walker *pWalker, Select *NotUsed){ UNUSED_PARAMETER(NotUsed); - pWalker->u.i = 0; + pWalker->eCode = 0; return WRC_Abort; } -static int exprIsConst(Expr *p, int initFlag){ +static int exprIsConst(Expr *p, int initFlag, int iCur){ Walker w; memset(&w, 0, sizeof(w)); - w.u.i = initFlag; + w.eCode = initFlag; w.xExprCallback = exprNodeIsConstant; w.xSelectCallback = selectNodeIsConstant; + w.u.iCur = iCur; sqlite3WalkExpr(&w, p); - return w.u.i; + return w.eCode; } /* -** Walk an expression tree. Return 1 if the expression is constant +** Walk an expression tree. Return non-zero if the expression is constant ** and 0 if it involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") @@ -81954,21 +83105,31 @@ static int exprIsConst(Expr *p, int initFlag){ ** a constant. */ SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){ - return exprIsConst(p, 1); + return exprIsConst(p, 1, 0); } /* -** Walk an expression tree. Return 1 if the expression is constant +** Walk an expression tree. Return non-zero if the expression is constant ** that does no originate from the ON or USING clauses of a join. ** Return 0 if it involves variables or function calls or terms from ** an ON or USING clause. */ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ - return exprIsConst(p, 2); + return exprIsConst(p, 2, 0); } /* -** Walk an expression tree. Return 1 if the expression is constant +** Walk an expression tree. Return non-zero if the expression constant +** for any single row of the table with cursor iCur. In other words, the +** expression must not refer to any non-deterministic function nor any +** table other than iCur. +*/ +SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){ + return exprIsConst(p, 3, iCur); +} + +/* +** Walk an expression tree. Return non-zero if the expression is constant ** or a function call with constant arguments. Return and 0 if there ** are any variables. ** @@ -81978,7 +83139,7 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ */ SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ assert( isInit==0 || isInit==1 ); - return exprIsConst(p, 3+isInit); + return exprIsConst(p, 4+isInit, 0); } /* @@ -83635,7 +84796,10 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) #ifndef SQLITE_OMIT_FLOATING_POINT /* If the column has REAL affinity, it may currently be stored as an - ** integer. Use OP_RealAffinity to make sure it is really real. */ + ** integer. Use OP_RealAffinity to make sure it is really real. + ** + ** EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to + ** floating point when extracting it from the record. */ if( pExpr->iColumn>=0 && pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL ){ @@ -84697,10 +85861,11 @@ static int exprSrcCount(Walker *pWalker, Expr *pExpr){ int i; struct SrcCount *p = pWalker->u.pSrcCount; SrcList *pSrc = p->pSrc; - for(i=0; inSrc; i++){ + int nSrc = pSrc ? pSrc->nSrc : 0; + for(i=0; iiTable==pSrc->a[i].iCursor ) break; } - if( inSrc ){ + if( inThis++; }else{ p->nOther++; @@ -86278,7 +87443,7 @@ static void statInit( p->mxSample = mxSample; p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1); p->current.anLt = &p->current.anEq[nColUp]; - p->iPrn = nCol*0x689e962d ^ sqlite3_value_int(argv[2])*0xd0944565; + p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]); /* Set up the Stat4Accum.a[] and aBest[] arrays */ p->a = (struct Stat4Sample*)&p->current.anLt[nColUp]; @@ -87287,23 +88452,28 @@ static void decodeIntArray( if( *z==' ' ) z++; } #ifndef SQLITE_ENABLE_STAT3_OR_STAT4 - assert( pIndex!=0 ); + assert( pIndex!=0 ); { #else - if( pIndex ) + if( pIndex ){ #endif - while( z[0] ){ - if( sqlite3_strglob("unordered*", z)==0 ){ - pIndex->bUnordered = 1; - }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){ - pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3)); - } + pIndex->bUnordered = 0; + pIndex->noSkipScan = 0; + while( z[0] ){ + if( sqlite3_strglob("unordered*", z)==0 ){ + pIndex->bUnordered = 1; + }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){ + pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3)); + }else if( sqlite3_strglob("noskipscan*", z)==0 ){ + pIndex->noSkipScan = 1; + } #ifdef SQLITE_ENABLE_COSTMULT - else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){ - pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9)); - } + else if( sqlite3_strglob("costmult=[0-9]*",z)==0 ){ + pIndex->pTable->costMult = sqlite3LogEst(sqlite3Atoi(z+9)); + } #endif - while( z[0]!=0 && z[0]!=' ' ) z++; - while( z[0]==' ' ) z++; + while( z[0]!=0 && z[0]!=' ' ) z++; + while( z[0]==' ' ) z++; + } } } @@ -87429,6 +88599,7 @@ static void initAvgEq(Index *pIdx){ nRow = pIdx->aiRowEst[0]; nDist100 = ((i64)100 * pIdx->aiRowEst[0]) / pIdx->aiRowEst[iCol+1]; } + pIdx->nRowEst0 = nRow; /* Set nSum to the number of distinct (iCol+1) field prefixes that ** occur in the stat4 table for this index. Set sumEq to the sum of @@ -87690,7 +88861,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load the statistics from the sqlite_stat4 table. */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){ int lookasideEnabled = db->lookaside.bEnabled; db->lookaside.bEnabled = 0; rc = loadStat4(db, sInfo.zDatabase); @@ -87865,6 +89036,7 @@ static void attachFunc( "attached databases must use the same text encoding as main database"); rc = SQLITE_ERROR; } + sqlite3BtreeEnter(aNew->pBt); pPager = sqlite3BtreePager(aNew->pBt); sqlite3PagerLockingMode(pPager, db->dfltLockMode); sqlite3BtreeSecureDelete(aNew->pBt, @@ -87872,6 +89044,7 @@ static void attachFunc( #ifndef SQLITE_OMIT_PAGER_PRAGMAS sqlite3BtreeSetPagerFlags(aNew->pBt, 3 | (db->flags & PAGER_FLAGS_MASK)); #endif + sqlite3BtreeLeave(aNew->pBt); } aNew->safety_level = 3; aNew->zName = sqlite3DbStrDup(db, zName); @@ -88372,6 +89545,9 @@ SQLITE_API int sqlite3_set_authorizer( int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), void *pArg ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->xAuth = (sqlite3_xauth)xAuth; db->pAuthArg = pArg; @@ -88866,7 +90042,11 @@ SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const char *zDatabase){ Table *p = 0; int i; - assert( zName!=0 ); + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return 0; +#endif + /* All mutexes are required for schema access. Make sure we hold them. */ assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); #if SQLITE_USER_AUTHENTICATION @@ -88990,7 +90170,6 @@ static void freeIndex(sqlite3 *db, Index *p){ #ifndef SQLITE_OMIT_ANALYZE sqlite3DeleteIndexSamples(db, p); #endif - if( db==0 || db->pnBytesFreed==0 ) sqlite3KeyInfoUnref(p->pKeyInfo); sqlite3ExprDelete(db, p->pPartIdxWhere); sqlite3DbFree(db, p->zColAff); if( p->isResized ) sqlite3DbFree(db, p->azColl); @@ -90269,6 +91448,19 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ pTab->iPKey = -1; }else{ pPk = sqlite3PrimaryKeyIndex(pTab); + /* + ** Remove all redundant columns from the PRIMARY KEY. For example, change + ** "PRIMARY KEY(a,b,a,b,c,b,c,d)" into just "PRIMARY KEY(a,b,c,d)". Later + ** code assumes the PRIMARY KEY contains no repeated columns. + */ + for(i=j=1; inKeyCol; i++){ + if( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ){ + pPk->nColumn--; + }else{ + pPk->aiColumn[j++] = pPk->aiColumn[i]; + } + } + pPk->nKeyCol = j; } pPk->isCovering = 1; assert( pPk!=0 ); @@ -92745,40 +93937,31 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ ** when it has finished using it. */ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ + int i; + int nCol = pIdx->nColumn; + int nKey = pIdx->nKeyCol; + KeyInfo *pKey; if( pParse->nErr ) return 0; -#ifndef SQLITE_OMIT_SHARED_CACHE - if( pIdx->pKeyInfo && pIdx->pKeyInfo->db!=pParse->db ){ - sqlite3KeyInfoUnref(pIdx->pKeyInfo); - pIdx->pKeyInfo = 0; + if( pIdx->uniqNotNull ){ + pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); + }else{ + pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); } -#endif - if( pIdx->pKeyInfo==0 ){ - int i; - int nCol = pIdx->nColumn; - int nKey = pIdx->nKeyCol; - KeyInfo *pKey; - if( pIdx->uniqNotNull ){ - pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); - }else{ - pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); + if( pKey ){ + assert( sqlite3KeyInfoIsWriteable(pKey) ); + for(i=0; iazColl[i]; + assert( zColl!=0 ); + pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 : + sqlite3LocateCollSeq(pParse, zColl); + pKey->aSortOrder[i] = pIdx->aSortOrder[i]; } - if( pKey ){ - assert( sqlite3KeyInfoIsWriteable(pKey) ); - for(i=0; iazColl[i]; - assert( zColl!=0 ); - pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 : - sqlite3LocateCollSeq(pParse, zColl); - pKey->aSortOrder[i] = pIdx->aSortOrder[i]; - } - if( pParse->nErr ){ - sqlite3KeyInfoUnref(pKey); - }else{ - pIdx->pKeyInfo = pKey; - } + if( pParse->nErr ){ + sqlite3KeyInfoUnref(pKey); + pKey = 0; } } - return sqlite3KeyInfoRef(pIdx->pKeyInfo); + return pKey; } #ifndef SQLITE_OMIT_CTE @@ -93559,8 +94742,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( WhereInfo *pWInfo; /* Information about the WHERE clause */ Index *pIdx; /* For looping over indices of the table */ int iTabCur; /* Cursor number for the table */ - int iDataCur; /* VDBE cursor for the canonical data source */ - int iIdxCur; /* Cursor number of the first index */ + int iDataCur = 0; /* VDBE cursor for the canonical data source */ + int iIdxCur = 0; /* Cursor number of the first index */ int nIdx; /* Number of indices */ sqlite3 *db; /* Main database structure */ AuthContext sContext; /* Authorization context */ @@ -94330,8 +95513,8 @@ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ default: { /* Because sqlite3_value_double() returns 0.0 if the argument is not ** something that can be converted into a number, we have: - ** IMP: R-57326-31541 Abs(X) return 0.0 if X is a string or blob that - ** cannot be converted to a numeric value. + ** IMP: R-01992-00519 Abs(X) returns 0.0 if X is a string or blob + ** that cannot be converted to a numeric value. */ double rVal = sqlite3_value_double(argv[0]); if( rVal<0 ) rVal = -rVal; @@ -96399,7 +97582,7 @@ static void fkLookupParent( OE_Abort, 0, P4_STATIC, P5_ConstraintFK); }else{ if( nIncr>0 && pFKey->isDeferred==0 ){ - sqlite3ParseToplevel(pParse)->mayAbort = 1; + sqlite3MayAbort(pParse); } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); } @@ -96471,6 +97654,10 @@ static Expr *exprTableColumn( ** code for an SQL UPDATE operation, this function may be called twice - ** once to "delete" the old row and once to "insert" the new row. ** +** Parameter nIncr is passed -1 when inserting a row (as this may decrease +** the number of FK violations in the db) or +1 when deleting one (as this +** may increase the number of FK constraint problems). +** ** The code generated by this function scans through the rows in the child ** table that correspond to the parent table row being deleted or inserted. ** For each child row found, one of the following actions is taken: @@ -96587,13 +97774,9 @@ static void fkScanChildren( sqlite3ResolveExprNames(&sNameContext, pWhere); /* Create VDBE to loop through the entries in pSrc that match the WHERE - ** clause. If the constraint is not deferred, throw an exception for - ** each row found. Otherwise, for deferred constraints, increment the - ** deferred constraint counter by nIncr for each row selected. */ + ** clause. For each row found, increment either the deferred or immediate + ** foreign key constraint counter. */ pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); - if( nIncr>0 && pFKey->isDeferred==0 ){ - sqlite3ParseToplevel(pParse)->mayAbort = 1; - } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); if( pWInfo ){ sqlite3WhereEnd(pWInfo); @@ -96772,6 +97955,24 @@ static int fkParentIsModified( return 0; } +/* +** Return true if the parser passed as the first argument is being +** used to code a trigger that is really a "SET NULL" action belonging +** to trigger pFKey. +*/ +static int isSetNullAction(Parse *pParse, FKey *pFKey){ + Parse *pTop = sqlite3ParseToplevel(pParse); + if( pTop->pTriggerPrg ){ + Trigger *p = pTop->pTriggerPrg->pTrigger; + if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull) + || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull) + ){ + return 1; + } + } + return 0; +} + /* ** This function is called when inserting, deleting or updating a row of ** table pTab to generate VDBE code to perform foreign key constraint @@ -96824,7 +98025,7 @@ SQLITE_PRIVATE void sqlite3FkCheck( int *aiCol; int iCol; int i; - int isIgnore = 0; + int bIgnore = 0; if( aChange && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 @@ -96883,7 +98084,7 @@ SQLITE_PRIVATE void sqlite3FkCheck( int rcauth; char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); - isIgnore = (rcauth==SQLITE_IGNORE); + bIgnore = (rcauth==SQLITE_IGNORE); } #endif } @@ -96898,12 +98099,18 @@ SQLITE_PRIVATE void sqlite3FkCheck( /* A row is being removed from the child table. Search for the parent. ** If the parent does not exist, removing the child row resolves an ** outstanding foreign key constraint violation. */ - fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1,isIgnore); + fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore); } - if( regNew!=0 ){ + if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){ /* A row is being added to the child table. If a parent row cannot - ** be found, adding the child row has violated the FK constraint. */ - fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1,isIgnore); + ** be found, adding the child row has violated the FK constraint. + ** + ** If this operation is being performed as part of a trigger program + ** that is actually a "SET NULL" action belonging to this very + ** foreign key, then omit this scan altogether. As all child key + ** values are guaranteed to be NULL, it is not possible for adding + ** this row to cause an FK violation. */ + fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore); } sqlite3DbFree(db, aiFree); @@ -96924,8 +98131,8 @@ SQLITE_PRIVATE void sqlite3FkCheck( && !pParse->pToplevel && !pParse->isMultiWrite ){ assert( regOld==0 && regNew!=0 ); - /* Inserting a single row into a parent table cannot cause an immediate - ** foreign key violation. So do nothing in this case. */ + /* Inserting a single row into a parent table cannot cause (or fix) + ** an immediate foreign key violation. So do nothing in this case. */ continue; } @@ -96949,13 +98156,28 @@ SQLITE_PRIVATE void sqlite3FkCheck( fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1); } if( regOld!=0 ){ - /* If there is a RESTRICT action configured for the current operation - ** on the parent table of this FK, then throw an exception - ** immediately if the FK constraint is violated, even if this is a - ** deferred trigger. That's what RESTRICT means. To defer checking - ** the constraint, the FK should specify NO ACTION (represented - ** using OE_None). NO ACTION is the default. */ + int eAction = pFKey->aAction[aChange!=0]; fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); + /* If this is a deferred FK constraint, or a CASCADE or SET NULL + ** action applies, then any foreign key violations caused by + ** removing the parent key will be rectified by the action trigger. + ** So do not set the "may-abort" flag in this case. + ** + ** Note 1: If the FK is declared "ON UPDATE CASCADE", then the + ** may-abort flag will eventually be set on this statement anyway + ** (when this function is called as part of processing the UPDATE + ** within the action trigger). + ** + ** Note 2: At first glance it may seem like SQLite could simply omit + ** all OP_FkCounter related scans when either CASCADE or SET NULL + ** applies. The trouble starts if the CASCADE or SET NULL action + ** trigger causes other triggers or action rules attached to the + ** child table to fire. In these cases the fk constraint counters + ** might be set incorrectly if any OP_FkCounter related scans are + ** omitted. */ + if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){ + sqlite3MayAbort(pParse); + } } pItem->zName = 0; sqlite3SrcListDelete(db, pSrc); @@ -100049,7 +101271,6 @@ struct sqlite3_api_routines { # define sqlite3_column_table_name16 0 # define sqlite3_column_origin_name 0 # define sqlite3_column_origin_name16 0 -# define sqlite3_table_column_metadata 0 #endif #ifdef SQLITE_OMIT_AUTHORIZATION @@ -100859,6 +102080,7 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ #define PragTyp_LOCK_STATUS 40 #define PragTyp_PARSER_TRACE 41 #define PragFlag_NeedSchema 0x01 +#define PragFlag_ReadOnly 0x02 static const struct sPragmaNames { const char *const zName; /* Name of pragma */ u8 ePragTyp; /* PragTyp_XXX value */ @@ -100875,7 +102097,7 @@ static const struct sPragmaNames { { /* zName: */ "application_id", /* ePragTyp: */ PragTyp_HEADER_VALUE, /* ePragFlag: */ 0, - /* iArg: */ 0 }, + /* iArg: */ BTREE_APPLICATION_ID }, #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) { /* zName: */ "auto_vacuum", @@ -100941,6 +102163,12 @@ static const struct sPragmaNames { /* ePragFlag: */ 0, /* iArg: */ 0 }, #endif +#if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) + { /* zName: */ "data_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlag: */ PragFlag_ReadOnly, + /* iArg: */ BTREE_DATA_VERSION }, +#endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) { /* zName: */ "database_list", /* ePragTyp: */ PragTyp_DATABASE_LIST, @@ -100996,8 +102224,8 @@ static const struct sPragmaNames { #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) { /* zName: */ "freelist_count", /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + /* ePragFlag: */ PragFlag_ReadOnly, + /* iArg: */ BTREE_FREE_PAGE_COUNT }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) { /* zName: */ "full_column_names", @@ -101149,7 +102377,7 @@ static const struct sPragmaNames { { /* zName: */ "schema_version", /* ePragTyp: */ PragTyp_HEADER_VALUE, /* ePragFlag: */ 0, - /* iArg: */ 0 }, + /* iArg: */ BTREE_SCHEMA_VERSION }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) { /* zName: */ "secure_delete", @@ -101215,7 +102443,7 @@ static const struct sPragmaNames { { /* zName: */ "user_version", /* ePragTyp: */ PragTyp_HEADER_VALUE, /* ePragFlag: */ 0, - /* iArg: */ 0 }, + /* iArg: */ BTREE_USER_VERSION }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if defined(SQLITE_DEBUG) @@ -101258,7 +102486,7 @@ static const struct sPragmaNames { /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, #endif }; -/* Number of pragmas: 57 on by default, 70 total. */ +/* Number of pragmas: 58 on by default, 71 total. */ /* End of the automatically generated pragma table. ***************************************************************************/ @@ -101508,7 +102736,7 @@ SQLITE_PRIVATE void sqlite3Pragma( Token *pId; /* Pointer to token */ char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */ int iDb; /* Database index for */ - int lwr, upr, mid; /* Binary search bounds */ + int lwr, upr, mid = 0; /* Binary search bounds */ int rc; /* return value form SQLITE_FCNTL_PRAGMA */ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* The specific database being pragmaed */ @@ -102868,7 +104096,8 @@ SQLITE_PRIVATE void sqlite3Pragma( ){ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){ - ENC(pParse->db) = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; + SCHEMA_ENC(db) = ENC(db) = + pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; break; } } @@ -102913,24 +104142,9 @@ SQLITE_PRIVATE void sqlite3Pragma( ** applications for any purpose. */ case PragTyp_HEADER_VALUE: { - int iCookie; /* Cookie index. 1 for schema-cookie, 6 for user-cookie. */ + int iCookie = aPragmaNames[mid].iArg; /* Which cookie to read or write */ sqlite3VdbeUsesBtree(v, iDb); - switch( zLeft[0] ){ - case 'a': case 'A': - iCookie = BTREE_APPLICATION_ID; - break; - case 'f': case 'F': - iCookie = BTREE_FREE_PAGE_COUNT; - break; - case 's': case 'S': - iCookie = BTREE_SCHEMA_VERSION; - break; - default: - iCookie = BTREE_USER_VERSION; - break; - } - - if( zRight && iCookie!=BTREE_FREE_PAGE_COUNT ){ + if( zRight && (aPragmaNames[mid].mPragFlag & PragFlag_ReadOnly)==0 ){ /* Write the specified cookie value */ static const VdbeOpList setCookie[] = { { OP_Transaction, 0, 1, 0}, /* 0 */ @@ -102983,7 +104197,7 @@ SQLITE_PRIVATE void sqlite3Pragma( #ifndef SQLITE_OMIT_WAL /* - ** PRAGMA [database.]wal_checkpoint = passive|full|restart + ** PRAGMA [database.]wal_checkpoint = passive|full|restart|truncate ** ** Checkpoint the database. */ @@ -102995,6 +104209,8 @@ SQLITE_PRIVATE void sqlite3Pragma( eMode = SQLITE_CHECKPOINT_FULL; }else if( sqlite3StrICmp(zRight, "restart")==0 ){ eMode = SQLITE_CHECKPOINT_RESTART; + }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ + eMode = SQLITE_CHECKPOINT_TRUNCATE; } } sqlite3VdbeSetNumCols(v, 3); @@ -103574,9 +104790,11 @@ SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){ int commit_internal = !(db->flags&SQLITE_InternChanges); assert( sqlite3_mutex_held(db->mutex) ); + assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) ); assert( db->init.busy==0 ); rc = SQLITE_OK; db->init.busy = 1; + ENC(db) = SCHEMA_ENC(db); for(i=0; rc==SQLITE_OK && inDb; i++){ if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue; rc = sqlite3InitOne(db, i, pzErrMsg); @@ -103889,9 +105107,12 @@ static int sqlite3LockAndPrepare( const char **pzTail /* OUT: End of parsed string */ ){ int rc; - assert( ppStmt!=0 ); + +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppStmt = 0; - if( !sqlite3SafetyCheckOk(db) ){ + if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(db->mutex); @@ -103998,9 +105219,11 @@ static int sqlite3Prepare16( const char *zTail8 = 0; int rc = SQLITE_OK; - assert( ppStmt ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppStmt = 0; - if( !sqlite3SafetyCheckOk(db) ){ + if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ return SQLITE_MISUSE_BKPT; } if( nBytes>=0 ){ @@ -104126,20 +105349,25 @@ struct SortCtx { #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ /* -** Delete all the content of a Select structure but do not deallocate -** the select structure itself. +** Delete all the content of a Select structure. Deallocate the structure +** itself only if bFree is true. */ -static void clearSelect(sqlite3 *db, Select *p){ - sqlite3ExprListDelete(db, p->pEList); - sqlite3SrcListDelete(db, p->pSrc); - sqlite3ExprDelete(db, p->pWhere); - sqlite3ExprListDelete(db, p->pGroupBy); - sqlite3ExprDelete(db, p->pHaving); - sqlite3ExprListDelete(db, p->pOrderBy); - sqlite3SelectDelete(db, p->pPrior); - sqlite3ExprDelete(db, p->pLimit); - sqlite3ExprDelete(db, p->pOffset); - sqlite3WithDelete(db, p->pWith); +static void clearSelect(sqlite3 *db, Select *p, int bFree){ + while( p ){ + Select *pPrior = p->pPrior; + sqlite3ExprListDelete(db, p->pEList); + sqlite3SrcListDelete(db, p->pSrc); + sqlite3ExprDelete(db, p->pWhere); + sqlite3ExprListDelete(db, p->pGroupBy); + sqlite3ExprDelete(db, p->pHaving); + sqlite3ExprListDelete(db, p->pOrderBy); + sqlite3ExprDelete(db, p->pLimit); + sqlite3ExprDelete(db, p->pOffset); + sqlite3WithDelete(db, p->pWith); + if( bFree ) sqlite3DbFree(db, p); + p = pPrior; + bFree = 1; + } } /* @@ -104198,8 +105426,7 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( pNew->addrOpenEphm[0] = -1; pNew->addrOpenEphm[1] = -1; if( db->mallocFailed ) { - clearSelect(db, pNew); - if( pNew!=&standin ) sqlite3DbFree(db, pNew); + clearSelect(db, pNew, pNew!=&standin); pNew = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); @@ -104224,10 +105451,7 @@ SQLITE_PRIVATE void sqlite3SelectSetName(Select *p, const char *zName){ ** Delete the given Select structure and all of its substructures. */ SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){ - if( p ){ - clearSelect(db, p); - sqlite3DbFree(db, p); - } + clearSelect(db, p, 1); } /* @@ -104610,7 +105834,9 @@ static void pushOntoSorter( pKI = pOp->p4.pKeyInfo; memset(pKI->aSortOrder, 0, pKI->nField); /* Makes OP_Jump below testable */ sqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO); - pOp->p4.pKeyInfo = keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat, 1); + testcase( pKI->nXField>2 ); + pOp->p4.pKeyInfo = keyInfoFromExprList(pParse, pSort->pOrderBy, nOBSat, + pKI->nXField-1); addrJmp = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v); pSort->labelBkOut = sqlite3VdbeMakeLabel(v); @@ -105121,7 +106347,7 @@ static KeyInfo *keyInfoFromExprList( int i; nExpr = pList->nExpr; - pInfo = sqlite3KeyInfoAlloc(db, nExpr+nExtra-iStart, 1); + pInfo = sqlite3KeyInfoAlloc(db, nExpr-iStart, nExtra+1); if( pInfo ){ assert( sqlite3KeyInfoIsWriteable(pInfo) ); for(i=iStart, pItem=pList->a+iStart; iselFlags & SF_Values ){ + sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); + }else{ + sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" + " do not have the same number of result columns", selectOpName(p->op)); + } +} + +/* +** Handle the special case of a compound-select that originates from a +** VALUES clause. By handling this as a special case, we avoid deep +** recursion, and thus do not need to enforce the SQLITE_LIMIT_COMPOUND_SELECT +** on a VALUES clause. +** +** Because the Select object originates from a VALUES clause: +** (1) It has no LIMIT or OFFSET +** (2) All terms are UNION ALL +** (3) There is no ORDER BY clause +*/ +static int multiSelectValues( + Parse *pParse, /* Parsing context */ + Select *p, /* The right-most of SELECTs to be coded */ + SelectDest *pDest /* What to do with query results */ +){ + Select *pPrior; + int nExpr = p->pEList->nExpr; + int nRow = 1; + int rc = 0; + assert( p->pNext==0 ); + assert( p->selFlags & SF_AllValues ); + do{ + assert( p->selFlags & SF_Values ); + assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) ); + assert( p->pLimit==0 ); + assert( p->pOffset==0 ); + if( p->pEList->nExpr!=nExpr ){ + selectWrongNumTermsError(pParse, p); + return 1; + } + if( p->pPrior==0 ) break; + assert( p->pPrior->pNext==p ); + p = p->pPrior; + nRow++; + }while(1); + while( p ){ + pPrior = p->pPrior; + p->pPrior = 0; + rc = sqlite3Select(pParse, p, pDest); + p->pPrior = pPrior; + if( rc ) break; + p->nSelectRow = nRow; + p = p->pNext; + } + return rc; +} /* ** This routine is called to process a compound query form from @@ -106224,17 +107510,19 @@ static int multiSelect( dest.eDest = SRT_Table; } + /* Special handling for a compound-select that originates as a VALUES clause. + */ + if( p->selFlags & SF_AllValues ){ + rc = multiSelectValues(pParse, p, &dest); + goto multi_select_end; + } + /* Make sure all SELECTs in the statement have the same number of elements ** in their result sets. */ assert( p->pEList && pPrior->pEList ); if( p->pEList->nExpr!=pPrior->pEList->nExpr ){ - if( p->selFlags & SF_Values ){ - sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); - }else{ - sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" - " do not have the same number of result columns", selectOpName(p->op)); - } + selectWrongNumTermsError(pParse, p); rc = 1; goto multi_select_end; } @@ -108120,7 +109408,9 @@ static int selectExpander(Walker *pWalker, Select *p){ } pTabList = p->pSrc; pEList = p->pEList; - sqlite3WithPush(pParse, findRightmost(p)->pWith, 0); + if( pWalker->xSelectCallback2==selectPopWith ){ + sqlite3WithPush(pParse, findRightmost(p)->pWith, 0); + } /* Make sure cursor numbers have been assigned to all entries in ** the FROM clause of the SELECT statement. @@ -108411,7 +109701,9 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; - w.xSelectCallback2 = selectPopWith; + if( (pSelect->selFlags & SF_AllValues)==0 ){ + w.xSelectCallback2 = selectPopWith; + } sqlite3WalkSelect(&w, pSelect); } @@ -108897,7 +110189,7 @@ SQLITE_PRIVATE int sqlite3Select( ** ** is transformed to: ** - ** SELECT xyz FROM ... GROUP BY xyz + ** SELECT xyz FROM ... GROUP BY xyz ORDER BY xyz ** ** The second form is preferred as a single index (or temp-table) may be ** used for both the ORDER BY and DISTINCT processing. As originally @@ -108910,7 +110202,6 @@ SQLITE_PRIVATE int sqlite3Select( p->selFlags &= ~SF_Distinct; p->pGroupBy = sqlite3ExprListDup(db, p->pEList, 0); pGroupBy = p->pGroupBy; - sSort.pOrderBy = 0; /* Notice that even thought SF_Distinct has been cleared from p->selFlags, ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ @@ -108926,7 +110217,7 @@ SQLITE_PRIVATE int sqlite3Select( */ if( sSort.pOrderBy ){ KeyInfo *pKeyInfo; - pKeyInfo = keyInfoFromExprList(pParse, sSort.pOrderBy, 0, 0); + pKeyInfo = keyInfoFromExprList(pParse, sSort.pOrderBy, 0, pEList->nExpr); sSort.iECursor = pParse->nTab++; sSort.addrSortIndex = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, @@ -109100,7 +110391,7 @@ SQLITE_PRIVATE int sqlite3Select( ** will be converted into a Noop. */ sAggInfo.sortingIdx = pParse->nTab++; - pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0, 0); + pKeyInfo = keyInfoFromExprList(pParse, pGroupBy, 0, sAggInfo.nColumn); addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, sAggInfo.sortingIdx, sAggInfo.nSortingColumn, 0, (char*)pKeyInfo, P4_KEYINFO); @@ -109713,6 +111004,9 @@ SQLITE_API int sqlite3_get_table( int rc; TabResult res; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pazResult==0 ) return SQLITE_MISUSE_BKPT; +#endif *pazResult = 0; if( pnColumn ) *pnColumn = 0; if( pnRow ) *pnRow = 0; @@ -111776,7 +113070,7 @@ static int execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ ** overwriting the database with the vacuumed content. ** ** Only 1x temporary space and only 1x writes would be required if -** the copy of step (3) were replace by deleting the original database +** the copy of step (3) were replaced by deleting the original database ** and renaming the transient database as the original. But that will ** not work if other processes are attached to the original database. ** And a power loss in between deleting the original and renaming the @@ -112134,6 +113428,9 @@ SQLITE_API int sqlite3_create_module( const sqlite3_module *pModule, /* The definition of the module */ void *pAux /* Context pointer for xCreate/xConnect */ ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif return createModule(db, zName, pModule, pAux, 0); } @@ -112147,6 +113444,9 @@ SQLITE_API int sqlite3_create_module_v2( void *pAux, /* Context pointer for xCreate/xConnect */ void (*xDestroy)(void *) /* Module destructor function */ ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif return createModule(db, zName, pModule, pAux, xDestroy); } @@ -112379,7 +113679,12 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( addModuleArgument(db, pTable, sqlite3NameFromToken(db, pModuleName)); addModuleArgument(db, pTable, 0); addModuleArgument(db, pTable, sqlite3DbStrDup(db, pTable->zName)); - pParse->sNameToken.n = (int)(&pModuleName->z[pModuleName->n] - pName1->z); + assert( (pParse->sNameToken.z==pName2->z && pName2->z!=0) + || (pParse->sNameToken.z==pName1->z && pName2->z==0) + ); + pParse->sNameToken.n = (int)( + &pModuleName->z[pModuleName->n] - pParse->sNameToken.z + ); #ifndef SQLITE_OMIT_AUTHORIZATION /* Creating a virtual table invokes the authorization callback twice. @@ -112751,6 +114056,9 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ Table *pTab; char *zErr = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); if( !db->pVtabCtx || !(pTab = db->pVtabCtx->pTab) ){ sqlite3Error(db, SQLITE_MISUSE); @@ -113107,6 +114415,9 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){ static const unsigned char aMap[] = { SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE }; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 ); assert( OE_Ignore==4 && OE_Replace==5 ); assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 ); @@ -113122,8 +114433,10 @@ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){ va_list ap; int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); - va_start(ap, op); switch( op ){ case SQLITE_VTAB_CONSTRAINT_SUPPORT: { @@ -113258,6 +114571,9 @@ struct WhereLevel { } u; struct WhereLoop *pWLoop; /* The selected WhereLoop object */ Bitmask notReady; /* FROM entries not usable at this level */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrVisit; /* Address at which row is visited */ +#endif }; /* @@ -113288,7 +114604,6 @@ struct WhereLoop { union { struct { /* Information for internal btree tables */ u16 nEq; /* Number of equality constraints */ - u16 nSkip; /* Number of initial index columns to skip */ Index *pIndex; /* Index used, or NULL */ } btree; struct { /* Information for virtual tables */ @@ -113301,12 +114616,13 @@ struct WhereLoop { } u; u32 wsFlags; /* WHERE_* flags describing the plan */ u16 nLTerm; /* Number of entries in aLTerm[] */ + u16 nSkip; /* Number of NULL aLTerm[] entries */ /**** whereLoopXfer() copies fields above ***********************/ # define WHERE_LOOP_XFER_SZ offsetof(WhereLoop,nLSlot) u16 nLSlot; /* Number of slots allocated for aLTerm[] */ WhereTerm **aLTerm; /* WhereTerms used */ WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */ - WhereTerm *aLTermSpace[4]; /* Initial aLTerm[] space */ + WhereTerm *aLTermSpace[3]; /* Initial aLTerm[] space */ }; /* This object holds the prerequisites and the cost of running a @@ -113632,6 +114948,7 @@ struct WhereInfo { #define WHERE_AUTO_INDEX 0x00004000 /* Uses an ephemeral index */ #define WHERE_SKIPSCAN 0x00008000 /* Uses the skip-scan algorithm */ #define WHERE_UNQ_WANTED 0x00010000 /* WHERE_ONEROW would have been helpful*/ +#define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */ /************** End of whereInt.h ********************************************/ /************** Continuing where we left off in where.c **********************/ @@ -113839,10 +115156,11 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u8 wtFlags){ sqlite3DbFree(db, pOld); } pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); + memset(&pWC->a[pWC->nTerm], 0, sizeof(pWC->a[0])*(pWC->nSlot-pWC->nTerm)); } pTerm = &pWC->a[idx = pWC->nTerm++]; if( p && ExprHasProperty(p, EP_Unlikely) ){ - pTerm->truthProb = sqlite3LogEst(p->iTable) - 99; + pTerm->truthProb = sqlite3LogEst(p->iTable) - 270; }else{ pTerm->truthProb = 1; } @@ -114373,6 +115691,15 @@ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ } } +/* +** Mark term iChild as being a child of term iParent +*/ +static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){ + pWC->a[iChild].iParent = iParent; + pWC->a[iChild].truthProb = pWC->a[iParent].truthProb; + pWC->a[iParent].nChild++; +} + #if !defined(SQLITE_OMIT_OR_OPTIMIZATION) && !defined(SQLITE_OMIT_SUBQUERY) /* ** Analyze a term that consists of two or more OR-connected @@ -114670,8 +115997,7 @@ static void exprAnalyzeOrTerm( testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); pTerm = &pWC->a[idxTerm]; - pWC->a[idxNew].iParent = idxTerm; - pTerm->nChild = 1; + markTermAsChild(pWC, idxNew, idxTerm); }else{ sqlite3ExprListDelete(db, pList); } @@ -114773,9 +116099,8 @@ static void exprAnalyze( idxNew = whereClauseInsert(pWC, pDup, TERM_VIRTUAL|TERM_DYNAMIC); if( idxNew==0 ) return; pNew = &pWC->a[idxNew]; - pNew->iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); pTerm = &pWC->a[idxTerm]; - pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; if( pExpr->op==TK_EQ && !ExprHasProperty(pExpr, EP_FromJoin) @@ -114832,9 +116157,8 @@ static void exprAnalyze( testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); pTerm = &pWC->a[idxTerm]; - pWC->a[idxNew].iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); } - pTerm->nChild = 2; } #endif /* SQLITE_OMIT_BETWEEN_OPTIMIZATION */ @@ -114909,9 +116233,8 @@ static void exprAnalyze( exprAnalyze(pSrc, pWC, idxNew2); pTerm = &pWC->a[idxTerm]; if( isComplete ){ - pWC->a[idxNew1].iParent = idxTerm; - pWC->a[idxNew2].iParent = idxTerm; - pTerm->nChild = 2; + markTermAsChild(pWC, idxNew1, idxTerm); + markTermAsChild(pWC, idxNew2, idxTerm); } } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ @@ -114944,9 +116267,8 @@ static void exprAnalyze( pNewTerm->leftCursor = pLeft->iTable; pNewTerm->u.leftColumn = pLeft->iColumn; pNewTerm->eOperator = WO_MATCH; - pNewTerm->iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); pTerm = &pWC->a[idxTerm]; - pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; pNewTerm->prereqAll = pTerm->prereqAll; } @@ -114967,7 +116289,7 @@ static void exprAnalyze( if( pExpr->op==TK_NOTNULL && pExpr->pLeft->op==TK_COLUMN && pExpr->pLeft->iColumn>=0 - && OptimizationEnabled(db, SQLITE_Stat3) + && OptimizationEnabled(db, SQLITE_Stat34) ){ Expr *pNewExpr; Expr *pLeft = pExpr->pLeft; @@ -114986,9 +116308,8 @@ static void exprAnalyze( pNewTerm->leftCursor = pLeft->iTable; pNewTerm->u.leftColumn = pLeft->iColumn; pNewTerm->eOperator = WO_GT; - pNewTerm->iParent = idxTerm; + markTermAsChild(pWC, idxNew, idxTerm); pTerm = &pWC->a[idxTerm]; - pTerm->nChild = 1; pTerm->wtFlags |= TERM_COPIED; pNewTerm->prereqAll = pTerm->prereqAll; } @@ -115208,6 +116529,8 @@ static void constructAutomaticIndex( Bitmask idxCols; /* Bitmap of columns used for indexing */ Bitmask extraCols; /* Bitmap of additional columns */ u8 sentWarning = 0; /* True if a warnning has been issued */ + Expr *pPartial = 0; /* Partial Index Expression */ + int iContinue = 0; /* Jump here to skip excluded rows */ /* Generate code to skip over the creation and initialization of the ** transient index on 2nd and subsequent iterations of the loop. */ @@ -115223,6 +116546,12 @@ static void constructAutomaticIndex( pLoop = pLevel->pWLoop; idxCols = 0; for(pTerm=pWC->a; pTermprereq==0 + && (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsTableConstant(pTerm->pExpr, pSrc->iCursor) ){ + pPartial = sqlite3ExprAnd(pParse->db, pPartial, + sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); + } if( termCanDriveIndex(pTerm, pSrc, notReady) ){ int iCol = pTerm->u.leftColumn; Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); @@ -115235,7 +116564,9 @@ static void constructAutomaticIndex( sentWarning = 1; } if( (idxCols & cMask)==0 ){ - if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ) return; + if( whereLoopResize(pParse->db, pLoop, nKeyCol+1) ){ + goto end_auto_index_create; + } pLoop->aLTerm[nKeyCol++] = pTerm; idxCols |= cMask; } @@ -115255,7 +116586,7 @@ static void constructAutomaticIndex( ** if they go out of sync. */ extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); - mxBitCol = (pTable->nCol >= BMS-1) ? BMS-1 : pTable->nCol; + mxBitCol = MIN(BMS-1,pTable->nCol); testcase( pTable->nCol==BMS-1 ); testcase( pTable->nCol==BMS-2 ); for(i=0; icolUsed & MASKBIT(BMS-1) ){ nKeyCol += pTable->nCol - BMS + 1; } - pLoop->wsFlags |= WHERE_COLUMN_EQ | WHERE_IDX_ONLY; /* Construct the Index object to describe this index */ pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed); - if( pIdx==0 ) return; + if( pIdx==0 ) goto end_auto_index_create; pLoop->u.btree.pIndex = pIdx; pIdx->zName = "auto-index"; pIdx->pTable = pTable; @@ -115320,18 +116650,29 @@ static void constructAutomaticIndex( VdbeComment((v, "for %s", pTable->zName)); /* Fill the automatic index with content */ + sqlite3ExprCachePush(pParse); addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v); + if( pPartial ){ + iContinue = sqlite3VdbeMakeLabel(v); + sqlite3ExprIfFalse(pParse, pPartial, iContinue, SQLITE_JUMPIFNULL); + pLoop->wsFlags |= WHERE_PARTIALIDX; + } regRecord = sqlite3GetTempReg(pParse); sqlite3GenerateIndexKey(pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0); sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); + sqlite3ExprCachePop(pParse); /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); + +end_auto_index_create: + sqlite3ExprDelete(pParse->db, pPartial); } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ @@ -115491,7 +116832,6 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ } #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */ - #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* ** Estimate the location of a particular key among all keys in an @@ -115500,9 +116840,10 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ ** aStat[0] Est. number of rows less than pVal ** aStat[1] Est. number of rows equal to pVal ** -** Return SQLITE_OK on success. +** Return the index of the sample that is the smallest sample that +** is greater than or equal to pRec. */ -static void whereKeyStats( +static int whereKeyStats( Parse *pParse, /* Database connection */ Index *pIdx, /* Index to consider domain of */ UnpackedRecord *pRec, /* Vector of values to consider */ @@ -115584,6 +116925,7 @@ static void whereKeyStats( } aStat[0] = iLower + iGap; } + return i; } #endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ @@ -115734,7 +117076,7 @@ static int whereRangeSkipScanEst( ** If either of the upper or lower bound is not present, then NULL is passed in ** place of the corresponding WhereTerm. ** -** The value in (pBuilder->pNew->u.btree.nEq) is the index of the index +** The value in (pBuilder->pNew->u.btree.nEq) is the number of the index ** column subject to the range constraint. Or, equivalently, the number of ** equality constraints optimized by the proposed index scan. For example, ** assuming index p is on t1(a, b), and the SQL query is: @@ -115750,7 +117092,7 @@ static int whereRangeSkipScanEst( ** ** When this function is called, *pnOut is set to the sqlite3LogEst() of the ** number of rows that the index scan is expected to visit without -** considering the range constraints. If nEq is 0, this is the number of +** considering the range constraints. If nEq is 0, then *pnOut is the number of ** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced) ** to account for the range constraints pLower and pUpper. ** @@ -115774,10 +117116,7 @@ static int whereRangeScanEst( Index *p = pLoop->u.btree.pIndex; int nEq = pLoop->u.btree.nEq; - if( p->nSample>0 - && nEqnSampleCol - && OptimizationEnabled(pParse->db, SQLITE_Stat3) - ){ + if( p->nSample>0 && nEqnSampleCol ){ if( nEq==pBuilder->nRecValid ){ UnpackedRecord *pRec = pBuilder->pRec; tRowcnt a[2]; @@ -115793,15 +117132,19 @@ static int whereRangeScanEst( ** is not a simple variable or literal value), the lower bound of the ** range is $P. Due to a quirk in the way whereKeyStats() works, even ** if $L is available, whereKeyStats() is called for both ($P) and - ** ($P:$L) and the larger of the two returned values used. + ** ($P:$L) and the larger of the two returned values is used. ** ** Similarly, iUpper is to be set to the estimate of the number of rows ** less than the upper bound of the range query. Where the upper bound ** is either ($P) or ($P:$U). Again, even if $U is available, both values ** of iUpper are requested of whereKeyStats() and the smaller used. + ** + ** The number of rows between the two bounds is then just iUpper-iLower. */ - tRowcnt iLower; - tRowcnt iUpper; + tRowcnt iLower; /* Rows less than the lower bound */ + tRowcnt iUpper; /* Rows less than the upper bound */ + int iLwrIdx = -2; /* aSample[] for the lower bound */ + int iUprIdx = -1; /* aSample[] for the upper bound */ if( pRec ){ testcase( pRec->nField!=pBuilder->nRecValid ); @@ -115815,7 +117158,7 @@ static int whereRangeScanEst( /* Determine iLower and iUpper using ($P) only. */ if( nEq==0 ){ iLower = 0; - iUpper = sqlite3LogEstToInt(p->aiRowLogEst[0]); + iUpper = p->nRowEst0; }else{ /* Note: this call could be optimized away - since the same values must ** have been requested when testing key $P in whereEqualScanEst(). */ @@ -115839,7 +117182,7 @@ static int whereRangeScanEst( rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); if( rc==SQLITE_OK && bOk ){ tRowcnt iNew; - whereKeyStats(pParse, p, pRec, 0, a); + iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a); iNew = a[0] + ((pLower->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); if( iNew>iLower ) iLower = iNew; nOut--; @@ -115854,7 +117197,7 @@ static int whereRangeScanEst( rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); if( rc==SQLITE_OK && bOk ){ tRowcnt iNew; - whereKeyStats(pParse, p, pRec, 1, a); + iUprIdx = whereKeyStats(pParse, p, pRec, 1, a); iNew = a[0] + ((pUpper->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); if( iNewiLower ){ nNew = sqlite3LogEst(iUpper - iLower); + /* TUNING: If both iUpper and iLower are derived from the same + ** sample, then assume they are 4x more selective. This brings + ** the estimated selectivity more in line with what it would be + ** if estimated without the use of STAT3/4 tables. */ + if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); }else{ nNew = 10; assert( 10==sqlite3LogEst(2) ); } @@ -115890,12 +117238,15 @@ static int whereRangeScanEst( nNew = whereRangeAdjust(pLower, nOut); nNew = whereRangeAdjust(pUpper, nNew); - /* TUNING: If there is both an upper and lower limit, assume the range is + /* TUNING: If there is both an upper and lower limit and neither limit + ** has an application-defined likelihood(), assume the range is ** reduced by an additional 75%. This means that, by default, an open-ended ** range query (e.g. col > ?) is assumed to match 1/4 of the rows in the ** index. While a closed range (e.g. col BETWEEN ? AND ?) is estimated to ** match 1/64 of the index. */ - if( pLower && pUpper ) nNew -= 20; + if( pLower && pLower->truthProb>0 && pUpper && pUpper->truthProb>0 ){ + nNew -= 20; + } nOut -= (pLower!=0) + (pUpper!=0); if( nNew<10 ) nNew = 10; @@ -116255,7 +117606,7 @@ static int codeAllEqualityTerms( pLoop = pLevel->pWLoop; assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ); nEq = pLoop->u.btree.nEq; - nSkip = pLoop->u.btree.nSkip; + nSkip = pLoop->nSkip; pIdx = pLoop->u.btree.pIndex; assert( pIdx!=0 ); @@ -116369,7 +117720,7 @@ static void explainAppendTerm( static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop, Table *pTab){ Index *pIndex = pLoop->u.btree.pIndex; u16 nEq = pLoop->u.btree.nEq; - u16 nSkip = pLoop->u.btree.nSkip; + u16 nSkip = pLoop->nSkip; int i, j; Column *aCol = pTab->aCol; i16 *aiColumn = pIndex->aiColumn; @@ -116400,11 +117751,14 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop, Table *pTab){ /* ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN -** command. If the query being compiled is an EXPLAIN QUERY PLAN, a single -** record is added to the output to describe the table scan strategy in -** pLevel. +** command, or if either SQLITE_DEBUG or SQLITE_ENABLE_STMT_SCANSTATUS was +** defined at compile-time. If it is not a no-op, a single OP_Explain opcode +** is added to the output to describe the table scan strategy in pLevel. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. */ -static void explainOneScan( +static int explainOneScan( Parse *pParse, /* Parse context */ SrcList *pTabList, /* Table list this loop refers to */ WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ @@ -116412,7 +117766,8 @@ static void explainOneScan( int iFrom, /* Value for "from" column of output */ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ){ -#ifndef SQLITE_DEBUG + int ret = 0; +#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) if( pParse->explain==2 ) #endif { @@ -116429,7 +117784,7 @@ static void explainOneScan( pLoop = pLevel->pWLoop; flags = pLoop->wsFlags; - if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return; + if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_ONETABLE_ONLY) ) return 0; isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) @@ -116458,6 +117813,8 @@ static void explainOneScan( if( isSearch ){ zFmt = "PRIMARY KEY"; } + }else if( flags & WHERE_PARTIALIDX ){ + zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; }else if( flags & WHERE_AUTO_INDEX ){ zFmt = "AUTOMATIC COVERING INDEX"; }else if( flags & WHERE_IDX_ONLY ){ @@ -116499,13 +117856,46 @@ static void explainOneScan( } #endif zMsg = sqlite3StrAccumFinish(&str); - sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg, P4_DYNAMIC); + ret = sqlite3VdbeAddOp4(v, OP_Explain, iId, iLevel, iFrom, zMsg,P4_DYNAMIC); } + return ret; } #else -# define explainOneScan(u,v,w,x,y,z) +# define explainOneScan(u,v,w,x,y,z) 0 #endif /* SQLITE_OMIT_EXPLAIN */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Configure the VM passed as the first argument with an +** sqlite3_stmt_scanstatus() entry corresponding to the scan used to +** implement level pLvl. Argument pSrclist is a pointer to the FROM +** clause that the scan reads data from. +** +** If argument addrExplain is not 0, it must be the address of an +** OP_Explain instruction that describes the same loop. +*/ +static void addScanStatus( + Vdbe *v, /* Vdbe to add scanstatus entry to */ + SrcList *pSrclist, /* FROM clause pLvl reads data from */ + WhereLevel *pLvl, /* Level to add scanstatus() entry for */ + int addrExplain /* Address of OP_Explain (or 0) */ +){ + const char *zObj = 0; + WhereLoop *pLoop = pLvl->pWLoop; + if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ + zObj = pLoop->u.btree.pIndex->zName; + }else{ + zObj = pSrclist->a[pLvl->iFrom].zName; + } + sqlite3VdbeScanStatus( + v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj + ); +} +#else +# define addScanStatus(a, b, c, d) ((void)d) +#endif + + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause @@ -116806,7 +118196,7 @@ static Bitmask codeOneLoopStart( pIdx = pLoop->u.btree.pIndex; iIdxCur = pLevel->iIdxCur; - assert( nEq>=pLoop->u.btree.nSkip ); + assert( nEq>=pLoop->nSkip ); /* If this loop satisfies a sort order (pOrderBy) request that ** was passed to this function to implement a "SELECT min(x) ..." @@ -116823,7 +118213,7 @@ static Bitmask codeOneLoopStart( && pWInfo->nOBSat>0 && (pIdx->nKeyCol>nEq) ){ - assert( pLoop->u.btree.nSkip==0 ); + assert( pLoop->nSkip==0 ); bSeekPastNull = 1; nExtraReg = 1; } @@ -117136,10 +118526,9 @@ static Bitmask codeOneLoopStart( Expr *pExpr = pWC->a[iTerm].pExpr; if( &pWC->a[iTerm] == pTerm ) continue; if( ExprHasProperty(pExpr, EP_FromJoin) ) continue; - testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO ); - testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL ); - if( pWC->a[iTerm].wtFlags & (TERM_ORINFO|TERM_VIRTUAL) ) continue; + if( (pWC->a[iTerm].wtFlags & TERM_VIRTUAL)!=0 ) continue; if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue; + testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO ); pExpr = sqlite3ExprDup(db, pExpr, 0); pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr); } @@ -117172,9 +118561,11 @@ static Bitmask codeOneLoopStart( assert( pSubWInfo || pParse->nErr || db->mallocFailed ); if( pSubWInfo ){ WhereLoop *pSubLoop; - explainOneScan( + int addrExplain = explainOneScan( pParse, pOrTab, &pSubWInfo->a[0], iLevel, pLevel->iFrom, 0 ); + addScanStatus(v, pOrTab, &pSubWInfo->a[0], addrExplain); + /* This is the sub-WHERE clause body. First skip over ** duplicate rows from prior sub-WHERE clauses, and record the ** rowid (or PRIMARY KEY) for the current row so that the same @@ -117305,6 +118696,10 @@ static Bitmask codeOneLoopStart( } } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pLevel->addrVisit = sqlite3VdbeCurrentAddr(v); +#endif + /* Insert code to test every subexpression that can be completely ** computed using the current set of tables. */ @@ -117444,7 +118839,7 @@ static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){ sqlite3_free(z); } if( p->wsFlags & WHERE_SKIPSCAN ){ - sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->u.btree.nSkip); + sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); }else{ sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm); } @@ -117480,7 +118875,6 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ p->u.vtab.idxStr = 0; }else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){ sqlite3DbFree(db, p->u.btree.pIndex->zColAff); - sqlite3KeyInfoUnref(p->u.btree.pIndex->pKeyInfo); sqlite3DbFree(db, p->u.btree.pIndex); p->u.btree.pIndex = 0; } @@ -117555,10 +118949,11 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ } /* -** Return TRUE if both of the following are true: +** Return TRUE if all of the following are true: ** ** (1) X has the same or lower cost that Y ** (2) X is a proper subset of Y +** (3) X skips at least as many columns as Y ** ** By "proper subset" we mean that X uses fewer WHERE clause terms ** than Y and that every WHERE clause term used by X is also used @@ -117566,19 +118961,25 @@ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ ** ** If X is a proper subset of Y then Y is a better choice and ought ** to have a lower cost. This routine returns TRUE when that cost -** relationship is inverted and needs to be adjusted. +** relationship is inverted and needs to be adjusted. The third rule +** was added because if X uses skip-scan less than Y it still might +** deserve a lower cost even if it is a proper subset of Y. */ static int whereLoopCheaperProperSubset( const WhereLoop *pX, /* First WhereLoop to compare */ const WhereLoop *pY /* Compare against this WhereLoop */ ){ int i, j; - if( pX->nLTerm >= pY->nLTerm ) return 0; /* X is not a subset of Y */ + if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ + return 0; /* X is not a subset of Y */ + } + if( pY->nSkip > pX->nSkip ) return 0; if( pX->rRun >= pY->rRun ){ if( pX->rRun > pY->rRun ) return 0; /* X costs more than Y */ if( pX->nOut > pY->nOut ) return 0; /* X costs more than Y */ } for(i=pX->nLTerm-1; i>=0; i--){ + if( pX->aLTerm[i]==0 ) continue; for(j=pY->nLTerm-1; j>=0; j--){ if( pY->aLTerm[j]==pX->aLTerm[i] ) break; } @@ -117600,33 +119001,24 @@ static int whereLoopCheaperProperSubset( ** To say "WhereLoop X is a proper subset of Y" means that X uses fewer ** WHERE clause terms than Y and that every WHERE clause term used by X is ** also used by Y. -** -** This adjustment is omitted for SKIPSCAN loops. In a SKIPSCAN loop, the -** WhereLoop.nLTerm field is not an accurate measure of the number of WHERE -** clause terms covered, since some of the first nLTerm entries in aLTerm[] -** will be NULL (because they are skipped). That makes it more difficult -** to compare the loops. We could add extra code to do the comparison, and -** perhaps we will someday. But SKIPSCAN is sufficiently uncommon, and this -** adjustment is sufficient minor, that it is very difficult to construct -** a test case where the extra code would improve the query plan. Better -** to avoid the added complexity and just omit cost adjustments to SKIPSCAN -** loops. */ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ if( (pTemplate->wsFlags & WHERE_INDEXED)==0 ) return; - if( (pTemplate->wsFlags & WHERE_SKIPSCAN)!=0 ) return; for(; p; p=p->pNextLoop){ if( p->iTab!=pTemplate->iTab ) continue; if( (p->wsFlags & WHERE_INDEXED)==0 ) continue; - if( (p->wsFlags & WHERE_SKIPSCAN)!=0 ) continue; if( whereLoopCheaperProperSubset(p, pTemplate) ){ /* Adjust pTemplate cost downward so that it is cheaper than its - ** subset p */ + ** subset p. */ + WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", + pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut-1)); pTemplate->rRun = p->rRun; pTemplate->nOut = p->nOut - 1; }else if( whereLoopCheaperProperSubset(pTemplate, p) ){ /* Adjust pTemplate cost upward so that it is costlier than p since ** pTemplate is a proper subset of p */ + WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", + pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut+1)); pTemplate->rRun = p->rRun; pTemplate->nOut = p->nOut + 1; } @@ -117671,8 +119063,9 @@ static WhereLoop **whereLoopFindLesser( /* Any loop using an appliation-defined index (or PRIMARY KEY or ** UNIQUE constraint) with one or more == constraints is better - ** than an automatic index. */ + ** than an automatic index. Unless it is a skip-scan. */ if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 + && (pTemplate->nSkip)==0 && (pTemplate->wsFlags & WHERE_INDEXED)!=0 && (pTemplate->wsFlags & WHERE_COLUMN_EQ)!=0 && (p->prereq & pTemplate->prereq)==pTemplate->prereq @@ -117831,10 +119224,30 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ ** Adjust the WhereLoop.nOut value downward to account for terms of the ** WHERE clause that reference the loop but which are not used by an ** index. +* +** For every WHERE clause term that is not used by the index +** and which has a truth probability assigned by one of the likelihood(), +** likely(), or unlikely() SQL functions, reduce the estimated number +** of output rows by the probability specified. ** -** In the current implementation, the first extra WHERE clause term reduces -** the number of output rows by a factor of 10 and each additional term -** reduces the number of output rows by sqrt(2). +** TUNING: For every WHERE clause term that is not used by the index +** and which does not have an assigned truth probability, heuristics +** described below are used to try to estimate the truth probability. +** TODO --> Perhaps this is something that could be improved by better +** table statistics. +** +** Heuristic 1: Estimate the truth probability as 93.75%. The 93.75% +** value corresponds to -1 in LogEst notation, so this means decrement +** the WhereLoop.nOut field for every such WHERE clause term. +** +** Heuristic 2: If there exists one or more WHERE clause terms of the +** form "x==EXPR" and EXPR is not a constant 0 or 1, then make sure the +** final output row estimate is no greater than 1/4 of the total number +** of rows in the table. In other words, assume that x==EXPR will filter +** out at least 3 out of 4 rows. If EXPR is -1 or 0 or 1, then maybe the +** "x" column is boolean or else -1 or 0 or 1 is a common default value +** on the "x" column and so in that case only cap the output row estimate +** at 1/2 instead of 1/4. */ static void whereLoopOutputAdjust( WhereClause *pWC, /* The WHERE clause */ @@ -117843,9 +119256,10 @@ static void whereLoopOutputAdjust( ){ WhereTerm *pTerm, *pX; Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf); - int i, j; - int nEq = 0; /* Number of = constraints not within likely()/unlikely() */ + int i, j, k; + LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */ + assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){ if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break; if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; @@ -117858,20 +119272,26 @@ static void whereLoopOutputAdjust( } if( j<0 ){ if( pTerm->truthProb<=0 ){ + /* If a truth probability is specified using the likelihood() hints, + ** then use the probability provided by the application. */ pLoop->nOut += pTerm->truthProb; }else{ + /* In the absence of explicit truth probabilities, use heuristics to + ** guess a reasonable truth probability. */ pLoop->nOut--; - if( pTerm->eOperator&WO_EQ ) nEq++; + if( pTerm->eOperator&WO_EQ ){ + Expr *pRight = pTerm->pExpr->pRight; + if( sqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){ + k = 10; + }else{ + k = 20; + } + if( iReducenOut>nRow-10 ){ - pLoop->nOut = nRow - 10; - } + if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; } /* @@ -117912,7 +119332,7 @@ static int whereLoopAddBtreeIndex( Bitmask saved_prereq; /* Original value of pNew->prereq */ u16 saved_nLTerm; /* Original value of pNew->nLTerm */ u16 saved_nEq; /* Original value of pNew->u.btree.nEq */ - u16 saved_nSkip; /* Original value of pNew->u.btree.nSkip */ + u16 saved_nSkip; /* Original value of pNew->nSkip */ u32 saved_wsFlags; /* Original value of pNew->wsFlags */ LogEst saved_nOut; /* Original value of pNew->nOut */ int iCol; /* Index of the column in the table */ @@ -117941,7 +119361,7 @@ static int whereLoopAddBtreeIndex( pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, iCol, opMask, pProbe); saved_nEq = pNew->u.btree.nEq; - saved_nSkip = pNew->u.btree.nSkip; + saved_nSkip = pNew->nSkip; saved_nLTerm = pNew->nLTerm; saved_wsFlags = pNew->wsFlags; saved_prereq = pNew->prereq; @@ -117949,44 +119369,6 @@ static int whereLoopAddBtreeIndex( pNew->rSetup = 0; rSize = pProbe->aiRowLogEst[0]; rLogSize = estLog(rSize); - - /* Consider using a skip-scan if there are no WHERE clause constraints - ** available for the left-most terms of the index, and if the average - ** number of repeats in the left-most terms is at least 18. - ** - ** The magic number 18 is selected on the basis that scanning 17 rows - ** is almost always quicker than an index seek (even though if the index - ** contains fewer than 2^17 rows we assume otherwise in other parts of - ** the code). And, even if it is not, it should not be too much slower. - ** On the other hand, the extra seeks could end up being significantly - ** more expensive. */ - assert( 42==sqlite3LogEst(18) ); - if( saved_nEq==saved_nSkip - && saved_nEq+1nKeyCol - && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */ - && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK - ){ - LogEst nIter; - pNew->u.btree.nEq++; - pNew->u.btree.nSkip++; - pNew->aLTerm[pNew->nLTerm++] = 0; - pNew->wsFlags |= WHERE_SKIPSCAN; - nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1]; - if( pTerm ){ - /* TUNING: When estimating skip-scan for a term that is also indexable, - ** multiply the cost of the skip-scan by 2.0, to make it a little less - ** desirable than the regular index lookup. */ - nIter += 10; assert( 10==sqlite3LogEst(2) ); - } - pNew->nOut -= nIter; - /* TUNING: Because uncertainties in the estimates for skip-scan queries, - ** add a 1.375 fudge factor to make skip-scan slightly less likely. */ - nIter += 5; - whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul); - pNew->nOut = saved_nOut; - pNew->u.btree.nEq = saved_nEq; - pNew->u.btree.nSkip = saved_nSkip; - } for(; rc==SQLITE_OK && pTerm!=0; pTerm = whereScanNext(&scan)){ u16 eOp = pTerm->eOperator; /* Shorthand for pTerm->eOperator */ LogEst rCostIdx; @@ -118081,7 +119463,6 @@ static int whereLoopAddBtreeIndex( if( nInMul==0 && pProbe->nSample && pNew->u.btree.nEq<=pProbe->nSampleCol - && OptimizationEnabled(db, SQLITE_Stat3) && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect)) ){ Expr *pExpr = pTerm->pExpr; @@ -118149,10 +119530,45 @@ static int whereLoopAddBtreeIndex( } pNew->prereq = saved_prereq; pNew->u.btree.nEq = saved_nEq; - pNew->u.btree.nSkip = saved_nSkip; + pNew->nSkip = saved_nSkip; pNew->wsFlags = saved_wsFlags; pNew->nOut = saved_nOut; pNew->nLTerm = saved_nLTerm; + + /* Consider using a skip-scan if there are no WHERE clause constraints + ** available for the left-most terms of the index, and if the average + ** number of repeats in the left-most terms is at least 18. + ** + ** The magic number 18 is selected on the basis that scanning 17 rows + ** is almost always quicker than an index seek (even though if the index + ** contains fewer than 2^17 rows we assume otherwise in other parts of + ** the code). And, even if it is not, it should not be too much slower. + ** On the other hand, the extra seeks could end up being significantly + ** more expensive. */ + assert( 42==sqlite3LogEst(18) ); + if( saved_nEq==saved_nSkip + && saved_nEq+1nKeyCol + && pProbe->noSkipScan==0 + && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */ + && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK + ){ + LogEst nIter; + pNew->u.btree.nEq++; + pNew->nSkip++; + pNew->aLTerm[pNew->nLTerm++] = 0; + pNew->wsFlags |= WHERE_SKIPSCAN; + nIter = pProbe->aiRowLogEst[saved_nEq] - pProbe->aiRowLogEst[saved_nEq+1]; + pNew->nOut -= nIter; + /* TUNING: Because uncertainties in the estimates for skip-scan queries, + ** add a 1.375 fudge factor to make skip-scan slightly less likely. */ + nIter += 5; + whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter + nInMul); + pNew->nOut = saved_nOut; + pNew->u.btree.nEq = saved_nEq; + pNew->nSkip = saved_nSkip; + pNew->wsFlags = saved_wsFlags; + } + return rc; } @@ -118331,7 +119747,7 @@ static int whereLoopAddBtree( if( pTerm->prereqRight & pNew->maskSelf ) continue; if( termCanDriveIndex(pTerm, pSrc, 0) ){ pNew->u.btree.nEq = 1; - pNew->u.btree.nSkip = 0; + pNew->nSkip = 0; pNew->u.btree.pIndex = 0; pNew->nLTerm = 1; pNew->aLTerm[0] = pTerm; @@ -118372,7 +119788,7 @@ static int whereLoopAddBtree( } rSize = pProbe->aiRowLogEst[0]; pNew->u.btree.nEq = 0; - pNew->u.btree.nSkip = 0; + pNew->nSkip = 0; pNew->nLTerm = 0; pNew->iSortIdx = 0; pNew->rSetup = 0; @@ -118922,7 +120338,7 @@ static i8 wherePathSatisfiesOrderBy( /* Skip over == and IS NULL terms */ if( ju.btree.nEq - && pLoop->u.btree.nSkip==0 + && pLoop->nSkip==0 && ((i = pLoop->aLTerm[j]->eOperator) & (WO_EQ|WO_ISNULL))!=0 ){ if( i & WO_ISNULL ){ @@ -119376,7 +120792,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ } #ifdef WHERETRACE_ENABLED /* >=2 */ - if( sqlite3WhereTrace>=2 ){ + if( sqlite3WhereTrace & 0x02 ){ sqlite3DebugPrintf("---- after round %d ----\n", iLoop); for(ii=0, pTo=aTo; iisWC; pLoop = pBuilder->pNew; pLoop->wsFlags = 0; - pLoop->u.btree.nSkip = 0; + pLoop->nSkip = 0; pTerm = findTerm(pWC, iCur, -1, 0, WO_EQ, 0); if( pTerm ){ pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW; @@ -119507,7 +120923,6 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ }else{ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pLoop->aLTermSpace==pLoop->aLTerm ); - assert( ArraySize(pLoop->aLTermSpace)==4 ); if( !IsUniqueIndex(pIdx) || pIdx->pPartIdxWhere!=0 || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) @@ -120016,7 +121431,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( */ notReady = ~(Bitmask)0; for(ii=0; iia[ii]; + wsFlags = pLevel->pWLoop->wsFlags; #ifndef SQLITE_OMIT_AUTOMATIC_INDEX if( (pLevel->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 ){ constructAutomaticIndex(pParse, &pWInfo->sWC, @@ -120024,10 +121442,15 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( db->mallocFailed ) goto whereBeginError; } #endif - explainOneScan(pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags); + addrExplain = explainOneScan( + pParse, pTabList, pLevel, ii, pLevel->iFrom, wctrlFlags + ); pLevel->addrBody = sqlite3VdbeCurrentAddr(v); notReady = codeOneLoopStart(pWInfo, ii, notReady); pWInfo->iContinue = pLevel->addrCont; + if( (wsFlags&WHERE_MULTI_OR)==0 && (wctrlFlags&WHERE_ONETABLE_ONLY)==0 ){ + addScanStatus(v, pTabList, pLevel, addrExplain); + } } /* Done. */ @@ -122608,13 +124031,19 @@ static void yy_reduce( int cnt = 0, mxSelect; p->pWith = yymsp[-1].minor.yy59; if( p->pPrior ){ + u16 allValues = SF_Values; pNext = 0; for(pLoop=p; pLoop; pNext=pLoop, pLoop=pLoop->pPrior, cnt++){ pLoop->pNext = pNext; pLoop->selFlags |= SF_Compound; + allValues &= pLoop->selFlags; } - mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT]; - if( mxSelect && cnt>mxSelect ){ + if( allValues ){ + p->selFlags |= SF_AllValues; + }else if( + (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 + && cnt>mxSelect + ){ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); } } @@ -124458,6 +125887,9 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr int mxSqlLen; /* Max length of an SQL string */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( zSql==0 || pzErrMsg==0 ) return SQLITE_MISUSE_BKPT; +#endif mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; if( db->nVdbeActive==0 ){ db->u1.isInterrupted = 0; @@ -124725,6 +126157,13 @@ SQLITE_API int sqlite3_complete(const char *zSql){ }; #endif /* SQLITE_OMIT_TRIGGER */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( zSql==0 ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + while( *zSql ){ switch( *zSql ){ case ';': { /* A semicolon */ @@ -125026,7 +126465,7 @@ SQLITE_API int sqlite3_threadsafe(void){ return SQLITE_THREADSAFE; } ** I/O active are written using this function. These messages ** are intended for debugging activity only. */ -SQLITE_PRIVATE void (*sqlite3IoTrace)(const char*, ...) = 0; +/* not-private */ void (*sqlite3IoTrace)(const char*, ...) = 0; #endif /* @@ -125235,6 +126674,13 @@ SQLITE_API int sqlite3_initialize(void){ ** when this routine is invoked, then this routine is a harmless no-op. */ SQLITE_API int sqlite3_shutdown(void){ +#ifdef SQLITE_OMIT_WSD + int rc = sqlite3_wsd_init(4096, 24); + if( rc!=SQLITE_OK ){ + return rc; + } +#endif + if( sqlite3GlobalConfig.isInit ){ #ifdef SQLITE_EXTRA_SHUTDOWN void SQLITE_EXTRA_SHUTDOWN(void); @@ -125293,15 +126739,17 @@ SQLITE_API int sqlite3_config(int op, ...){ switch( op ){ /* Mutex configuration options are only available in a threadsafe - ** compile. + ** compile. */ -#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-54466-46756 */ case SQLITE_CONFIG_SINGLETHREAD: { /* Disable all mutexing */ sqlite3GlobalConfig.bCoreMutex = 0; sqlite3GlobalConfig.bFullMutex = 0; break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-20520-54086 */ case SQLITE_CONFIG_MULTITHREAD: { /* Disable mutexing of database connections */ /* Enable mutexing of core data structures */ @@ -125309,17 +126757,23 @@ SQLITE_API int sqlite3_config(int op, ...){ sqlite3GlobalConfig.bFullMutex = 0; break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-59593-21810 */ case SQLITE_CONFIG_SERIALIZED: { /* Enable all mutexing */ sqlite3GlobalConfig.bCoreMutex = 1; sqlite3GlobalConfig.bFullMutex = 1; break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-63666-48755 */ case SQLITE_CONFIG_MUTEX: { /* Specify an alternative mutex implementation */ sqlite3GlobalConfig.mutex = *va_arg(ap, sqlite3_mutex_methods*); break; } +#endif +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE>0 /* IMP: R-14450-37597 */ case SQLITE_CONFIG_GETMUTEX: { /* Retrieve the current mutex implementation */ *va_arg(ap, sqlite3_mutex_methods*) = sqlite3GlobalConfig.mutex; @@ -125327,37 +126781,61 @@ SQLITE_API int sqlite3_config(int op, ...){ } #endif - case SQLITE_CONFIG_MALLOC: { - /* Specify an alternative malloc implementation */ + /* EVIDENCE-OF: R-55594-21030 The SQLITE_CONFIG_MALLOC option takes a + ** single argument which is a pointer to an instance of the + ** sqlite3_mem_methods structure. The argument specifies alternative + ** low-level memory allocation routines to be used in place of the memory + ** allocation routines built into SQLite. */ sqlite3GlobalConfig.m = *va_arg(ap, sqlite3_mem_methods*); break; } case SQLITE_CONFIG_GETMALLOC: { - /* Retrieve the current malloc() implementation */ + /* EVIDENCE-OF: R-51213-46414 The SQLITE_CONFIG_GETMALLOC option takes a + ** single argument which is a pointer to an instance of the + ** sqlite3_mem_methods structure. The sqlite3_mem_methods structure is + ** filled with the currently defined memory allocation routines. */ if( sqlite3GlobalConfig.m.xMalloc==0 ) sqlite3MemSetDefault(); *va_arg(ap, sqlite3_mem_methods*) = sqlite3GlobalConfig.m; break; } case SQLITE_CONFIG_MEMSTATUS: { - /* Enable or disable the malloc status collection */ + /* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes + ** single argument of type int, interpreted as a boolean, which enables + ** or disables the collection of memory allocation statistics. */ sqlite3GlobalConfig.bMemstat = va_arg(ap, int); break; } case SQLITE_CONFIG_SCRATCH: { - /* Designate a buffer for scratch memory space */ + /* EVIDENCE-OF: R-08404-60887 There are three arguments to + ** SQLITE_CONFIG_SCRATCH: A pointer an 8-byte aligned memory buffer from + ** which the scratch allocations will be drawn, the size of each scratch + ** allocation (sz), and the maximum number of scratch allocations (N). */ sqlite3GlobalConfig.pScratch = va_arg(ap, void*); sqlite3GlobalConfig.szScratch = va_arg(ap, int); sqlite3GlobalConfig.nScratch = va_arg(ap, int); break; } case SQLITE_CONFIG_PAGECACHE: { - /* Designate a buffer for page cache memory space */ + /* EVIDENCE-OF: R-31408-40510 There are three arguments to + ** SQLITE_CONFIG_PAGECACHE: A pointer to 8-byte aligned memory, the size + ** of each page buffer (sz), and the number of pages (N). */ sqlite3GlobalConfig.pPage = va_arg(ap, void*); sqlite3GlobalConfig.szPage = va_arg(ap, int); sqlite3GlobalConfig.nPage = va_arg(ap, int); break; } + case SQLITE_CONFIG_PCACHE_HDRSZ: { + /* EVIDENCE-OF: R-39100-27317 The SQLITE_CONFIG_PCACHE_HDRSZ option takes + ** a single parameter which is a pointer to an integer and writes into + ** that integer the number of extra bytes per page required for each page + ** in SQLITE_CONFIG_PAGECACHE. */ + *va_arg(ap, int*) = + sqlite3HeaderSizeBtree() + + sqlite3HeaderSizePcache() + + sqlite3HeaderSizePcache1(); + break; + } case SQLITE_CONFIG_PCACHE: { /* no-op */ @@ -125370,11 +126848,18 @@ SQLITE_API int sqlite3_config(int op, ...){ } case SQLITE_CONFIG_PCACHE2: { - /* Specify an alternative page cache implementation */ + /* EVIDENCE-OF: R-63325-48378 The SQLITE_CONFIG_PCACHE2 option takes a + ** single argument which is a pointer to an sqlite3_pcache_methods2 + ** object. This object specifies the interface to a custom page cache + ** implementation. */ sqlite3GlobalConfig.pcache2 = *va_arg(ap, sqlite3_pcache_methods2*); break; } case SQLITE_CONFIG_GETPCACHE2: { + /* EVIDENCE-OF: R-22035-46182 The SQLITE_CONFIG_GETPCACHE2 option takes a + ** single argument which is a pointer to an sqlite3_pcache_methods2 + ** object. SQLite copies of the current page cache implementation into + ** that object. */ if( sqlite3GlobalConfig.pcache2.xInit==0 ){ sqlite3PCacheSetDefault(); } @@ -125382,9 +126867,14 @@ SQLITE_API int sqlite3_config(int op, ...){ break; } +/* EVIDENCE-OF: R-06626-12911 The SQLITE_CONFIG_HEAP option is only +** available if SQLite is compiled with either SQLITE_ENABLE_MEMSYS3 or +** SQLITE_ENABLE_MEMSYS5 and returns SQLITE_ERROR if invoked otherwise. */ #if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5) case SQLITE_CONFIG_HEAP: { - /* Designate a buffer for heap memory space */ + /* EVIDENCE-OF: R-19854-42126 There are three arguments to + ** SQLITE_CONFIG_HEAP: An 8-byte aligned pointer to the memory, the + ** number of bytes in the memory buffer, and the minimum allocation size. */ sqlite3GlobalConfig.pHeap = va_arg(ap, void*); sqlite3GlobalConfig.nHeap = va_arg(ap, int); sqlite3GlobalConfig.mnReq = va_arg(ap, int); @@ -125397,17 +126887,19 @@ SQLITE_API int sqlite3_config(int op, ...){ } if( sqlite3GlobalConfig.pHeap==0 ){ - /* If the heap pointer is NULL, then restore the malloc implementation - ** back to NULL pointers too. This will cause the malloc to go - ** back to its default implementation when sqlite3_initialize() is - ** run. + /* EVIDENCE-OF: R-49920-60189 If the first pointer (the memory pointer) + ** is NULL, then SQLite reverts to using its default memory allocator + ** (the system malloc() implementation), undoing any prior invocation of + ** SQLITE_CONFIG_MALLOC. + ** + ** Setting sqlite3GlobalConfig.m to all zeros will cause malloc to + ** revert to its default implementation when sqlite3_initialize() is run */ memset(&sqlite3GlobalConfig.m, 0, sizeof(sqlite3GlobalConfig.m)); }else{ - /* The heap pointer is not NULL, then install one of the - ** mem5.c/mem3.c methods. The enclosing #if guarantees at - ** least one of these methods is currently enabled. - */ + /* EVIDENCE-OF: R-61006-08918 If the memory pointer is not NULL then the + ** alternative memory allocator is engaged to handle all of SQLites + ** memory allocation needs. */ #ifdef SQLITE_ENABLE_MEMSYS3 sqlite3GlobalConfig.m = *sqlite3MemGetMemsys3(); #endif @@ -125446,11 +126938,19 @@ SQLITE_API int sqlite3_config(int op, ...){ ** sqlite3_config(SQLITE_CONFIG_URI,0) configuration calls. */ case SQLITE_CONFIG_URI: { + /* EVIDENCE-OF: R-25451-61125 The SQLITE_CONFIG_URI option takes a single + ** argument of type int. If non-zero, then URI handling is globally + ** enabled. If the parameter is zero, then URI handling is globally + ** disabled. */ sqlite3GlobalConfig.bOpenUri = va_arg(ap, int); break; } case SQLITE_CONFIG_COVERING_INDEX_SCAN: { + /* EVIDENCE-OF: R-36592-02772 The SQLITE_CONFIG_COVERING_INDEX_SCAN + ** option takes a single integer argument which is interpreted as a + ** boolean in order to enable or disable the use of covering indices for + ** full table scans in the query optimizer. */ sqlite3GlobalConfig.bUseCis = va_arg(ap, int); break; } @@ -125465,25 +126965,43 @@ SQLITE_API int sqlite3_config(int op, ...){ #endif case SQLITE_CONFIG_MMAP_SIZE: { + /* EVIDENCE-OF: R-58063-38258 SQLITE_CONFIG_MMAP_SIZE takes two 64-bit + ** integer (sqlite3_int64) values that are the default mmap size limit + ** (the default setting for PRAGMA mmap_size) and the maximum allowed + ** mmap size limit. */ sqlite3_int64 szMmap = va_arg(ap, sqlite3_int64); sqlite3_int64 mxMmap = va_arg(ap, sqlite3_int64); - if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ){ - mxMmap = SQLITE_MAX_MMAP_SIZE; - } - sqlite3GlobalConfig.mxMmap = mxMmap; + /* EVIDENCE-OF: R-53367-43190 If either argument to this option is + ** negative, then that argument is changed to its compile-time default. + ** + ** EVIDENCE-OF: R-34993-45031 The maximum allowed mmap size will be + ** silently truncated if necessary so that it does not exceed the + ** compile-time maximum mmap size set by the SQLITE_MAX_MMAP_SIZE + ** compile-time option. + */ + if( mxMmap<0 || mxMmap>SQLITE_MAX_MMAP_SIZE ) mxMmap = SQLITE_MAX_MMAP_SIZE; if( szMmap<0 ) szMmap = SQLITE_DEFAULT_MMAP_SIZE; if( szMmap>mxMmap) szMmap = mxMmap; + sqlite3GlobalConfig.mxMmap = mxMmap; sqlite3GlobalConfig.szMmap = szMmap; break; } -#if SQLITE_OS_WIN && defined(SQLITE_WIN32_MALLOC) +#if SQLITE_OS_WIN && defined(SQLITE_WIN32_MALLOC) /* IMP: R-04780-55815 */ case SQLITE_CONFIG_WIN32_HEAPSIZE: { + /* EVIDENCE-OF: R-34926-03360 SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit + ** unsigned integer value that specifies the maximum size of the created + ** heap. */ sqlite3GlobalConfig.nHeap = va_arg(ap, int); break; } #endif + case SQLITE_CONFIG_PMASZ: { + sqlite3GlobalConfig.szPma = va_arg(ap, unsigned int); + break; + } + default: { rc = SQLITE_ERROR; break; @@ -125562,6 +127080,12 @@ static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ ** Return the mutex associated with a database connection. */ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->mutex; } @@ -125571,6 +127095,10 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3 *db){ */ SQLITE_API int sqlite3_db_release_memory(sqlite3 *db){ int i; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); sqlite3BtreeEnterAll(db); for(i=0; inDb; i++){ @@ -125660,13 +127188,20 @@ static int binCollFunc( ){ int rc, n; n = nKey1lastRowid; } @@ -125708,6 +127249,12 @@ SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ ** Return the number of changes in the most recent call to sqlite3_exec(). */ SQLITE_API int sqlite3_changes(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->nChange; } @@ -125715,6 +127262,12 @@ SQLITE_API int sqlite3_changes(sqlite3 *db){ ** Return the number of changes since the database handle was opened. */ SQLITE_API int sqlite3_total_changes(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->nTotalChange; } @@ -125894,16 +127447,6 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ for(j=0; jnDb; j++){ struct Db *pDb = &db->aDb[j]; if( pDb->pBt ){ - if( pDb->pSchema ){ - /* Must clear the KeyInfo cache. See ticket [e4a18565a36884b00edf] */ - sqlite3BtreeEnter(pDb->pBt); - for(i=sqliteHashFirst(&pDb->pSchema->idxHash); i; i=sqliteHashNext(i)){ - Index *pIdx = sqliteHashData(i); - sqlite3KeyInfoUnref(pIdx->pKeyInfo); - pIdx->pKeyInfo = 0; - } - sqlite3BtreeLeave(pDb->pBt); - } sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; if( j!=1 ){ @@ -126210,7 +127753,7 @@ static int sqliteDefaultBusyCallback( void *ptr, /* Database connection */ int count /* Number of times table has been busy */ ){ -#if SQLITE_OS_WIN || (defined(HAVE_USLEEP) && HAVE_USLEEP) +#if SQLITE_OS_WIN || HAVE_USLEEP static const u8 delays[] = { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 }; static const u8 totals[] = @@ -126273,6 +127816,9 @@ SQLITE_API int sqlite3_busy_handler( int (*xBusy)(void*,int), void *pArg ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE; +#endif sqlite3_mutex_enter(db->mutex); db->busyHandler.xFunc = xBusy; db->busyHandler.pArg = pArg; @@ -126294,6 +127840,12 @@ SQLITE_API void sqlite3_progress_handler( int (*xProgress)(void*), void *pArg ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif sqlite3_mutex_enter(db->mutex); if( nOps>0 ){ db->xProgress = xProgress; @@ -126314,6 +127866,9 @@ SQLITE_API void sqlite3_progress_handler( ** specified number of milliseconds before returning 0. */ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif if( ms>0 ){ sqlite3_busy_handler(db, sqliteDefaultBusyCallback, (void*)db); db->busyTimeout = ms; @@ -126327,6 +127882,12 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ ** Cause any pending operation to stop at its earliest opportunity. */ SQLITE_API void sqlite3_interrupt(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif db->u1.isInterrupted = 1; } @@ -126464,6 +128025,12 @@ SQLITE_API int sqlite3_create_function_v2( ){ int rc = SQLITE_ERROR; FuncDestructor *pArg = 0; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); if( xDestroy ){ pArg = (FuncDestructor *)sqlite3DbMallocZero(db, sizeof(FuncDestructor)); @@ -126500,6 +128067,10 @@ SQLITE_API int sqlite3_create_function16( ){ int rc; char *zFunc8; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zFunctionName==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); zFunc8 = sqlite3Utf16to8(db, zFunctionName, -1, SQLITE_UTF16NATIVE); @@ -126531,6 +128102,12 @@ SQLITE_API int sqlite3_overload_function( ){ int nName = sqlite3Strlen30(zName); int rc = SQLITE_OK; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 || nArg<-2 ){ + return SQLITE_MISUSE_BKPT; + } +#endif sqlite3_mutex_enter(db->mutex); if( sqlite3FindFunction(db, zName, nName, nArg, SQLITE_UTF8, 0)==0 ){ rc = sqlite3CreateFunc(db, zName, nArg, SQLITE_UTF8, @@ -126552,6 +128129,13 @@ SQLITE_API int sqlite3_overload_function( */ SQLITE_API void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){ void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pOld = db->pTraceArg; db->xTrace = xTrace; @@ -126573,6 +128157,13 @@ SQLITE_API void *sqlite3_profile( void *pArg ){ void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pOld = db->pProfileArg; db->xProfile = xProfile; @@ -126593,6 +128184,13 @@ SQLITE_API void *sqlite3_commit_hook( void *pArg /* Argument to the function */ ){ void *pOld; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pOld = db->pCommitArg; db->xCommitCallback = xCallback; @@ -126611,6 +128209,13 @@ SQLITE_API void *sqlite3_update_hook( void *pArg /* Argument to the function */ ){ void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pUpdateArg; db->xUpdateCallback = xCallback; @@ -126629,6 +128234,13 @@ SQLITE_API void *sqlite3_rollback_hook( void *pArg /* Argument to the function */ ){ void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pRollbackArg; db->xRollbackCallback = xCallback; @@ -126675,6 +128287,9 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int nFrame){ UNUSED_PARAMETER(db); UNUSED_PARAMETER(nFrame); #else +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif if( nFrame>0 ){ sqlite3_wal_hook(db, sqlite3WalDefaultHook, SQLITE_INT_TO_PTR(nFrame)); }else{ @@ -126695,6 +128310,12 @@ SQLITE_API void *sqlite3_wal_hook( ){ #ifndef SQLITE_OMIT_WAL void *pRet; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pWalArg; db->xWalCallback = xCallback; @@ -126722,14 +128343,21 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( int rc; /* Return code */ int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + /* Initialize the output variables to -1 in case an error occurs. */ if( pnLog ) *pnLog = -1; if( pnCkpt ) *pnCkpt = -1; - assert( SQLITE_CHECKPOINT_FULL>SQLITE_CHECKPOINT_PASSIVE ); - assert( SQLITE_CHECKPOINT_FULLSQLITE_CHECKPOINT_RESTART ){ + assert( SQLITE_CHECKPOINT_PASSIVE==0 ); + assert( SQLITE_CHECKPOINT_FULL==1 ); + assert( SQLITE_CHECKPOINT_RESTART==2 ); + assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); + if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ + /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint + ** mode: */ return SQLITE_MISUSE; } @@ -126757,7 +128385,9 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ** checkpointed. */ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ - return sqlite3_wal_checkpoint_v2(db, zDb, SQLITE_CHECKPOINT_PASSIVE, 0, 0); + /* EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is equivalent to + ** sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0). */ + return sqlite3_wal_checkpoint_v2(db,zDb,SQLITE_CHECKPOINT_PASSIVE,0,0); } #ifndef SQLITE_OMIT_WAL @@ -126944,32 +128574,6 @@ SQLITE_API const char *sqlite3_errstr(int rc){ return sqlite3ErrStr(rc); } -/* -** Invalidate all cached KeyInfo objects for database connection "db" -*/ -static void invalidateCachedKeyInfo(sqlite3 *db){ - Db *pDb; /* A single database */ - int iDb; /* The database index number */ - HashElem *k; /* For looping over tables in pDb */ - Table *pTab; /* A table in the database */ - Index *pIdx; /* Each index */ - - for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){ - if( pDb->pBt==0 ) continue; - sqlite3BtreeEnter(pDb->pBt); - for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ - pTab = (Table*)sqliteHashData(k); - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->pKeyInfo && pIdx->pKeyInfo->db==db ){ - sqlite3KeyInfoUnref(pIdx->pKeyInfo); - pIdx->pKeyInfo = 0; - } - } - } - sqlite3BtreeLeave(pDb->pBt); - } -} - /* ** Create a new collating function for database "db". The name is zName ** and the encoding is enc. @@ -127013,7 +128617,6 @@ static int createCollation( return SQLITE_BUSY; } sqlite3ExpirePreparedStatements(db); - invalidateCachedKeyInfo(db); /* If collation sequence pColl was created directly by a call to ** sqlite3_create_collation, and not generated by synthCollSeq(), @@ -127118,6 +128721,12 @@ static const int aHardLimit[] = { SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ int oldLimit; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif /* EVIDENCE-OF: R-30189-54097 For each limit category SQLITE_LIMIT_NAME ** there is a hard upper bound set at compile-time by a C preprocessor @@ -127194,7 +128803,8 @@ SQLITE_PRIVATE int sqlite3ParseUri( assert( *pzErrMsg==0 ); - if( ((flags & SQLITE_OPEN_URI) || sqlite3GlobalConfig.bOpenUri) + if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ + || sqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */ && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ ){ char *zOpt; @@ -127403,6 +129013,9 @@ static int openDatabase( char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ char *zErrMsg = 0; /* Error message from sqlite3ParseUri() */ +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppDb==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppDb = 0; #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); @@ -127507,6 +129120,9 @@ static int openDatabase( #endif #if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS | SQLITE_ForeignKeys +#endif +#if defined(SQLITE_REVERSE_UNORDERED_SELECTS) + | SQLITE_ReverseOrder #endif ; sqlite3HashInit(&db->aCollSeq); @@ -127517,20 +129133,24 @@ static int openDatabase( /* Add the default collation sequence BINARY. BINARY works for both UTF-8 ** and UTF-16, so add a version for each to avoid any unnecessary ** conversions. The only error that can occur here is a malloc() failure. + ** + ** EVIDENCE-OF: R-52786-44878 SQLite defines three built-in collating + ** functions: */ createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0); createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0); createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0); + createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0); if( db->mallocFailed ){ goto opendb_out; } + /* EVIDENCE-OF: R-08308-17224 The default collating function for all + ** strings is BINARY. + */ db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 0); assert( db->pDfltColl!=0 ); - /* Also add a UTF-8 case-insensitive collation sequence. */ - createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); - /* Parse the filename/URI argument. */ db->openFlags = flags; rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); @@ -127553,6 +129173,7 @@ static int openDatabase( } sqlite3BtreeEnter(db->aDb[0].pBt); db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt); + if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db); sqlite3BtreeLeave(db->aDb[0].pBt); db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); @@ -127694,13 +129315,15 @@ SQLITE_API int sqlite3_open16( sqlite3_value *pVal; int rc; - assert( zFilename ); - assert( ppDb ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( ppDb==0 ) return SQLITE_MISUSE_BKPT; +#endif *ppDb = 0; #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ) return rc; #endif + if( zFilename==0 ) zFilename = "\000\000"; pVal = sqlite3ValueNew(0); sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC); zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8); @@ -127709,7 +129332,7 @@ SQLITE_API int sqlite3_open16( SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); assert( *ppDb || rc==SQLITE_NOMEM ); if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){ - ENC(*ppDb) = SQLITE_UTF16NATIVE; + SCHEMA_ENC(*ppDb) = ENC(*ppDb) = SQLITE_UTF16NATIVE; } }else{ rc = SQLITE_NOMEM; @@ -127730,13 +129353,7 @@ SQLITE_API int sqlite3_create_collation( void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ - int rc; - sqlite3_mutex_enter(db->mutex); - assert( !db->mallocFailed ); - rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, 0); - rc = sqlite3ApiExit(db, rc); - sqlite3_mutex_leave(db->mutex); - return rc; + return sqlite3_create_collation_v2(db, zName, enc, pCtx, xCompare, 0); } /* @@ -127751,6 +129368,10 @@ SQLITE_API int sqlite3_create_collation_v2( void(*xDel)(void*) ){ int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); rc = createCollation(db, zName, (u8)enc, pCtx, xCompare, xDel); @@ -127772,6 +129393,10 @@ SQLITE_API int sqlite3_create_collation16( ){ int rc = SQLITE_OK; char *zName8; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || zName==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); assert( !db->mallocFailed ); zName8 = sqlite3Utf16to8(db, zName, -1, SQLITE_UTF16NATIVE); @@ -127794,6 +129419,9 @@ SQLITE_API int sqlite3_collation_needed( void *pCollNeededArg, void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->xCollNeeded = xCollNeeded; db->xCollNeeded16 = 0; @@ -127812,6 +129440,9 @@ SQLITE_API int sqlite3_collation_needed16( void *pCollNeededArg, void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->xCollNeeded = 0; db->xCollNeeded16 = xCollNeeded16; @@ -127838,6 +129469,12 @@ SQLITE_API int sqlite3_global_recover(void){ ** by the next COMMIT or ROLLBACK. */ SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif return db->autoCommit; } @@ -127891,7 +129528,6 @@ SQLITE_API void sqlite3_thread_cleanup(void){ ** Return meta information about a specific column of a database table. ** See comment in sqlite3.h (sqlite.h.in) for details. */ -#ifdef SQLITE_ENABLE_COLUMN_METADATA SQLITE_API int sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ const char *zDbName, /* Database name or NULL */ @@ -127907,7 +129543,7 @@ SQLITE_API int sqlite3_table_column_metadata( char *zErrMsg = 0; Table *pTab = 0; Column *pCol = 0; - int iCol; + int iCol = 0; char const *zDataType = 0; char const *zCollSeq = 0; @@ -127931,11 +129567,8 @@ SQLITE_API int sqlite3_table_column_metadata( } /* Find the column for which info is requested */ - if( sqlite3IsRowid(zColumnName) ){ - iCol = pTab->iPKey; - if( iCol>=0 ){ - pCol = &pTab->aCol[iCol]; - } + if( zColumnName==0 ){ + /* Query for existance of table only */ }else{ for(iCol=0; iColnCol; iCol++){ pCol = &pTab->aCol[iCol]; @@ -127944,8 +129577,13 @@ SQLITE_API int sqlite3_table_column_metadata( } } if( iCol==pTab->nCol ){ - pTab = 0; - goto error_out; + if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){ + iCol = pTab->iPKey; + pCol = iCol>=0 ? &pTab->aCol[iCol] : 0; + }else{ + pTab = 0; + goto error_out; + } } } @@ -127998,7 +129636,6 @@ error_out: sqlite3_mutex_leave(db->mutex); return rc; } -#endif /* ** Sleep for a little while. Return the amount of time slept. @@ -128020,6 +129657,9 @@ SQLITE_API int sqlite3_sleep(int ms){ ** Enable or disable the extended result codes. */ SQLITE_API int sqlite3_extended_result_codes(sqlite3 *db, int onoff){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); db->errMask = onoff ? 0xffffffff : 0xff; sqlite3_mutex_leave(db->mutex); @@ -128033,6 +129673,9 @@ SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, vo int rc = SQLITE_ERROR; Btree *pBtree; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); pBtree = sqlite3DbNameToBtree(db, zDbName); if( pBtree ){ @@ -128375,7 +130018,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** returns a NULL pointer. */ SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam){ - if( zFilename==0 ) return 0; + if( zFilename==0 || zParam==0 ) return 0; zFilename += sqlite3Strlen30(zFilename) + 1; while( zFilename[0] ){ int x = strcmp(zFilename, zParam); @@ -128431,7 +130074,14 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ ** connection. */ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){ - Btree *pBt = sqlite3DbNameToBtree(db, zDbName); + Btree *pBt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + pBt = sqlite3DbNameToBtree(db, zDbName); return pBt ? sqlite3BtreeGetFilename(pBt) : 0; } @@ -128440,7 +130090,14 @@ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName){ ** no such database exists. */ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){ - Btree *pBt = sqlite3DbNameToBtree(db, zDbName); + Btree *pBt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif + pBt = sqlite3DbNameToBtree(db, zDbName); return pBt ? sqlite3BtreeIsReadonly(pBt) : -1; } @@ -131509,7 +133166,7 @@ static int fts3SelectLeaf( sqlite3_int64 *piLeaf, /* Selected leaf node */ sqlite3_int64 *piLeaf2 /* Selected leaf node */ ){ - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int iHeight; /* Height of this node in tree */ assert( piLeaf || piLeaf2 ); @@ -131520,7 +133177,7 @@ static int fts3SelectLeaf( if( rc==SQLITE_OK && iHeight>1 ){ char *zBlob = 0; /* Blob read from %_segments table */ - int nBlob; /* Size of zBlob in bytes */ + int nBlob = 0; /* Size of zBlob in bytes */ if( piLeaf && piLeaf2 && (*piLeaf!=*piLeaf2) ){ rc = sqlite3Fts3ReadBlock(p, *piLeaf, &zBlob, &nBlob, 0); @@ -132742,7 +134399,7 @@ static int fts3FilterMethod( int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ - int rc; + int rc = SQLITE_OK; char *zSql; /* SQL statement used to access %_content */ int eSearch; Fts3Table *p = (Fts3Table *)pCursor->pVtab; @@ -137861,7 +139518,7 @@ static int isVowel(const char *z){ ** by a consonant. ** ** In this routine z[] is in reverse order. So we are really looking -** for an instance of of a consonant followed by a vowel. +** for an instance of a consonant followed by a vowel. */ static int m_gt_0(const char *z){ while( isVowel(z) ){ z++; } @@ -139230,7 +140887,7 @@ static int fts3tokConnectMethod( sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */ char **pzErr /* OUT: sqlite3_malloc'd error message */ ){ - Fts3tokTable *pTab; + Fts3tokTable *pTab = 0; const sqlite3_tokenizer_module *pMod = 0; sqlite3_tokenizer *pTok = 0; int rc; @@ -142605,8 +144262,8 @@ static int fts3PromoteSegments( if( bOk ){ int iIdx = 0; - sqlite3_stmt *pUpdate1; - sqlite3_stmt *pUpdate2; + sqlite3_stmt *pUpdate1 = 0; + sqlite3_stmt *pUpdate2 = 0; if( rc==SQLITE_OK ){ rc = fts3SqlStmt(p, SQL_UPDATE_LEVEL_IDX, &pUpdate1, 0); @@ -147838,13 +149495,12 @@ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ - u32 i = ( + pCoord->u = ( (((u32)p[0]) << 24) + (((u32)p[1]) << 16) + (((u32)p[2]) << 8) + (((u32)p[3]) << 0) ); - *(u32 *)pCoord = i; } static i64 readInt64(u8 *p){ return ( @@ -147873,7 +149529,7 @@ static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); - i = *(u32 *)pCoord; + i = pCoord->u; p[0] = (i>>24)&0xFF; p[1] = (i>>16)&0xFF; p[2] = (i>> 8)&0xFF; @@ -148204,14 +149860,13 @@ static void nodeGetCell( RtreeCell *pCell /* OUT: Write the cell contents here */ ){ u8 *pData; - u8 *pEnd; RtreeCoord *pCoord; + int ii; pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell); pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell); - pEnd = pData + pRtree->nDim*8; pCoord = pCell->aCoord; - for(; pDatanDim*2; ii++){ + readCoord(&pData[ii*4], &pCoord[ii]); } } @@ -148651,7 +150306,7 @@ static RtreeSearchPoint *rtreeEnqueue( pNew = pCur->aPoint + i; pNew->rScore = rScore; pNew->iLevel = iLevel; - assert( iLevel>=0 && iLevel<=RTREE_MAX_DEPTH ); + assert( iLevel<=RTREE_MAX_DEPTH ); while( i>0 ){ RtreeSearchPoint *pParent; j = (i-1)/2; @@ -150275,6 +151930,8 @@ static int rtreeUpdate( rtreeReference(pRtree); assert(nData>=1); + cell.iRowid = 0; /* Used only to suppress a compiler warning */ + /* Constraint handling. A write operation on an r-tree table may return ** SQLITE_CONSTRAINT for two reasons: ** diff --git a/TMessagesProj/jni/sqlite/sqlite3.h b/TMessagesProj/jni/sqlite/sqlite3.h index c31f126d..07406477 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.h +++ b/TMessagesProj/jni/sqlite/sqlite3.h @@ -57,7 +57,7 @@ extern "C" { /* ** These no-op macros are used in front of interfaces to mark those ** interfaces as either deprecated or experimental. New applications -** should not use deprecated interfaces - they are support for backwards +** should not use deprecated interfaces - they are supported for backwards ** compatibility only. Application writers should be aware that ** experimental interfaces are subject to change in point releases. ** @@ -107,9 +107,9 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.8.7.4" -#define SQLITE_VERSION_NUMBER 3008007 -#define SQLITE_SOURCE_ID "2014-12-09 01:34:36 f66f7a17b78ba617acde90fc810107f34f1a1f2e" +#define SQLITE_VERSION "3.8.8.1" +#define SQLITE_VERSION_NUMBER 3008008 +#define SQLITE_SOURCE_ID "2015-01-20 16:51:25 f73337e3e289915a76ca96e7a05a1a8d4e890d55" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -201,7 +201,7 @@ SQLITE_API const char *sqlite3_compileoption_get(int N); ** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but ** can be fully or partially disabled using a call to [sqlite3_config()] ** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], -** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the ** sqlite3_threadsafe() function shows only the compile-time setting of ** thread safety, not any run-time changes to that setting made by ** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() @@ -1221,7 +1221,7 @@ struct sqlite3_vfs { ** ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given no the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED @@ -1504,26 +1504,28 @@ struct sqlite3_mem_methods { ** SQLITE_CONFIG_SERIALIZED configuration option.
  • ** ** [[SQLITE_CONFIG_MALLOC]]
    SQLITE_CONFIG_MALLOC
    -**
    ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The argument specifies +**
    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** a pointer to an instance of the [sqlite3_mem_methods] structure. +** The argument specifies ** alternative low-level memory allocation routines to be used in place of ** the memory allocation routines built into SQLite.)^ ^SQLite makes ** its own private copy of the content of the [sqlite3_mem_methods] structure ** before the [sqlite3_config()] call returns.
    ** ** [[SQLITE_CONFIG_GETMALLOC]]
    SQLITE_CONFIG_GETMALLOC
    -**
    ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mem_methods] structure. The [sqlite3_mem_methods] +**
    ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which +** is a pointer to an instance of the [sqlite3_mem_methods] structure. +** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation ** routines with a wrapper that simulations memory allocation failure or ** tracks memory usage, for example.
    ** ** [[SQLITE_CONFIG_MEMSTATUS]]
    SQLITE_CONFIG_MEMSTATUS
    -**
    ^This option takes single argument of type int, interpreted as a -** boolean, which enables or disables the collection of memory allocation -** statistics. ^(When memory allocation statistics are disabled, the -** following SQLite interfaces become non-operational: +**
    ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** interpreted as a boolean, which enables or disables the collection of +** memory allocation statistics. ^(When memory allocation statistics are +** disabled, the following SQLite interfaces become non-operational: **
    ** ** [[SQLITE_CONFIG_SCRATCH]]
    SQLITE_CONFIG_SCRATCH
    -**
    ^This option specifies a static memory buffer that SQLite can use for -** scratch memory. There are three arguments: A pointer an 8-byte +**
    ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer +** that SQLite can use for scratch memory. ^(There are three arguments +** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte ** aligned memory buffer from which the scratch allocations will be ** drawn, the size of each scratch allocation (sz), -** and the maximum number of scratch allocations (N). The sz -** argument must be a multiple of 16. +** and the maximum number of scratch allocations (N).)^ ** The first argument must be a pointer to an 8-byte aligned buffer ** of at least sz*N bytes of memory. -** ^SQLite will use no more than two scratch buffers per thread. So -** N should be set to twice the expected maximum number of threads. -** ^SQLite will never require a scratch buffer that is more than 6 -** times the database page size. ^If SQLite needs needs additional +** ^SQLite will not use more than one scratch buffers per thread. +** ^SQLite will never request a scratch buffer that is more than 6 +** times the database page size. +** ^If SQLite needs needs additional ** scratch memory beyond what is provided by this configuration option, then -** [sqlite3_malloc()] will be used to obtain the memory needed.
    +** [sqlite3_malloc()] will be used to obtain the memory needed.

    +** ^When the application provides any amount of scratch memory using +** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large +** [sqlite3_malloc|heap allocations]. +** This can help [Robson proof|prevent memory allocation failures] due to heap +** fragmentation in low-memory embedded systems. +** ** ** [[SQLITE_CONFIG_PAGECACHE]]

    SQLITE_CONFIG_PAGECACHE
    -**
    ^This option specifies a static memory buffer that SQLite can use for -** the database page cache with the default page cache implementation. +**
    ^The SQLITE_CONFIG_PAGECACHE option specifies a static memory buffer +** that SQLite can use for the database page cache with the default page +** cache implementation. ** This configuration should not be used if an application-define page -** cache implementation is loaded using the SQLITE_CONFIG_PCACHE2 option. -** There are three arguments to this option: A pointer to 8-byte aligned +** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2] +** configuration option. +** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to +** 8-byte aligned ** memory, the size of each page buffer (sz), and the number of pages (N). ** The sz argument should be the size of the largest database page -** (a power of two between 512 and 32768) plus a little extra for each -** page header. ^The page header size is 20 to 40 bytes depending on -** the host architecture. ^It is harmless, apart from the wasted memory, -** to make sz a little too large. The first -** argument should point to an allocation of at least sz*N bytes of memory. +** (a power of two between 512 and 65536) plus some extra bytes for each +** page header. ^The number of extra bytes needed by the page header +** can be determined using the [SQLITE_CONFIG_PCACHE_HDRSZ] option +** to [sqlite3_config()]. +** ^It is harmless, apart from the wasted memory, +** for the sz parameter to be larger than necessary. The first +** argument should pointer to an 8-byte aligned block of memory that +** is at least sz*N bytes of memory, otherwise subsequent behavior is +** undefined. ** ^SQLite will use the memory provided by the first argument to satisfy its ** memory needs for the first N pages that it adds to cache. ^If additional ** page cache memory is needed beyond what is provided by this option, then -** SQLite goes to [sqlite3_malloc()] for the additional storage space. -** The pointer in the first argument must -** be aligned to an 8-byte boundary or subsequent behavior of SQLite -** will be undefined.
    +** SQLite goes to [sqlite3_malloc()] for the additional storage space. ** ** [[SQLITE_CONFIG_HEAP]]
    SQLITE_CONFIG_HEAP
    -**
    ^This option specifies a static memory buffer that SQLite will use -** for all of its dynamic memory allocation needs beyond those provided -** for by [SQLITE_CONFIG_SCRATCH] and [SQLITE_CONFIG_PAGECACHE]. -** There are three arguments: An 8-byte aligned pointer to the memory, +**
    ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** that SQLite will use for all of its dynamic memory allocation needs +** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and +** [SQLITE_CONFIG_PAGECACHE]. +** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled +** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns +** [SQLITE_ERROR] if invoked otherwise. +** ^There are three arguments to SQLITE_CONFIG_HEAP: +** An 8-byte aligned pointer to the memory, ** the number of bytes in the memory buffer, and the minimum allocation size. ** ^If the first pointer (the memory pointer) is NULL, then SQLite reverts ** to using its default memory allocator (the system malloc() implementation), ** undoing any prior invocation of [SQLITE_CONFIG_MALLOC]. ^If the -** memory pointer is not NULL and either [SQLITE_ENABLE_MEMSYS3] or -** [SQLITE_ENABLE_MEMSYS5] are defined, then the alternative memory +** memory pointer is not NULL then the alternative memory ** allocator is engaged to handle all of SQLites memory allocation needs. ** The first pointer (the memory pointer) must be aligned to an 8-byte ** boundary or subsequent behavior of SQLite will be undefined. @@ -1590,11 +1606,11 @@ struct sqlite3_mem_methods { ** for the minimum allocation size are 2**5 through 2**8.
    ** ** [[SQLITE_CONFIG_MUTEX]]
    SQLITE_CONFIG_MUTEX
    -**
    ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The argument specifies -** alternative low-level mutex routines to be used in place -** the mutex routines built into SQLite.)^ ^SQLite makes a copy of the -** content of the [sqlite3_mutex_methods] structure before the call to +**
    ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a +** pointer to an instance of the [sqlite3_mutex_methods] structure. +** The argument specifies alternative low-level mutex routines to be used +** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** the entire mutexing subsystem is omitted from the build and hence calls to @@ -1602,8 +1618,8 @@ struct sqlite3_mem_methods { ** return [SQLITE_ERROR].
    ** ** [[SQLITE_CONFIG_GETMUTEX]]
    SQLITE_CONFIG_GETMUTEX
    -**
    ^(This option takes a single argument which is a pointer to an -** instance of the [sqlite3_mutex_methods] structure. The +**
    ^(The SQLITE_CONFIG_GETMUTEX option takes a single argument which +** is a pointer to an instance of the [sqlite3_mutex_methods] structure. The ** [sqlite3_mutex_methods] ** structure is filled with the currently defined mutex routines.)^ ** This option can be used to overload the default mutex allocation @@ -1615,25 +1631,25 @@ struct sqlite3_mem_methods { ** return [SQLITE_ERROR].
    ** ** [[SQLITE_CONFIG_LOOKASIDE]]
    SQLITE_CONFIG_LOOKASIDE
    -**
    ^(This option takes two arguments that determine the default -** memory allocation for the lookaside memory allocator on each -** [database connection]. The first argument is the +**
    ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine +** the default size of lookaside memory on each [database connection]. +** The first argument is the ** size of each lookaside buffer slot and the second is the number of -** slots allocated to each database connection.)^ ^(This option sets the -** default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] -** verb to [sqlite3_db_config()] can be used to change the lookaside +** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE +** sets the default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] +** option to [sqlite3_db_config()] can be used to change the lookaside ** configuration on individual connections.)^
    ** ** [[SQLITE_CONFIG_PCACHE2]]
    SQLITE_CONFIG_PCACHE2
    -**
    ^(This option takes a single argument which is a pointer to -** an [sqlite3_pcache_methods2] object. This object specifies the interface -** to a custom page cache implementation.)^ ^SQLite makes a copy of the -** object and uses it for page cache memory allocations.
    +**
    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** a pointer to an [sqlite3_pcache_methods2] object. This object specifies +** the interface to a custom page cache implementation.)^ +** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
    ** ** [[SQLITE_CONFIG_GETPCACHE2]]
    SQLITE_CONFIG_GETPCACHE2
    -**
    ^(This option takes a single argument which is a pointer to an -** [sqlite3_pcache_methods2] object. SQLite copies of the current -** page cache implementation into that object.)^
    +**
    ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** the current page cache implementation into that object.)^
    ** ** [[SQLITE_CONFIG_LOG]]
    SQLITE_CONFIG_LOG
    **
    The SQLITE_CONFIG_LOG option is used to configure the SQLite @@ -1656,10 +1672,11 @@ struct sqlite3_mem_methods { ** function must be threadsafe.
    ** ** [[SQLITE_CONFIG_URI]]
    SQLITE_CONFIG_URI -**
    ^(This option takes a single argument of type int. If non-zero, then -** URI handling is globally enabled. If the parameter is zero, then URI handling -** is globally disabled.)^ ^If URI handling is globally enabled, all filenames -** passed to [sqlite3_open()], [sqlite3_open_v2()], [sqlite3_open16()] or +**
    ^(The SQLITE_CONFIG_URI option takes a single argument of type int. +** If non-zero, then URI handling is globally enabled. If the parameter is zero, +** then URI handling is globally disabled.)^ ^If URI handling is globally +** enabled, all filenames passed to [sqlite3_open()], [sqlite3_open_v2()], +** [sqlite3_open16()] or ** specified as part of [ATTACH] commands are interpreted as URIs, regardless ** of whether or not the [SQLITE_OPEN_URI] flag is set when the database ** connection is opened. ^If it is globally disabled, filenames are @@ -1669,9 +1686,10 @@ struct sqlite3_mem_methods { ** [SQLITE_USE_URI] symbol defined.)^ ** ** [[SQLITE_CONFIG_COVERING_INDEX_SCAN]]
    SQLITE_CONFIG_COVERING_INDEX_SCAN -**
    ^This option takes a single integer argument which is interpreted as -** a boolean in order to enable or disable the use of covering indices for -** full table scans in the query optimizer. ^The default setting is determined +**
    ^The SQLITE_CONFIG_COVERING_INDEX_SCAN option takes a single integer +** argument which is interpreted as a boolean in order to enable or disable +** the use of covering indices for full table scans in the query optimizer. +** ^The default setting is determined ** by the [SQLITE_ALLOW_COVERING_INDEX_SCAN] compile-time option, or is "on" ** if that compile-time option is omitted. ** The ability to disable the use of covering indices for full table scans @@ -1711,19 +1729,39 @@ struct sqlite3_mem_methods { ** ^The default setting can be overridden by each database connection using ** either the [PRAGMA mmap_size] command, or by using the ** [SQLITE_FCNTL_MMAP_SIZE] file control. ^(The maximum allowed mmap size -** cannot be changed at run-time. Nor may the maximum allowed mmap size -** exceed the compile-time maximum mmap size set by the +** will be silently truncated if necessary so that it does not exceed the +** compile-time maximum mmap size set by the ** [SQLITE_MAX_MMAP_SIZE] compile-time option.)^ ** ^If either argument to this option is negative, then that argument is ** changed to its compile-time default. ** ** [[SQLITE_CONFIG_WIN32_HEAPSIZE]] **
    SQLITE_CONFIG_WIN32_HEAPSIZE -**
    ^This option is only available if SQLite is compiled for Windows -** with the [SQLITE_WIN32_MALLOC] pre-processor macro defined. -** SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value +**
    ^The SQLITE_CONFIG_WIN32_HEAPSIZE option is only available if SQLite is +** compiled for Windows with the [SQLITE_WIN32_MALLOC] pre-processor macro +** defined. ^SQLITE_CONFIG_WIN32_HEAPSIZE takes a 32-bit unsigned integer value ** that specifies the maximum size of the created heap. ** +** +** [[SQLITE_CONFIG_PCACHE_HDRSZ]] +**
    SQLITE_CONFIG_PCACHE_HDRSZ +**
    ^The SQLITE_CONFIG_PCACHE_HDRSZ option takes a single parameter which +** is a pointer to an integer and writes into that integer the number of extra +** bytes per page required for each page in [SQLITE_CONFIG_PAGECACHE]. +** The amount of extra space required can change depending on the compiler, +** target platform, and SQLite version. +** +** [[SQLITE_CONFIG_PMASZ]] +**
    SQLITE_CONFIG_PMASZ +**
    ^The SQLITE_CONFIG_PMASZ option takes a single parameter which +** is an unsigned integer and sets the "Minimum PMA Size" for the multithreaded +** sorter to that integer. The default minimum PMA Size is set by the +** [SQLITE_SORTER_PMASZ] compile-time option. New threads are launched +** to help with sort operations when multithreaded sorting +** is enabled (using the [PRAGMA threads] command) and the amount of content +** to be sorted exceeds the page size times the minimum of the +** [PRAGMA cache_size] setting and this value. +** */ #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ @@ -1748,6 +1786,8 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ #define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ #define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ /* ** CAPI3REF: Database Connection Configuration Options @@ -1875,47 +1915,45 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); /* ** CAPI3REF: Count The Number Of Rows Modified ** -** ^This function returns the number of database rows that were changed -** or inserted or deleted by the most recently completed SQL statement -** on the [database connection] specified by the first parameter. -** ^(Only changes that are directly specified by the [INSERT], [UPDATE], -** or [DELETE] statement are counted. Auxiliary changes caused by -** triggers or [foreign key actions] are not counted.)^ Use the -** [sqlite3_total_changes()] function to find the total number of changes -** including changes caused by triggers and foreign key actions. +** ^This function returns the number of rows modified, inserted or +** deleted by the most recently completed INSERT, UPDATE or DELETE +** statement on the database connection specified by the only parameter. +** ^Executing any other type of SQL statement does not modify the value +** returned by this function. ** -** ^Changes to a view that are simulated by an [INSTEAD OF trigger] -** are not counted. Only real table changes are counted. +** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** [foreign key actions] or [REPLACE] constraint resolution are not counted. +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real +** tables are counted. ** -** ^(A "row change" is a change to a single row of a single table -** caused by an INSERT, DELETE, or UPDATE statement. Rows that -** are changed as side effects of [REPLACE] constraint resolution, -** rollback, ABORT processing, [DROP TABLE], or by any other -** mechanisms do not count as direct row changes.)^ -** -** A "trigger context" is a scope of execution that begins and -** ends with the script of a [CREATE TRIGGER | trigger]. -** Most SQL statements are -** evaluated outside of any trigger. This is the "top level" -** trigger context. If a trigger fires from the top level, a -** new trigger context is entered for the duration of that one -** trigger. Subtriggers create subcontexts for their duration. -** -** ^Calling [sqlite3_exec()] or [sqlite3_step()] recursively does -** not create a new trigger context. -** -** ^This function returns the number of direct row changes in the -** most recent INSERT, UPDATE, or DELETE statement within the same -** trigger context. -** -** ^Thus, when called from the top level, this function returns the -** number of changes in the most recent INSERT, UPDATE, or DELETE -** that also occurred at the top level. ^(Within the body of a trigger, -** the sqlite3_changes() interface can be called to find the number of -** changes in the most recently completed INSERT, UPDATE, or DELETE -** statement within the body of the same trigger. -** However, the number returned does not include changes -** caused by subtriggers since those have their own context.)^ +** Things are more complicated if the sqlite3_changes() function is +** executed while a trigger program is running. This may happen if the +** program uses the [changes() SQL function], or if some other callback +** function invokes sqlite3_changes() directly. Essentially: +** +** +** +** ^This means that if the changes() SQL function (or similar) is used +** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** returns the value as set when the calling statement began executing. +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the +** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** See also the [sqlite3_total_changes()] interface, the ** [count_changes pragma], and the [changes() SQL function]. @@ -1929,20 +1967,17 @@ SQLITE_API int sqlite3_changes(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** -** ^This function returns the number of row changes caused by [INSERT], -** [UPDATE] or [DELETE] statements since the [database connection] was opened. -** ^(The count returned by sqlite3_total_changes() includes all changes -** from all [CREATE TRIGGER | trigger] contexts and changes made by -** [foreign key actions]. However, -** the count does not include changes used to implement [REPLACE] constraints, -** do rollbacks or ABORT processing, or [DROP TABLE] processing. The -** count does not include rows of views that fire an [INSTEAD OF trigger], -** though if the INSTEAD OF trigger makes changes of its own, those changes -** are counted.)^ -** ^The sqlite3_total_changes() function counts the changes as soon as -** the statement that makes them is completed (when the statement handle -** is passed to [sqlite3_reset()] or [sqlite3_finalize()]). -** +** ^This function returns the total number of rows inserted, modified or +** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed +** since the database connection was opened, including those executed as +** part of trigger programs. ^Executing any other type of SQL statement +** does not affect the value returned by sqlite3_total_changes(). +** +** ^Changes made as part of [foreign key actions] are included in the +** count, but those made as part of REPLACE constraint resolution are +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** are not counted. +** ** See also the [sqlite3_changes()] interface, the ** [count_changes pragma], and the [total_changes() SQL function]. ** @@ -2029,6 +2064,7 @@ SQLITE_API int sqlite3_complete16(const void *sql); /* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors +** KEYWORDS: {busy-handler callback} {busy handler} ** ** ^The sqlite3_busy_handler(D,X,P) routine sets a callback function X ** that might be invoked with argument P whenever @@ -2045,7 +2081,7 @@ SQLITE_API int sqlite3_complete16(const void *sql); ** ^The first argument to the busy handler is a copy of the void* pointer which ** is the third argument to sqlite3_busy_handler(). ^The second argument to ** the busy handler callback is the number of times that the busy handler has -** been invoked for the same locking event. ^If the +** been invoked previously for the same locking event. ^If the ** busy callback returns 0, then no additional attempts are made to ** access the database and [SQLITE_BUSY] is returned ** to the application. @@ -2420,13 +2456,14 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag); ** applications to access the same PRNG for other purposes. ** ** ^A call to this routine stores N bytes of randomness into buffer P. -** ^If N is less than one, then P can be a NULL pointer. +** ^The P parameter can be a NULL pointer. ** ** ^If this routine has not been previously called or if the previous -** call had N less than one, then the PRNG is seeded using randomness -** obtained from the xRandomness method of the default [sqlite3_vfs] object. -** ^If the previous call to this routine had an N of 1 or more then -** the pseudo-randomness is generated +** call had N less than one or a NULL pointer for P, then the PRNG is +** seeded using randomness obtained from the xRandomness method of +** the default [sqlite3_vfs] object. +** ^If the previous call to this routine had an N of 1 or more and a +** non-NULL P then the pseudo-randomness is generated ** internally and without recourse to the [sqlite3_vfs] xRandomness ** method. */ @@ -4148,9 +4185,9 @@ SQLITE_API int sqlite3_create_function_v2( ** These constant define integer codes that represent the various ** text encodings supported by SQLite. */ -#define SQLITE_UTF8 1 -#define SQLITE_UTF16LE 2 -#define SQLITE_UTF16BE 3 +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ @@ -4499,7 +4536,8 @@ typedef void (*sqlite3_destructor_type)(void*); ** the [sqlite3_context] pointer, the results are undefined. */ SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,sqlite3_uint64,void(*)(void*)); +SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); SQLITE_API void sqlite3_result_double(sqlite3_context*, double); SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); @@ -5131,20 +5169,27 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); /* ** CAPI3REF: Extract Metadata About A Column Of A Table ** -** ^This routine returns metadata about a specific column of a specific -** database table accessible using the [database connection] handle -** passed as the first function argument. +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR and if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existance of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. ** ** ^The column is identified by the second, third and fourth parameters to -** this function. ^The second parameter is either the name of the database +** this function. ^(The second parameter is either the name of the database ** (i.e. "main", "temp", or an attached database) containing the specified -** table or NULL. ^If it is NULL, then all attached databases are searched +** table or NULL.)^ ^If it is NULL, then all attached databases are searched ** for the table using the same algorithm used by the database engine to ** resolve unqualified table references. ** ** ^The third and fourth parameters to this function are the table and column -** name of the desired column, respectively. Neither of these parameters -** may be NULL. +** name of the desired column, respectively. ** ** ^Metadata is returned by writing to the memory locations passed as the 5th ** and subsequent parameters to this function. ^Any of these arguments may be @@ -5163,16 +5208,17 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** )^ ** ** ^The memory pointed to by the character pointers returned for the -** declaration type and collation sequence is valid only until the next +** declaration type and collation sequence is valid until the next ** call to any SQLite API function. ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and an +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no -** explicitly declared [INTEGER PRIMARY KEY] column, then the output -** parameters are set as follows: +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: ** **
     **     data type: "INTEGER"
    @@ -5182,13 +5228,9 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
     **     auto increment: 0
     ** 
    )^ ** -** ^(This function may load one or more schemas from database files. If an -** error occurs during this process, or if the requested table or column -** cannot be found, an [error code] is returned and an error message left -** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^ -** -** ^This API is only available if the library was compiled with the -** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. */ SQLITE_API int sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ @@ -5641,26 +5683,42 @@ typedef struct sqlite3_blob sqlite3_blob; ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** )^ ** +** ^(Parameter zDb is not the filename that contains the database, but +** rather the symbolic name of the database. For attached databases, this is +** the name that appears after the AS keyword in the [ATTACH] statement. +** For the main database file, the database name is "main". For TEMP +** tables, the database name is "temp".)^ +** ** ^If the flags parameter is non-zero, then the BLOB is opened for read -** and write access. ^If it is zero, the BLOB is opened for read access. -** ^It is not possible to open a column that is part of an index or primary -** key for writing. ^If [foreign key constraints] are enabled, it is -** not possible to open a column that is part of a [child key] for writing. +** and write access. ^If the flags parameter is zero, the BLOB is opened for +** read-only access. ** -** ^Note that the database name is not the filename that contains -** the database but rather the symbolic name of the database that -** appears after the AS keyword when the database is connected using [ATTACH]. -** ^For the main database file, the database name is "main". -** ^For TEMP tables, the database name is "temp". +** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored +** in *ppBlob. Otherwise an [error code] is returned and, unless the error +** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function it returns. +** +** This function fails with SQLITE_ERROR if any of the following are true: +** +** +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** -** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is written -** to *ppBlob. Otherwise an [error code] is returned and *ppBlob is set -** to be a null pointer.)^ -** ^This function sets the [database connection] error code and message -** accessible via [sqlite3_errcode()] and [sqlite3_errmsg()] and related -** functions. ^Note that the *ppBlob variable is always initialized in a -** way that makes it safe to invoke [sqlite3_blob_close()] on *ppBlob -** regardless of the success or failure of this routine. ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects @@ -5678,13 +5736,9 @@ typedef struct sqlite3_blob sqlite3_blob; ** interface. Use the [UPDATE] SQL command to change the size of a ** blob. ** -** ^The [sqlite3_blob_open()] interface will fail for a [WITHOUT ROWID] -** table. Incremental BLOB I/O is not possible on [WITHOUT ROWID] tables. -** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function can be used, if desired, -** to create an empty, zero-filled blob in which to read or write using -** this interface. +** and the built-in [zeroblob] SQL function may be used to create a +** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. @@ -5726,24 +5780,22 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_i /* ** CAPI3REF: Close A BLOB Handle ** -** ^Closes an open [BLOB handle]. +** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed +** unconditionally. Even if this routine returns an error code, the +** handle is still closed.)^ ** -** ^Closing a BLOB shall cause the current transaction to commit -** if there are no other BLOBs, no pending prepared statements, and the -** database connection is in [autocommit mode]. -** ^If any writes were made to the BLOB, they might be held in cache -** until the close operation if they will fit. +** ^If the blob handle being closed was opened for read-write access, and if +** the database is in auto-commit mode and there are no other open read-write +** blob handles or active write statements, the current transaction is +** committed. ^If an error occurs while committing the transaction, an error +** code is returned and the transaction rolled back. ** -** ^(Closing the BLOB often forces the changes -** out to disk and so if any I/O errors occur, they will likely occur -** at the time when the BLOB is closed. Any errors that occur during -** closing are reported as a non-zero return value.)^ -** -** ^(The BLOB is closed unconditionally. Even if this routine returns -** an error code, the BLOB is still closed.)^ -** -** ^Calling this routine with a null pointer (such as would be returned -** by a failed call to [sqlite3_blob_open()]) is a harmless no-op. +** Calling this function with an argument that is not a NULL pointer or an +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to +** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function +** is passed a valid open blob handle, the values returned by the +** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); @@ -5793,21 +5845,27 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); /* ** CAPI3REF: Write Data Into A BLOB Incrementally ** -** ^This function is used to write data into an open [BLOB handle] from a -** caller-supplied buffer. ^N bytes of data are copied from the buffer Z -** into the open BLOB, starting at offset iOffset. +** ^(This function is used to write data into an open [BLOB handle] from a +** caller-supplied buffer. N bytes of data are copied from the buffer Z +** into the open BLOB, starting at offset iOffset.)^ +** +** ^(On success, sqlite3_blob_write() returns SQLITE_OK. +** Otherwise, an [error code] or an [extended error code] is returned.)^ +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), ** this function returns [SQLITE_READONLY]. ** -** ^This function may only modify the contents of the BLOB; it is +** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. ^If N is -** less than zero [SQLITE_ERROR] is returned and no data is written. -** The size of the BLOB (and hence the maximum value of N+iOffset) -** can be determined using the [sqlite3_blob_bytes()] interface. +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an ** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred @@ -5816,9 +5874,6 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** have been overwritten by the statement that expired the BLOB handle ** or by other independent statements. ** -** ^(On success, sqlite3_blob_write() returns SQLITE_OK. -** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ** This routine only works on a [BLOB handle] which has been created ** by a prior successful call to [sqlite3_blob_open()] and which has not ** been closed by [sqlite3_blob_close()]. Passing any other pointer in @@ -5871,34 +5926,34 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ** The SQLite source code contains multiple implementations ** of these mutex routines. An appropriate implementation -** is selected automatically at compile-time. ^(The following +** is selected automatically at compile-time. The following ** implementations are available in the SQLite core: ** ** )^ +** ** -** ^The SQLITE_MUTEX_NOOP implementation is a set of routines +** The SQLITE_MUTEX_NOOP implementation is a set of routines ** that does no real locking and is appropriate for use in -** a single-threaded application. ^The SQLITE_MUTEX_PTHREADS and +** a single-threaded application. The SQLITE_MUTEX_PTHREADS and ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** -** ^(If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor +** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor ** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex ** implementation is included with the library. In this case the ** application must supply a custom mutex implementation using the ** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function ** before calling sqlite3_initialize() or any other public sqlite3_ -** function that calls sqlite3_initialize().)^ +** function that calls sqlite3_initialize(). ** ** ^The sqlite3_mutex_alloc() routine allocates a new -** mutex and returns a pointer to it. ^If it returns NULL -** that means that a mutex could not be allocated. ^SQLite -** will unwind its stack and return an error. ^(The argument -** to sqlite3_mutex_alloc() is one of these integer constants: +** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() +** routine returns NULL if it is unable to allocate the requested +** mutex. The argument to sqlite3_mutex_alloc() must one of these +** integer constants: ** ** )^ +**
  • SQLITE_MUTEX_STATIC_APP3 +** ** ** ^The first two constants (SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) ** cause sqlite3_mutex_alloc() to create @@ -5919,14 +5975,14 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** is used but not necessarily so when SQLITE_MUTEX_FAST is used. ** The mutex implementation does not need to make a distinction ** between SQLITE_MUTEX_RECURSIVE and SQLITE_MUTEX_FAST if it does -** not want to. ^SQLite will only request a recursive mutex in -** cases where it really needs one. ^If a faster non-recursive mutex +** not want to. SQLite will only request a recursive mutex in +** cases where it really needs one. If a faster non-recursive mutex ** implementation is available on the host platform, the mutex subsystem ** might return such a mutex in response to SQLITE_MUTEX_FAST. ** ** ^The other allowed parameters to sqlite3_mutex_alloc() (anything other ** than SQLITE_MUTEX_FAST and SQLITE_MUTEX_RECURSIVE) each return -** a pointer to a static preexisting mutex. ^Six static mutexes are +** a pointer to a static preexisting mutex. ^Nine static mutexes are ** used by the current version of SQLite. Future versions of SQLite ** may add additional static mutexes. Static mutexes are for internal ** use by SQLite only. Applications that use SQLite mutexes should @@ -5935,16 +5991,13 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ** ^Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. ^But for the static +** returns a different mutex on every call. ^For the static ** mutex types, the same mutex is returned on every call that has ** the same type number. ** ** ^The sqlite3_mutex_free() routine deallocates a previously -** allocated dynamic mutex. ^SQLite is careful to deallocate every -** dynamic mutex that it allocates. The dynamic mutexes must not be in -** use when they are deallocated. Attempting to deallocate a static -** mutex results in undefined behavior. ^SQLite never deallocates -** a static mutex. +** allocated dynamic mutex. Attempting to deallocate a static +** mutex results in undefined behavior. ** ** ^The sqlite3_mutex_enter() and sqlite3_mutex_try() routines attempt ** to enter a mutex. ^If another thread is already within the mutex, @@ -5952,23 +6005,21 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** SQLITE_BUSY. ^The sqlite3_mutex_try() interface returns [SQLITE_OK] ** upon successful entry. ^(Mutexes created using ** SQLITE_MUTEX_RECURSIVE can be entered multiple times by the same thread. -** In such cases the, +** In such cases, the ** mutex must be exited an equal number of times before another thread -** can enter.)^ ^(If the same thread tries to enter any other -** kind of mutex more than once, the behavior is undefined. -** SQLite will never exhibit -** such behavior in its own use of mutexes.)^ +** can enter.)^ If the same thread tries to enter any mutex other +** than an SQLITE_MUTEX_RECURSIVE more than once, the behavior is undefined. ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable behavior.)^ +** will always return SQLITE_BUSY. The SQLite core only ever uses +** sqlite3_mutex_try() as an optimization so this is acceptable +** behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was -** previously entered by the same thread. ^(The behavior +** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered by the -** calling thread or is not currently allocated. SQLite will -** never do either.)^ +** calling thread or is not currently allocated. ** ** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or ** sqlite3_mutex_leave() is a NULL pointer, then all three routines @@ -5989,9 +6040,9 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); ** used to allocate and use mutexes. ** ** Usually, the default mutex implementations provided by SQLite are -** sufficient, however the user has the option of substituting a custom +** sufficient, however the application has the option of substituting a custom ** implementation for specialized deployments or systems for which SQLite -** does not provide a suitable implementation. In this case, the user +** does not provide a suitable implementation. In this case, the application ** creates and populates an instance of this structure to pass ** to sqlite3_config() along with the [SQLITE_CONFIG_MUTEX] option. ** Additionally, an instance of this structure can be used as an @@ -6032,13 +6083,13 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). ** -** The xMutexInit() method must be threadsafe. ^It must be harmless to +** The xMutexInit() method must be threadsafe. It must be harmless to ** invoke xMutexInit() multiple times within the same process and without ** intervening calls to xMutexEnd(). Second and subsequent calls to ** xMutexInit() must be no-ops. ** -** ^xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] -** and its associates). ^Similarly, xMutexAlloc() must not use SQLite memory +** xMutexInit() must not use SQLite memory allocation ([sqlite3_malloc()] +** and its associates). Similarly, xMutexAlloc() must not use SQLite memory ** allocation for a static mutex. ^However xMutexAlloc() may use SQLite ** memory allocation for a fast or recursive mutex. ** @@ -6064,29 +6115,29 @@ struct sqlite3_mutex_methods { ** CAPI3REF: Mutex Verification Routines ** ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routines -** are intended for use inside assert() statements. ^The SQLite core +** are intended for use inside assert() statements. The SQLite core ** never uses these routines except inside an assert() and applications -** are advised to follow the lead of the core. ^The SQLite core only +** are advised to follow the lead of the core. The SQLite core only ** provides implementations for these routines when it is compiled -** with the SQLITE_DEBUG flag. ^External mutex implementations +** with the SQLITE_DEBUG flag. External mutex implementations ** are only required to provide these routines if SQLITE_DEBUG is ** defined and if NDEBUG is not defined. ** -** ^These routines should return true if the mutex in their argument +** These routines should return true if the mutex in their argument ** is held or not held, respectively, by the calling thread. ** -** ^The implementation is not required to provide versions of these +** The implementation is not required to provide versions of these ** routines that actually work. If the implementation does not provide working ** versions of these routines, it should at least provide stubs that always ** return true so that one does not get spurious assertion failures. ** -** ^If the argument to sqlite3_mutex_held() is a NULL pointer then +** If the argument to sqlite3_mutex_held() is a NULL pointer then ** the routine should return 1. This seems counter-intuitive since ** clearly the mutex cannot be held if it does not exist. But ** the reason the mutex does not exist is because the build is not ** using mutexes. And we do not want the assert() containing the ** call to sqlite3_mutex_held() to fail, so a non-zero return is -** the appropriate thing to do. ^The sqlite3_mutex_notheld() +** the appropriate thing to do. The sqlite3_mutex_notheld() ** interface should also return 1 when given a NULL pointer. */ #ifndef NDEBUG @@ -6819,6 +6870,10 @@ typedef struct sqlite3_backup sqlite3_backup; ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** +** ^A call to sqlite3_backup_init() will fail, returning SQLITE_ERROR, if +** there is already a read or read-write transaction open on the +** destination database. +** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is ** returned and an error code and error message are stored in the ** destination [database connection] D. @@ -7142,12 +7197,10 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** CAPI3REF: Write-Ahead Log Commit Hook ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that -** will be invoked each time a database connection commits data to a -** [write-ahead log] (i.e. whenever a transaction is committed in -** [journal_mode | journal_mode=WAL mode]). +** is invoked each time data is committed to a database in wal mode. ** -** ^The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked @@ -7212,97 +7265,114 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); /* ** CAPI3REF: Checkpoint a database ** -** ^The [sqlite3_wal_checkpoint(D,X)] interface causes database named X -** on [database connection] D to be [checkpointed]. ^If X is NULL or an -** empty string, then a checkpoint is run on all databases of -** connection D. ^If the database connection D is not in -** [WAL | write-ahead log mode] then this interface is a harmless no-op. -** ^The [sqlite3_wal_checkpoint(D,X)] interface initiates a -** [sqlite3_wal_checkpoint_v2|PASSIVE] checkpoint. -** Use the [sqlite3_wal_checkpoint_v2()] interface to get a FULL -** or RESET checkpoint. +** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to +** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ ** -** ^The [wal_checkpoint pragma] can be used to invoke this interface -** from SQL. ^The [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] can be used to cause this interface to be -** run whenever the WAL reaches a certain size threshold. +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** [write-ahead log] for database X on [database connection] D to be +** transferred into the database file and for the write-ahead log to +** be reset. See the [checkpointing] documentation for addition +** information. ** -** See also: [sqlite3_wal_checkpoint_v2()] +** This interface used to be the only way to cause a checkpoint to +** occur. But then the newer and more powerful [sqlite3_wal_checkpoint_v2()] +** interface was added. This interface is retained for backwards +** compatibility and as a convenience for applications that need to manually +** start a callback but which do not need the full power (and corresponding +** complication) of [sqlite3_wal_checkpoint_v2()]. */ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); /* ** CAPI3REF: Checkpoint a database ** -** Run a checkpoint operation on WAL database zDb attached to database -** handle db. The specific operation is determined by the value of the -** eMode parameter: +** ^(The sqlite3_wal_checkpoint_v2(D,X,M,L,C) interface runs a checkpoint +** operation on database X of [database connection] D in mode M. Status +** information is written back into integers pointed to by L and C.)^ +** ^(The M parameter must be a valid [checkpoint mode]:)^ ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    -** Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish. Sync the db file if all frames in the log -** are checkpointed. This mode is the same as calling -** sqlite3_wal_checkpoint(). The [sqlite3_busy_handler|busy-handler callback] -** is never invoked. +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames +** in the log were checkpointed. ^The [busy-handler callback] +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** ^On the other hand, passive mode might leave the checkpoint unfinished +** if there are concurrent readers or writers. ** **
    SQLITE_CHECKPOINT_FULL
    -** This mode blocks (it invokes the +** ^This mode blocks (it invokes the ** [sqlite3_busy_handler|busy-handler callback]) until there is no ** database writer and all readers are reading from the most recent database -** snapshot. It then checkpoints all frames in the log file and syncs the -** database file. This call blocks database writers while it is running, -** but not database readers. +** snapshot. ^It then checkpoints all frames in the log file and syncs the +** database file. ^This mode blocks new database writers while it is pending, +** but new database readers are allowed to continue unimpeded. ** **
    SQLITE_CHECKPOINT_RESTART
    -** This mode works the same way as SQLITE_CHECKPOINT_FULL, except after -** checkpointing the log file it blocks (calls the -** [sqlite3_busy_handler|busy-handler callback]) -** until all readers are reading from the database file only. This ensures -** that the next client to write to the database file restarts the log file -** from the beginning. This call blocks database writers while it is running, -** but not database readers. +** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition +** that after checkpointing the log file it blocks (calls the +** [busy-handler callback]) +** until all readers are reading from the database file only. ^This ensures +** that the next writer will restart the log file from the beginning. +** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new +** database writer attempts while it is pending, but does not impede readers. +** +**
    SQLITE_CHECKPOINT_TRUNCATE
    +** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the +** addition that it also truncates the log file to zero bytes just prior +** to a successful return. **
    ** -** If pnLog is not NULL, then *pnLog is set to the total number of frames in -** the log file before returning. If pnCkpt is not NULL, then *pnCkpt is set to -** the total number of checkpointed frames (including any that were already -** checkpointed when this function is called). *pnLog and *pnCkpt may be -** populated even if sqlite3_wal_checkpoint_v2() returns other than SQLITE_OK. -** If no values are available because of an error, they are both set to -1 -** before returning to communicate this to the caller. +** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in +** the log file or to -1 if the checkpoint could not run because +** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not +** NULL,then *pnCkpt is set to the total number of checkpointed frames in the +** log file (including any that were already checkpointed before the function +** was called) or to -1 if the checkpoint could not run due to an error or +** because the database is not in WAL mode. ^Note that upon successful +** completion of an SQLITE_CHECKPOINT_TRUNCATE, the log file will have been +** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. ** -** All calls obtain an exclusive "checkpoint" lock on the database file. If +** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If ** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. Even if there is a +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a ** busy-handler configured, it will not be invoked in this case. ** -** The SQLITE_CHECKPOINT_FULL and RESTART modes also obtain the exclusive -** "writer" lock on the database file. If the writer lock cannot be obtained -** immediately, and a busy-handler is configured, it is invoked and the writer -** lock retried until either the busy-handler returns 0 or the lock is -** successfully obtained. The busy-handler is also invoked while waiting for -** database readers as described above. If the busy-handler returns 0 before +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** exclusive "writer" lock on the database file. ^If the writer lock cannot be +** obtained immediately, and a busy-handler is configured, it is invoked and +** the writer lock retried until either the busy-handler returns 0 or the lock +** is successfully obtained. ^The busy-handler is also invoked while waiting for +** database readers as described above. ^If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the ** checkpoint operation proceeds from that point in the same way as ** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible -** without blocking any further. SQLITE_BUSY is returned in this case. +** without blocking any further. ^SQLITE_BUSY is returned in this case. ** -** If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. If +** ^If parameter zDb is NULL or points to a zero length string, then the +** specified operation is attempted on all WAL databases [attached] to +** [database connection] db. In this case the +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If ** an SQLITE_BUSY error is encountered when processing one or more of the ** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned to the caller. If any other +** attached databases and SQLITE_BUSY is returned at the end. ^If any other ** error occurs while processing an attached database, processing is abandoned -** and the error code returned to the caller immediately. If no error +** and the error code is returned to the caller immediately. ^If no error ** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** -** If database zDb is the name of an attached database that is not in WAL -** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. If +** ^If database zDb is the name of an attached database that is not in WAL +** mode, SQLITE_OK is returned and both *pnLog and *pnCkpt set to -1. ^If ** zDb is not NULL (or a zero length string) and is not the name of any ** attached database, SQLITE_ERROR is returned to the caller. +** +** ^Unless it returns SQLITE_MISUSE, +** the sqlite3_wal_checkpoint_v2() interface +** sets the error information that is queried by +** [sqlite3_errcode()] and [sqlite3_errmsg()]. +** +** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface +** from SQL. */ SQLITE_API int sqlite3_wal_checkpoint_v2( sqlite3 *db, /* Database handle */ @@ -7313,16 +7383,18 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ); /* -** CAPI3REF: Checkpoint operation parameters +** CAPI3REF: Checkpoint Mode Values +** KEYWORDS: {checkpoint mode} ** -** These constants can be used as the 3rd parameter to -** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()] -** documentation for additional information about the meaning and use of -** each of these values. +** These constants define all valid values for the "checkpoint mode" passed +** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface. +** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the +** meaning of each of these checkpoint modes. */ -#define SQLITE_CHECKPOINT_PASSIVE 0 -#define SQLITE_CHECKPOINT_FULL 1 -#define SQLITE_CHECKPOINT_RESTART 2 +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* ** CAPI3REF: Virtual Table Interface Configuration @@ -7411,6 +7483,106 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); /* #define SQLITE_ABORT 4 // Also an error code */ #define SQLITE_REPLACE 5 +/* +** CAPI3REF: Prepared Statement Scan Status Opcodes +** KEYWORDS: {scanstatus options} +** +** The following constants can be used for the T parameter to the +** [sqlite3_stmt_scanstatus(S,X,T,V)] interface. Each constant designates a +** different metric for sqlite3_stmt_scanstatus() to return. +** +** When the value returned to V is a string, space to hold that string is +** managed by the prepared statement S and will be automatically freed when +** S is finalized. +** +**
    +** [[SQLITE_SCANSTAT_NLOOP]]
    SQLITE_SCANSTAT_NLOOP
    +**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be +** set to the total number of times that the X-th loop has run.
    +** +** [[SQLITE_SCANSTAT_NVISIT]]
    SQLITE_SCANSTAT_NVISIT
    +**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be set +** to the total number of rows examined by all iterations of the X-th loop.
    +** +** [[SQLITE_SCANSTAT_EST]]
    SQLITE_SCANSTAT_EST
    +**
    ^The "double" variable pointed to by the T parameter will be set to the +** query planner's estimate for the average number of rows output from each +** iteration of the X-th loop. If the query planner's estimates was accurate, +** then this value will approximate the quotient NVISIT/NLOOP and the +** product of this value for all prior loops with the same SELECTID will +** be the NLOOP value for the current loop. +** +** [[SQLITE_SCANSTAT_NAME]]
    SQLITE_SCANSTAT_NAME
    +**
    ^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the name of the index or table +** used for the X-th loop. +** +** [[SQLITE_SCANSTAT_EXPLAIN]]
    SQLITE_SCANSTAT_EXPLAIN
    +**
    ^The "const char *" variable pointed to by the T parameter will be set +** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] +** description for the X-th loop. +** +** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECT
    +**
    ^The "int" variable pointed to by the T parameter will be set to the +** "select-id" for the X-th loop. The select-id identifies which query or +** subquery the loop is part of. The main query has a select-id of zero. +** The select-id is the same value as is output in the first column +** of an [EXPLAIN QUERY PLAN] query. +**
    +*/ +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_SELECTID 5 + +/* +** CAPI3REF: Prepared Statement Scan Status +** +** This interface returns information about the predicted and measured +** performance for pStmt. Advanced applications can use this +** interface to compare the predicted and the measured performance and +** issue warnings and/or rerun [ANALYZE] if discrepancies are found. +** +** Since this interface is expected to be rarely used, it is only +** available if SQLite is compiled using the [SQLITE_ENABLE_STMT_SCANSTATUS] +** compile-time option. +** +** The "iScanStatusOp" parameter determines which status information to return. +** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior +** of this interface is undefined. +** ^The requested measurement is written into a variable pointed to by +** the "pOut" parameter. +** Parameter "idx" identifies the specific loop to retrieve statistics for. +** Loops are numbered starting from zero. ^If idx is out of range - less than +** zero or greater than or equal to the total number of loops used to implement +** the statement - a non-zero value is returned and the variable that pOut +** points to is unchanged. +** +** ^Statistics might not be available for all loops in all statements. ^In cases +** where there exist loops with no available statistics, this function behaves +** as if the loop did not exist - it returns non-zero and leave the variable +** that pOut points to unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Zero Scan-Status Counters +** +** ^Zero all [sqlite3_stmt_scanstatus()] related event counters. +** +** This API is only available if the library is built with pre-processor +** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. +*/ +SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); /* diff --git a/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so b/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so index 46e200f5..132154ab 100755 Binary files a/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so and b/TMessagesProj/libs/armeabi-v7a/libtmessages.5.so differ diff --git a/TMessagesProj/libs/armeabi/libtmessages.5.so b/TMessagesProj/libs/armeabi/libtmessages.5.so index 397e267c..3e08cd8f 100755 Binary files a/TMessagesProj/libs/armeabi/libtmessages.5.so and b/TMessagesProj/libs/armeabi/libtmessages.5.so differ diff --git a/TMessagesProj/libs/x86/libtmessages.5.so b/TMessagesProj/libs/x86/libtmessages.5.so index ead3c521..326a064f 100755 Binary files a/TMessagesProj/libs/x86/libtmessages.5.so and b/TMessagesProj/libs/x86/libtmessages.5.so differ diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml index a85bdd03..5ca90c91 100644 --- a/TMessagesProj/src/main/AndroidManifest.xml +++ b/TMessagesProj/src/main/AndroidManifest.xml @@ -149,6 +149,7 @@ + diff --git a/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java index 555806d1..1e9a454a 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/android/ImageLoader.java @@ -18,6 +18,7 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.graphics.drawable.BitmapDrawable; import android.media.ExifInterface; +import android.media.ThumbnailUtils; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -56,12 +57,19 @@ public class ImageLoader { private HashMap bitmapUseCounts = new HashMap<>(); private LruCache memCache; - private ConcurrentHashMap imageLoadingByUrl = new ConcurrentHashMap<>(); - private ConcurrentHashMap imageLoadingByKeys = new ConcurrentHashMap<>(); + private HashMap imageLoadingByUrl = new HashMap<>(); + private HashMap imageLoadingByKeys = new HashMap<>(); private HashMap imageLoadingByTag = new HashMap<>(); + private HashMap waitingForQualityThumb = new HashMap<>(); + private HashMap waitingForQualityThumbByTag = new HashMap<>(); private LinkedList httpTasks = new LinkedList<>(); private DispatchQueue cacheOutQueue = new DispatchQueue("cacheOutQueue"); + private DispatchQueue cacheThumbOutQueue = new DispatchQueue("cacheThumbOutQueue"); + private DispatchQueue thumbGeneratingQueue = new DispatchQueue("thumbGeneratingQueue"); + private DispatchQueue imageLoadQueue = new DispatchQueue("imageLoadQueue"); + private DispatchQueue recycleQueue = new DispatchQueue("recycleQueue"); private ConcurrentHashMap fileProgresses = new ConcurrentHashMap<>(); + private HashMap thumbGenerateTasks = new HashMap<>(); private int currentHttpTasksCount = 0; private LinkedList httpFileLoadTasks = new LinkedList<>(); @@ -78,6 +86,12 @@ public class ImageLoader { private File telegramPath = null; + private class ThumbGenerateInfo { + private int count; + private TLRPC.FileLocation fileLocation; + private String filter; + } + private class HttpFileTask extends AsyncTask { private String url; @@ -298,7 +312,9 @@ public class ImageLoader { if (done) { if (cacheImage.tempFilePath != null) { - cacheImage.tempFilePath.renameTo(cacheImage.finalFilePath); + if (!cacheImage.tempFilePath.renameTo(cacheImage.finalFilePath)) { + cacheImage.finalFilePath = cacheImage.tempFilePath; + } } } @@ -308,7 +324,7 @@ public class ImageLoader { @Override protected void onPostExecute(final Boolean result) { if (result || !canRetry) { - fileDidLoaded(cacheImage.url, cacheImage.finalFilePath, cacheImage.tempFilePath); + fileDidLoaded(cacheImage.url, cacheImage.finalFilePath, FileLoader.MEDIA_DIR_IMAGE); } else { httpFileLoadError(cacheImage.url); } @@ -359,15 +375,120 @@ public class ImageLoader { } } + private class ThumbGenerateTask implements Runnable { + + private File originalPath; + private int mediaType; + private TLRPC.FileLocation thumbLocation; + private String filter; + + public ThumbGenerateTask(int type, File path, TLRPC.FileLocation location, String f) { + mediaType = type; + originalPath = path; + thumbLocation = location; + filter = f; + } + + private void removeTask() { + if (thumbLocation == null) { + return; + } + final String name = FileLoader.getAttachFileName(thumbLocation); + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + thumbGenerateTasks.remove(name); + } + }); + } + + @Override + public void run() { + try { + if (thumbLocation == null) { + removeTask(); + return; + } + final String key = thumbLocation.volume_id + "_" + thumbLocation.local_id; + File thumbFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + key + ".jpg"); + if (thumbFile.exists() || !originalPath.exists()) { + removeTask(); + return; + } + int size = Math.min(180, Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / 4); + Bitmap originalBitmap = null; + if (mediaType == FileLoader.MEDIA_DIR_IMAGE) { + originalBitmap = ImageLoader.loadBitmap(originalPath.toString(), null, size, size, false); + } else if (mediaType == FileLoader.MEDIA_DIR_VIDEO) { + originalBitmap = ThumbnailUtils.createVideoThumbnail(originalPath.toString(), MediaStore.Video.Thumbnails.MINI_KIND); + } else if (mediaType == FileLoader.MEDIA_DIR_DOCUMENT) { + String path = originalPath.toString().toLowerCase(); + if (!path.endsWith(".jpg") && !path.endsWith(".jpeg") && !path.endsWith(".png") && !path.endsWith(".gif")) { + removeTask(); + return; + } + originalBitmap = ImageLoader.loadBitmap(path, null, size, size, false); + } + if (originalBitmap == null) { + removeTask(); + return; + } + + int w = originalBitmap.getWidth(); + int h = originalBitmap.getHeight(); + if (w == 0 || h == 0) { + removeTask(); + return; + } + float scaleFactor = Math.min((float) w / size, (float) h / size); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, (int) (w / scaleFactor), (int) (h / scaleFactor), true); + if (scaledBitmap != originalBitmap) { + originalBitmap.recycle(); + callGC(); + } + originalBitmap = scaledBitmap; + FileOutputStream stream = new FileOutputStream(thumbFile); + originalBitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream); + final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + removeTask(); + + String kf = key; + if (filter != null) { + kf += "@" + filter; + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageThumbGenerated, bitmapDrawable, kf); + /*BitmapDrawable old = memCache.get(kf); + if (old != null) { + Bitmap image = old.getBitmap(); + if (runtimeHack != null) { + runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight()); + } + if (!image.isRecycled()) { + image.recycle(); + } + }*/ + memCache.put(kf, bitmapDrawable); + } + }); + } catch (Throwable e) { + FileLog.e("tmessages", e); + removeTask(); + } + } + } + private class CacheOutTask implements Runnable { - private Thread runningThread = null; + private Thread runningThread; private final Object sync = new Object(); - private CacheImage cacheImage = null; - private boolean isCancelled = false; + private CacheImage cacheImage; + private boolean isCancelled; - public CacheOutTask(CacheImage cacheImage) { - this.cacheImage = cacheImage; + public CacheOutTask(CacheImage image) { + cacheImage = image; } @Override @@ -382,138 +503,185 @@ public class ImageLoader { Long mediaId = null; Bitmap image = null; - File cacheFileFinal = null; + File cacheFileFinal = cacheImage.finalFilePath; boolean canDeleteFile = true; boolean isWebp = false; - if (cacheImage.finalFilePath != null && cacheImage.finalFilePath.exists()) { - cacheFileFinal = cacheImage.finalFilePath; - } else if (cacheImage.tempFilePath != null && cacheImage.tempFilePath.exists()) { - cacheFileFinal = cacheImage.tempFilePath; - } else if (cacheImage.finalFilePath != null) { - cacheFileFinal = cacheImage.finalFilePath; - } - if (cacheFileFinal.toString().endsWith("webp")) { isWebp = true; } - try { - if (cacheImage.httpUrl != null) { - if (cacheImage.httpUrl.startsWith("thumb://")) { - int idx = cacheImage.httpUrl.indexOf(":", 8); - if (idx >= 0) { - mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx)); - } - canDeleteFile = false; - } else if (!cacheImage.httpUrl.startsWith("http")) { - canDeleteFile = false; - } - } + if (cacheImage.thumb) { - int delay = 20; - if (runtimeHack != null) { - delay = 60; - } - if (mediaId != null) { - delay = 0; - } - if (delay != 0 && lastCacheOutTime != 0 && lastCacheOutTime > System.currentTimeMillis() - delay) { - Thread.sleep(delay); - } - lastCacheOutTime = System.currentTimeMillis(); - synchronized (sync) { - if (isCancelled) { - return; - } - } - - BitmapFactory.Options opts = new BitmapFactory.Options(); - - float w_filter = 0; - float h_filter = 0; - boolean blur = false; + int blurType = 0; if (cacheImage.filter != null) { - String args[] = cacheImage.filter.split("_"); - w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density; - h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density; - if (args.length > 2) { - blur = true; + if (cacheImage.filter.contains("b2")) { + blurType = 3; + } else if (cacheImage.filter.contains("b1")) { + blurType = 2; + } else if (cacheImage.filter.contains("b")) { + blurType = 1; } - opts.inJustDecodeBounds = true; + } - if (mediaId != null) { - MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts); - } else { - if (cacheImage.finalFilePath != null && cacheImage.finalFilePath.exists()) { - BitmapFactory.decodeFile(cacheImage.finalFilePath.getAbsolutePath(), opts); - } else if (cacheImage.tempFilePath != null && cacheImage.tempFilePath.exists()) { - BitmapFactory.decodeFile(cacheImage.tempFilePath.getAbsolutePath(), opts); + try { + lastCacheOutTime = System.currentTimeMillis(); + synchronized (sync) { + if (isCancelled) { + return; } } - float photoW = opts.outWidth; - float photoH = opts.outHeight; - float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter); - if (scaleFactor < 1) { - scaleFactor = 1; + if (image == null) { + if (isWebp) { + RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r"); + ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length()); + image = Utilities.loadWebpImage(buffer, buffer.limit(), null); + file.close(); + } else { + FileInputStream is = new FileInputStream(cacheFileFinal); + image = BitmapFactory.decodeStream(is, null, null); + is.close(); + } } - opts.inJustDecodeBounds = false; - opts.inSampleSize = (int)scaleFactor; - } - synchronized (sync) { - if (isCancelled) { - return; - } - } - - if (cacheImage.filter == null || blur) { - opts.inPreferredConfig = Bitmap.Config.ARGB_8888; - } else { - opts.inPreferredConfig = Bitmap.Config.RGB_565; - } - opts.inDither = false; - if (mediaId != null) { - image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, null); - } - if (image == null) { - if (isWebp) { - RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r"); - ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length()); - image = Utilities.loadWebpImage(buffer, buffer.limit(), null); - file.close(); + if (image == null) { + if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) { + cacheFileFinal.delete(); + } } else { - FileInputStream is = new FileInputStream(cacheFileFinal); - image = BitmapFactory.decodeStream(is, null, opts); - is.close(); - } - } - if (image == null) { - if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) { - cacheFileFinal.delete(); - } - } else { - if (cacheImage.filter != null) { - float bitmapW = image.getWidth(); - float bitmapH = image.getHeight(); - if (bitmapW != w_filter && bitmapW > w_filter) { - float scaleFactor = bitmapW / w_filter; - Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int)w_filter, (int)(bitmapH / scaleFactor), true); - if (image != scaledBitmap) { - image.recycle(); - image = scaledBitmap; + if (image != null) { + if (blurType == 1) { + Utilities.blurBitmap(image, 3); + } else if (blurType == 2) { + Utilities.blurBitmap(image, 1); + } else if (blurType == 3) { + Utilities.blurBitmap(image, 7); + Utilities.blurBitmap(image, 7); + Utilities.blurBitmap(image, 7); } } - if (image != null && blur && bitmapH < 100 && bitmapW < 100) { - Utilities.blurBitmap(image, 3); + if (runtimeHack != null) { + runtimeHack.trackFree(image.getRowBytes() * image.getHeight()); } } - if (runtimeHack != null) { - runtimeHack.trackFree(image.getRowBytes() * image.getHeight()); - } + } catch (Throwable e) { + FileLog.e("tmessages", e); + } + } else { + try { + if (cacheImage.httpUrl != null) { + if (cacheImage.httpUrl.startsWith("thumb://")) { + int idx = cacheImage.httpUrl.indexOf(":", 8); + if (idx >= 0) { + mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx)); + } + canDeleteFile = false; + } else if (!cacheImage.httpUrl.startsWith("http")) { + canDeleteFile = false; + } + } + + int delay = 20; + if (runtimeHack != null) { + delay = 60; + } + if (mediaId != null) { + delay = 0; + } + if (delay != 0 && lastCacheOutTime != 0 && lastCacheOutTime > System.currentTimeMillis() - delay && Build.VERSION.SDK_INT < 21) { + Thread.sleep(delay); + } + lastCacheOutTime = System.currentTimeMillis(); + synchronized (sync) { + if (isCancelled) { + return; + } + } + + BitmapFactory.Options opts = new BitmapFactory.Options(); + + float w_filter = 0; + float h_filter = 0; + boolean blur = false; + if (cacheImage.filter != null) { + String args[] = cacheImage.filter.split("_"); + w_filter = Float.parseFloat(args[0]) * AndroidUtilities.density; + h_filter = Float.parseFloat(args[1]) * AndroidUtilities.density; + if (args.length > 2) { + blur = true; + } + opts.inJustDecodeBounds = true; + + if (mediaId != null) { + MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, opts); + } else { + BitmapFactory.decodeFile(cacheImage.finalFilePath.getAbsolutePath(), opts); + } + + float photoW = opts.outWidth; + float photoH = opts.outHeight; + float scaleFactor = Math.max(photoW / w_filter, photoH / h_filter); + if (scaleFactor < 1) { + scaleFactor = 1; + } + opts.inJustDecodeBounds = false; + opts.inSampleSize = (int)scaleFactor; + } + synchronized (sync) { + if (isCancelled) { + return; + } + } + + if (cacheImage.filter == null || blur) { + opts.inPreferredConfig = Bitmap.Config.ARGB_8888; + } else { + opts.inPreferredConfig = Bitmap.Config.RGB_565; + } + opts.inDither = false; + if (mediaId != null) { + image = MediaStore.Images.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Images.Thumbnails.MINI_KIND, null); + } + if (image == null) { + if (isWebp) { + RandomAccessFile file = new RandomAccessFile(cacheFileFinal, "r"); + ByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, cacheFileFinal.length()); + image = Utilities.loadWebpImage(buffer, buffer.limit(), null); + file.close(); + } else { + FileInputStream is = new FileInputStream(cacheFileFinal); + image = BitmapFactory.decodeStream(is, null, opts); + is.close(); + } + } + if (image == null) { + if (canDeleteFile && (cacheFileFinal.length() == 0 || cacheImage.filter == null)) { + cacheFileFinal.delete(); + } + } else { + if (cacheImage.filter != null) { + float bitmapW = image.getWidth(); + float bitmapH = image.getHeight(); + if (bitmapW != w_filter && bitmapW > w_filter) { + float scaleFactor = bitmapW / w_filter; + Bitmap scaledBitmap = Bitmap.createScaledBitmap(image, (int)w_filter, (int)(bitmapH / scaleFactor), true); + if (image != scaledBitmap) { + image.recycle(); + callGC(); + image = scaledBitmap; + } + } + if (image != null && blur && bitmapH < 100 && bitmapW < 100) { + Utilities.blurBitmap(image, 3); + } + } + if (runtimeHack != null) { + runtimeHack.trackFree(image.getRowBytes() * image.getHeight()); + } + } + } catch (Throwable e) { + //don't promt } - } catch (Throwable e) { - //don't promt } Thread.interrupted(); onPostExecute(image != null ? new BitmapDrawable(image) : null); @@ -523,10 +691,28 @@ public class ImageLoader { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (bitmapDrawable != null && memCache.get(cacheImage.key) == null) { - memCache.put(cacheImage.key, bitmapDrawable); + BitmapDrawable toSet = null; + if (bitmapDrawable != null) { + toSet = memCache.get(cacheImage.key); + if (toSet == null) { + memCache.put(cacheImage.key, bitmapDrawable); + toSet = bitmapDrawable; + } else { + Bitmap image = bitmapDrawable.getBitmap(); + if (runtimeHack != null) { + runtimeHack.trackAlloc(image.getRowBytes() * image.getHeight()); + } + image.recycle(); + callGC(); + } } - cacheImage.setImageAndClear(bitmapDrawable); + final BitmapDrawable toSetFinal = toSet; + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + cacheImage.setImageAndClear(toSetFinal); + } + }); } }); } @@ -593,83 +779,97 @@ public class ImageLoader { } private class CacheImage { - protected String key = null; - protected String url = null; - protected String filter = null; - protected TLObject fileLocation = null; - protected String httpUrl = null; - protected File finalFilePath = null; - protected File tempFilePath = null; - protected CacheOutTask cacheTask; - protected HttpImageTask httpTask; - protected ArrayList imageViewArray = new ArrayList<>(); + protected String key; + protected String url; + protected String filter; + protected TLObject location; - public void addImageView(ImageReceiver imageView) { + protected File finalFilePath; + protected File tempFilePath; + protected boolean thumb; + + protected String httpUrl; + protected HttpImageTask httpTask; + protected CacheOutTask cacheTask; + + protected ArrayList imageReceiverArray = new ArrayList<>(); + + public void addImageReceiver(ImageReceiver imageReceiver) { boolean exist = false; - for (ImageReceiver v : imageViewArray) { - if (v == imageView) { + for (ImageReceiver v : imageReceiverArray) { + if (v == imageReceiver) { exist = true; break; } } if (!exist) { - imageViewArray.add(imageView); - imageLoadingByTag.put(imageView.getTag(), this); + imageReceiverArray.add(imageReceiver); + imageLoadingByTag.put(imageReceiver.getTag(thumb), this); } } - public void removeImageView(ImageReceiver imageView) { - for (int a = 0; a < imageViewArray.size(); a++) { - ImageReceiver obj = imageViewArray.get(a); - if (obj == null || obj == imageView) { - imageViewArray.remove(a); + public void removeImageReceiver(ImageReceiver imageReceiver) { + for (int a = 0; a < imageReceiverArray.size(); a++) { + ImageReceiver obj = imageReceiverArray.get(a); + if (obj == null || obj == imageReceiver) { + imageReceiverArray.remove(a); if (obj != null) { - imageLoadingByTag.remove(obj.getTag()); + imageLoadingByTag.remove(obj.getTag(thumb)); } a--; } } - - if (imageViewArray.size() == 0) { - cancelAndClear(); + if (imageReceiverArray.size() == 0) { + for (ImageReceiver receiver : imageReceiverArray) { + imageLoadingByTag.remove(receiver.getTag(thumb)); + } + imageReceiverArray.clear(); + if (location != null) { + if (location instanceof TLRPC.FileLocation) { + FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location); + } else if (location instanceof TLRPC.Document) { + FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location); + } + } + if (cacheTask != null) { + if (thumb) { + cacheThumbOutQueue.cancelRunnable(cacheTask); + } else { + cacheOutQueue.cancelRunnable(cacheTask); + } + cacheTask.cancel(); + cacheTask = null; + } + if (httpTask != null) { + httpTasks.remove(httpTask); + httpTask.cancel(true); + httpTask = null; + } + if (url != null) { + imageLoadingByUrl.remove(url); + } + if (key != null) { + imageLoadingByKeys.remove(key); + } } } - public void setImageAndClear(BitmapDrawable image) { + public void setImageAndClear(final BitmapDrawable image) { if (image != null) { - for (ImageReceiver imgView : imageViewArray) { - imgView.setImageBitmap(image, key); - } + final ArrayList finalImageReceiverArray = new ArrayList<>(imageReceiverArray); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + for (ImageReceiver imgView : finalImageReceiverArray) { + imgView.setImageBitmapByKey(image, key, thumb); + } + } + }); } - clear(); - } - - public void cancelAndClear() { - if (fileLocation != null) { - if (fileLocation instanceof TLRPC.FileLocation) { - FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) fileLocation); - } else if (fileLocation instanceof TLRPC.Document) { - FileLoader.getInstance().cancelLoadFile((TLRPC.Document) fileLocation); - } + for (ImageReceiver imageReceiver : imageReceiverArray) { + imageLoadingByTag.remove(imageReceiver.getTag(thumb)); } - if (cacheTask != null) { - cacheOutQueue.cancelRunnable(cacheTask); - cacheTask.cancel(); - cacheTask = null; - } - if (httpTask != null) { - httpTasks.remove(httpTask); - httpTask.cancel(true); - httpTask = null; - } - clear(); - } - - private void clear() { - for (ImageReceiver imageReceiver : imageViewArray) { - imageLoadingByTag.remove(imageReceiver.getTag()); - } - imageViewArray.clear(); + imageReceiverArray.clear(); if (url != null) { imageLoadingByUrl.remove(url); } @@ -711,11 +911,11 @@ public class ImageLoader { } } @Override - protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldBitmap, BitmapDrawable newBitmap) { + protected void entryRemoved(boolean evicted, String key, final BitmapDrawable oldBitmap, BitmapDrawable newBitmap) { if (ignoreRemoval != null && key != null && ignoreRemoval.equals(key)) { return; } - Integer count = bitmapUseCounts.get(key); + final Integer count = bitmapUseCounts.get(key); if (count == null || count == 0) { Bitmap b = oldBitmap.getBitmap(); if (runtimeHack != null) { @@ -768,7 +968,7 @@ public class ImageLoader { } @Override - public void fileDidLoaded(final String location, final File finalFile, final File tempFile) { + public void fileDidLoaded(final String location, final File finalFile, final int type) { fileProgresses.remove(location); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -780,20 +980,20 @@ public class ImageLoader { } } } - ImageLoader.this.fileDidLoaded(location, finalFile, tempFile); + ImageLoader.this.fileDidLoaded(location, finalFile, type); NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidLoaded, location); } }); } @Override - public void fileDidFailedLoad(final String location, final int state) { + public void fileDidFailedLoad(final String location, final int canceled) { fileProgresses.remove(location); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - ImageLoader.this.fileDidFailedLoad(location); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, state); + ImageLoader.this.fileDidFailedLoad(location, canceled); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileDidFailedLoad, location, canceled); } }); } @@ -971,6 +1171,17 @@ public class ImageLoader { } } + public void callGC() { + if (Build.VERSION.SDK_INT > 13) { + recycleQueue.postRunnable(new Runnable() { + @Override + public void run() { + System.gc(); + } + }); + } + } + public boolean decrementUseCount(String key) { Integer count = bitmapUseCounts.get(key); if (count == null) { @@ -998,25 +1209,55 @@ public class ImageLoader { memCache.evictAll(); } - public void cancelLoadingForImageView(ImageReceiver imageView) { - if (imageView == null) { - return; - } - Integer TAG = imageView.getTag(); - if (TAG == null) { - imageView.setTag(TAG = lastImageNum); - lastImageNum++; - if (lastImageNum == Integer.MAX_VALUE) { - lastImageNum = 0; + private void removeFromWaitingForThumb(Integer TAG) { + String location = waitingForQualityThumbByTag.get(TAG); + if (location != null) { + ThumbGenerateInfo info = waitingForQualityThumb.get(location); + if (info != null) { + info.count--; + if (info.count == 0) { + waitingForQualityThumb.remove(location); + } } - } - CacheImage ei = imageLoadingByTag.get(TAG); - if (ei != null) { - ei.removeImageView(imageView); + waitingForQualityThumbByTag.remove(TAG); } } - public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, String filter, ImageReceiver imageReceiver) { + public void cancelLoadingForImageReceiver(final ImageReceiver imageReceiver, final int type) { + if (imageReceiver == null) { + return; + } + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + int start = 0; + int count = 2; + if (type == 1) { + count = 1; + } else if (type == 2) { + start = 1; + } + for (int a = start; a < count; a++) { + Integer TAG = imageReceiver.getTag(a == 0); + if (a == 0) { + removeFromWaitingForThumb(TAG); + } + if (TAG != null) { + CacheImage ei = imageLoadingByTag.get(TAG); + if (ei != null) { + ei.removeImageReceiver(imageReceiver); + } + } + } + } + }); + } + + public BitmapDrawable getImageFromMemory(String key) { + return memCache.get(key); + } + + public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, String filter) { if (fileLocation == null && httpUrl == null) { return null; } @@ -1035,17 +1276,7 @@ public class ImageLoader { if (filter != null) { key += "@" + filter; } - BitmapDrawable bitmapDrawable = memCache.get(key); - if (bitmapDrawable != null && imageReceiver != null) { - Integer TAG = imageReceiver.getTag(); - if (TAG != null) { - CacheImage alreadyLoadingImage = imageLoadingByTag.get(TAG); - if (alreadyLoadingImage != null) { - alreadyLoadingImage.removeImageView(imageReceiver); - } - } - } - return bitmapDrawable; + return memCache.get(key); } public void replaceImageInCache(final String oldKey, final String newKey) { @@ -1068,177 +1299,331 @@ public class ImageLoader { memCache.put(key, bitmap); } - public void loadImage(final TLObject fileLocation, final String httpUrl, final ImageReceiver imageView, final int size, final boolean cacheOnly) { - if ((fileLocation == null && httpUrl == null) || imageView == null || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation) && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation) && !(fileLocation instanceof TLRPC.TL_document))) { + private void generateThumb(int mediaType, File originalPath, TLRPC.FileLocation thumbLocation, String filter) { + if (mediaType != FileLoader.MEDIA_DIR_IMAGE && mediaType != FileLoader.MEDIA_DIR_VIDEO && mediaType != FileLoader.MEDIA_DIR_DOCUMENT || originalPath == null || thumbLocation == null) { return; } - - String url = null; - String key = null; - boolean writeToCache = false; - if (httpUrl != null) { - key = Utilities.MD5(httpUrl); - url = key + "." + getHttpUrlExtension(httpUrl); - } else { - if (fileLocation instanceof TLRPC.FileLocation) { - TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation; - key = location.volume_id + "_" + location.local_id; - url = key + "." + (location.ext != null ? location.ext : "jpg"); - if (location.ext != null) { - writeToCache = true; - } - if (!writeToCache) { - writeToCache = location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0; - } - } else if (fileLocation instanceof TLRPC.Document) { - TLRPC.Document location = (TLRPC.Document) fileLocation; - if (location.id == 0 || location.dc_id == 0) { - return; - } - key = location.dc_id + "_" + location.id; - url = key + ".webp"; - writeToCache = true; - } - } - String filter = imageView.getFilter(); - if (filter != null) { - key += "@" + filter; + String name = FileLoader.getAttachFileName(thumbLocation); + ThumbGenerateTask task = thumbGenerateTasks.get(name); + if (task == null) { + task = new ThumbGenerateTask(mediaType, originalPath, thumbLocation, filter); + thumbGeneratingQueue.postRunnable(task); } + } - Integer TAG = imageView.getTag(); + private void createLoadOperationForImageReceiver(final ImageReceiver imageReceiver, final String key, final String url, final TLObject imageLocation, final String httpLocation, final String filter, final int size, final boolean cacheOnly, final int thumb) { + if (imageReceiver == null || url == null || key == null) { + return; + } + Integer TAG = imageReceiver.getTag(thumb != 0); if (TAG == null) { - imageView.setTag(TAG = lastImageNum); + imageReceiver.setTag(TAG = lastImageNum, thumb != 0); lastImageNum++; if (lastImageNum == Integer.MAX_VALUE) { lastImageNum = 0; } } - boolean added = false; - CacheImage alreadyLoadingUrl = imageLoadingByUrl.get(url); - CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key); - CacheImage alreadyLoadingImage = imageLoadingByTag.get(TAG); - if (alreadyLoadingImage != null) { - if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) { - added = true; - } else { - alreadyLoadingImage.removeImageView(imageView); - } - } - - if (!added && alreadyLoadingCache != null) { - alreadyLoadingCache.addImageView(imageView); - added = true; - } - if (!added && alreadyLoadingUrl != null) { - alreadyLoadingUrl.addImageView(imageView); - added = true; - } - - if (!added) { - boolean onlyCache = false; - File cacheFile = null; - if (cacheOnly || size == 0 || httpUrl != null || fileLocation != null && writeToCache) { - cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url); - } else { - cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url); - } - if (httpUrl != null) { - if (!httpUrl.startsWith("http")) { - onlyCache = true; - if (httpUrl.startsWith("thumb://")) { - int idx = httpUrl.indexOf(":", 8); - if (idx >= 0) { - cacheFile = new File(httpUrl.substring(idx + 1)); + final Integer finalTag = TAG; + final boolean finalIsNeedsQualityThumb = imageReceiver.isNeedsQualityThumb(); + final MessageObject parentMessageObject = imageReceiver.getParentMessageObject(); + final boolean shouldGenerateQualityThumb = imageReceiver.isShouldGenerateQualityThumb(); + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + boolean added = false; + if (thumb != 2) { + CacheImage alreadyLoadingUrl = imageLoadingByUrl.get(url); + CacheImage alreadyLoadingCache = imageLoadingByKeys.get(key); + CacheImage alreadyLoadingImage = imageLoadingByTag.get(finalTag); + if (alreadyLoadingImage != null) { + if (alreadyLoadingImage == alreadyLoadingUrl || alreadyLoadingImage == alreadyLoadingCache) { + added = true; + } else { + alreadyLoadingImage.removeImageReceiver(imageReceiver); + } + } + + if (!added && alreadyLoadingCache != null) { + alreadyLoadingCache.addImageReceiver(imageReceiver); + added = true; + } + if (!added && alreadyLoadingUrl != null) { + alreadyLoadingUrl.addImageReceiver(imageReceiver); + added = true; + } + } + + if (!added) { + boolean onlyCache = false; + boolean isQuality = false; + File cacheFile = null; + + if (httpLocation != null) { + if (!httpLocation.startsWith("http")) { + onlyCache = true; + if (httpLocation.startsWith("thumb://")) { + int idx = httpLocation.indexOf(":", 8); + if (idx >= 0) { + cacheFile = new File(httpLocation.substring(idx + 1)); + } + } else { + cacheFile = new File(httpLocation); + } + } + } else if (thumb != 0) { + if (finalIsNeedsQualityThumb) { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "q_" + url); + if (!cacheFile.exists()) { + cacheFile = null; + } + } + + if (parentMessageObject != null) { + File attachPath = null; + if (parentMessageObject.messageOwner.attachPath != null && parentMessageObject.messageOwner.attachPath.length() > 0) { + attachPath = new File(parentMessageObject.messageOwner.attachPath); + if (!attachPath.exists()) { + attachPath = null; + } + } + if (attachPath == null) { + attachPath = FileLoader.getPathToMessage(parentMessageObject.messageOwner); + } + if (finalIsNeedsQualityThumb && cacheFile == null) { + String location = parentMessageObject.getFileName(); + ThumbGenerateInfo info = waitingForQualityThumb.get(location); + if (info == null) { + info = new ThumbGenerateInfo(); + info.fileLocation = (TLRPC.TL_fileLocation) imageLocation; + info.filter = filter; + waitingForQualityThumb.put(location, info); + } + info.count++; + waitingForQualityThumbByTag.put(finalTag, location); + } + if (attachPath.exists() && shouldGenerateQualityThumb) { + generateThumb(parentMessageObject.getFileType(), attachPath, (TLRPC.TL_fileLocation) imageLocation, filter); + } + } + } + + if (thumb != 2) { + if (cacheFile == null) { + if (cacheOnly || size == 0 || httpLocation != null) { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url); + } else { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url); + } + } + + CacheImage img = new CacheImage(); + img.thumb = thumb != 0; + img.key = key; + img.filter = filter; + img.httpUrl = httpLocation; + img.addImageReceiver(imageReceiver); + if (onlyCache || cacheFile.exists()) { + img.finalFilePath = cacheFile; + img.cacheTask = new CacheOutTask(img); + imageLoadingByKeys.put(key, img); + if (thumb != 0) { + cacheThumbOutQueue.postRunnable(img.cacheTask); + } else { + cacheOutQueue.postRunnable(img.cacheTask); + } + } else { + img.url = url; + img.location = imageLocation; + imageLoadingByUrl.put(url, img); + if (httpLocation == null) { + if (imageLocation instanceof TLRPC.FileLocation) { + TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation; + FileLoader.getInstance().loadFile(location, size, size == 0 || location.key != null || cacheOnly); + } else if (imageLocation instanceof TLRPC.Document) { + FileLoader.getInstance().loadFile((TLRPC.Document) imageLocation, true, true); + } + } else { + String file = Utilities.MD5(httpLocation); + File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE); + img.tempFilePath = new File(cacheDir, file + "_temp.jpg"); + img.finalFilePath = cacheFile; + img.httpTask = new HttpImageTask(img, size); + httpTasks.add(img.httpTask); + runHttpTasks(false); + } } - } else { - cacheFile = new File(httpUrl); } } } - CacheImage img = new CacheImage(); - if (onlyCache || cacheFile.exists()) { - img.finalFilePath = cacheFile; - img.key = key; - img.httpUrl = httpUrl; - if (imageView.getFilter() != null) { - img.filter = imageView.getFilter(); + }); + } + + public void loadImageForImageReceiver(ImageReceiver imageReceiver) { + if (imageReceiver == null) { + return; + } + + String key = imageReceiver.getKey(); + if (key != null) { + BitmapDrawable bitmapDrawable = memCache.get(key); + if (bitmapDrawable != null) { + cancelLoadingForImageReceiver(imageReceiver, 0); + if (!imageReceiver.isForcePreview()) { + imageReceiver.setImageBitmapByKey(bitmapDrawable, key, false); + return; } - img.addImageView(imageView); - imageLoadingByKeys.put(key, img); - img.cacheTask = new CacheOutTask(img); - cacheOutQueue.postRunnable(img.cacheTask); + } + } + boolean thumbSet = false; + String thumbKey = imageReceiver.getThumbKey(); + if (thumbKey != null) { + BitmapDrawable bitmapDrawable = memCache.get(thumbKey); + if (bitmapDrawable != null) { + imageReceiver.setImageBitmapByKey(bitmapDrawable, thumbKey, true); + cancelLoadingForImageReceiver(imageReceiver, 1); + thumbSet = true; + } + } + + TLRPC.FileLocation thumbLocation = imageReceiver.getThumbLocation(); + TLObject imageLocation = imageReceiver.getImageLocation(); + String httpLocation = imageReceiver.getHttpImageLocation(); + + boolean saveImageToCache = false; + + String url = null; + String thumbUrl = null; + key = null; + thumbKey = null; + String ext = null; + if (httpLocation != null) { + key = Utilities.MD5(httpLocation); + url = key + "." + getHttpUrlExtension(httpLocation); + } else if (imageLocation != null) { + if (imageLocation instanceof TLRPC.FileLocation) { + TLRPC.FileLocation location = (TLRPC.FileLocation) imageLocation; + key = location.volume_id + "_" + location.local_id; + ext = "." + (location.ext != null ? location.ext : "jpg"); + url = key + ext; + if (location.ext != null || location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0) { + saveImageToCache = true; + } + } else if (imageLocation instanceof TLRPC.Document) { + TLRPC.Document location = (TLRPC.Document) imageLocation; + if (location.id == 0 || location.dc_id == 0) { + return; + } + key = location.dc_id + "_" + location.id; + ext = ".webp"; + url = key + ext; + if (thumbKey != null) { + thumbUrl = thumbKey + ext; + } + saveImageToCache = true; + } + if (imageLocation == thumbLocation) { + imageLocation = null; + key = null; + url = null; + } + } + + if (thumbLocation != null) { + thumbKey = thumbLocation.volume_id + "_" + thumbLocation.local_id; + if (ext != null) { + thumbUrl = thumbKey + ext; } else { - img.url = url; - img.fileLocation = fileLocation; - img.httpUrl = httpUrl; - img.addImageView(imageView); - imageLoadingByUrl.put(url, img); - if (httpUrl == null) { - if (fileLocation instanceof TLRPC.FileLocation) { - TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation; - FileLoader.getInstance().loadFile(location, size, size == 0 || location.key != null || cacheOnly); - } else if (fileLocation instanceof TLRPC.Document) { - FileLoader.getInstance().loadFile((TLRPC.Document) fileLocation, true, true); + thumbUrl = thumbKey + "." + (thumbLocation.ext != null ? thumbLocation.ext : "jpg"); + } + } + + String filter = imageReceiver.getFilter(); + String thumbFilter = imageReceiver.getThumbFilter(); + if (key != null && filter != null) { + key += "@" + filter; + } + if (thumbKey != null && thumbFilter != null) { + thumbKey += "@" + thumbFilter; + } + + if (httpLocation != null) { + createLoadOperationForImageReceiver(imageReceiver, key, url, null, httpLocation, filter, 0, true, 0); + } else { + createLoadOperationForImageReceiver(imageReceiver, thumbKey, thumbUrl, thumbLocation, null, thumbFilter, 0, true, thumbSet ? 2 : 1); + createLoadOperationForImageReceiver(imageReceiver, key, url, imageLocation, null, filter, imageReceiver.getSize(), saveImageToCache || imageReceiver.getCacheOnly(), 0); + } + } + + private void httpFileLoadError(final String location) { + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + CacheImage img = imageLoadingByUrl.get(location); + if (img == null) { + return; + } + HttpImageTask oldTask = img.httpTask; + img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize); + httpTasks.add(img.httpTask); + runHttpTasks(false); + } + }); + } + + private void fileDidLoaded(final String location, final File finalFile, final int type) { + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + ThumbGenerateInfo info = waitingForQualityThumb.get(location); + if (info != null) { + generateThumb(type, finalFile, info.fileLocation, info.filter); + waitingForQualityThumb.remove(location); + } + CacheImage img = imageLoadingByUrl.get(location); + if (img == null) { + return; + } + imageLoadingByUrl.remove(location); + CacheOutTask task = null; + for (ImageReceiver imageReceiver : img.imageReceiverArray) { + CacheImage cacheImage = imageLoadingByKeys.get(img.key); + if (cacheImage == null) { + cacheImage = new CacheImage(); + cacheImage.finalFilePath = finalFile; + cacheImage.key = img.key; + cacheImage.httpUrl = img.httpUrl; + cacheImage.thumb = img.thumb; + cacheImage.cacheTask = task = new CacheOutTask(cacheImage); + cacheImage.filter = img.filter; + imageLoadingByKeys.put(cacheImage.key, cacheImage); + } + cacheImage.addImageReceiver(imageReceiver); + } + if (task != null) { + if (img.thumb) { + cacheThumbOutQueue.postRunnable(task); + } else { + cacheOutQueue.postRunnable(task); } - } else { - String file = Utilities.MD5(httpUrl); - File cacheDir = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE); - img.tempFilePath = new File(cacheDir, file + "_temp.jpg"); - img.finalFilePath = cacheFile; - img.httpTask = new HttpImageTask(img, size); - httpTasks.add(img.httpTask); - runHttpTasks(false); } } - } + }); } - private void httpFileLoadError(String location) { - CacheImage img = imageLoadingByUrl.get(location); - if (img == null) { + private void fileDidFailedLoad(final String location, int canceled) { + if (canceled == 1) { return; } - HttpImageTask oldTask = img.httpTask; - img.httpTask = new HttpImageTask(oldTask.cacheImage, oldTask.imageSize); - httpTasks.add(img.httpTask); - runHttpTasks(false); - } - - private void fileDidLoaded(String location, File finalFile, File tempFile) { - CacheImage img = imageLoadingByUrl.get(location); - if (img == null) { - return; - } - imageLoadingByUrl.remove(location); - for (ImageReceiver imageReceiver : img.imageViewArray) { - String key = imageReceiver.getKey(); - if (key == null) { - continue; - } - CacheImage cacheImage = imageLoadingByKeys.get(key); - if (cacheImage == null) { - cacheImage = new CacheImage(); - cacheImage.finalFilePath = finalFile; - cacheImage.tempFilePath = tempFile; - cacheImage.key = key; - cacheImage.httpUrl = img.httpUrl; - cacheImage.cacheTask = new CacheOutTask(cacheImage); - if (imageReceiver.getFilter() != null) { - cacheImage.filter = imageReceiver.getFilter(); + imageLoadQueue.postRunnable(new Runnable() { + @Override + public void run() { + CacheImage img = imageLoadingByUrl.get(location); + if (img != null) { + img.setImageAndClear(null); } - imageLoadingByKeys.put(cacheImage.key, cacheImage); - cacheOutQueue.postRunnable(cacheImage.cacheTask); } - cacheImage.addImageView(imageReceiver); - } - } - - private void fileDidFailedLoad(String location) { - CacheImage img = imageLoadingByUrl.get(location); - if (img != null) { - img.setImageAndClear(null); - } + }); } private void runHttpTasks(boolean complete) { @@ -1331,7 +1716,7 @@ public class ImageLoader { } } - public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight) { + public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxHeight, boolean useMaxScale) { BitmapFactory.Options bmOptions = new BitmapFactory.Options(); bmOptions.inJustDecodeBounds = true; FileDescriptor fileDescriptor = null; @@ -1372,7 +1757,7 @@ public class ImageLoader { } float photoW = bmOptions.outWidth; float photoH = bmOptions.outHeight; - float scaleFactor = Math.max(photoW / maxWidth, photoH / maxHeight); + float scaleFactor = useMaxScale ? Math.max(photoW / maxWidth, photoH / maxHeight) : Math.min(photoW / maxWidth, photoH / maxHeight); if (scaleFactor < 1) { scaleFactor = 1; } @@ -1466,12 +1851,7 @@ public class ImageLoader { location.dc_id = Integer.MIN_VALUE; location.local_id = UserConfig.lastLocalId; UserConfig.lastLocalId--; - TLRPC.PhotoSize size; - if (!cache) { - size = new TLRPC.TL_photoSize(); - } else { - size = new TLRPC.TL_photoCachedSize(); - } + TLRPC.PhotoSize size = new TLRPC.TL_photoSize(); size.location = location; size.w = scaledBitmap.getWidth(); size.h = scaledBitmap.getHeight(); @@ -1487,18 +1867,20 @@ public class ImageLoader { size.type = "w"; } - if (!cache) { - String fileName = location.volume_id + "_" + location.local_id + ".jpg"; - final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); - FileOutputStream stream = new FileOutputStream(cacheFile); - scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); - size.size = (int)stream.getChannel().size(); - } else { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); - size.bytes = stream.toByteArray(); + String fileName = location.volume_id + "_" + location.local_id + ".jpg"; + final File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); + FileOutputStream stream = new FileOutputStream(cacheFile); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream); + if (cache) { + ByteArrayOutputStream stream2 = new ByteArrayOutputStream(); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream2); + size.bytes = stream2.toByteArray(); size.size = size.bytes.length; + stream2.close(); + } else { + size.size = (int)stream.getChannel().size(); } + stream.close(); if (scaledBitmap != bitmap) { scaledBitmap.recycle(); } @@ -1557,4 +1939,77 @@ public class ImageLoader { } return ext; } + + public static void saveMessageThumbs(TLRPC.Message message) { + TLRPC.PhotoSize photoSize = null; + if (message.media instanceof TLRPC.TL_messageMediaPhoto) { + for (TLRPC.PhotoSize size : message.media.photo.sizes) { + if (size instanceof TLRPC.TL_photoCachedSize) { + photoSize = size; + break; + } + } + } else if (message.media instanceof TLRPC.TL_messageMediaVideo) { + if (message.media.video.thumb instanceof TLRPC.TL_photoCachedSize) { + photoSize = message.media.video.thumb; + } + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + if (message.media.document.thumb instanceof TLRPC.TL_photoCachedSize) { + photoSize = message.media.document.thumb; + for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) { + if (attribute instanceof TLRPC.TL_documentAttributeSticker) { + photoSize.location.ext = "webp"; + break; + } + } + } + } + if (photoSize != null && photoSize.bytes != null && photoSize.bytes.length != 0) { + if (photoSize.location instanceof TLRPC.TL_fileLocationUnavailable) { + photoSize.location = new TLRPC.TL_fileLocation(); + photoSize.location.volume_id = Integer.MIN_VALUE; + photoSize.location.dc_id = Integer.MIN_VALUE; + photoSize.location.local_id = UserConfig.lastLocalId; + UserConfig.lastLocalId--; + } + File file = FileLoader.getPathToAttach(photoSize, true); + if (!file.exists()) { + try { + RandomAccessFile writeFile = new RandomAccessFile(file, "rws"); + writeFile.write(photoSize.bytes); + writeFile.close(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + TLRPC.TL_photoSize newPhotoSize = new TLRPC.TL_photoSize(); + newPhotoSize.w = photoSize.w; + newPhotoSize.h = photoSize.h; + newPhotoSize.location = photoSize.location; + newPhotoSize.size = photoSize.size; + newPhotoSize.type = photoSize.type; + + if (message.media instanceof TLRPC.TL_messageMediaPhoto) { + for (int a = 0; a < message.media.photo.sizes.size(); a++) { + if (message.media.photo.sizes.get(a) instanceof TLRPC.TL_photoCachedSize) { + message.media.photo.sizes.set(a, newPhotoSize); + break; + } + } + } else if (message.media instanceof TLRPC.TL_messageMediaVideo) { + message.media.video.thumb = newPhotoSize; + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + message.media.document.thumb = newPhotoSize; + } + } + } + + public static void saveMessagesThumbs(ArrayList messages) { + if (messages == null || messages.isEmpty()) { + return; + } + for (TLRPC.Message message : messages) { + saveMessageThumbs(message); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java index 8290a5f8..9c467f72 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/android/ImageReceiver.java @@ -27,33 +27,49 @@ import org.telegram.messenger.TLRPC; import org.telegram.messenger.FileLog; import org.telegram.messenger.Utilities; -public class ImageReceiver { - private TLObject last_path = null; - private String last_httpUrl = null; - private String last_filter = null; - private Drawable last_placeholder = null; - private TLRPC.FileLocation last_placeholderLocation = null; - private int last_size = 0; - private String currentPath = null; - private boolean isPlaceholder = false; - private Drawable currentImage = null; - private Integer tag = null; - private View parentView = null; - private int imageX = 0, imageY = 0, imageW = 0, imageH = 0; +public class ImageReceiver implements NotificationCenter.NotificationCenterDelegate { + + public static interface ImageReceiverDelegate { + public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb); + } + + private View parentView; + private Integer tag; + private Integer thumbTag; + private MessageObject parentMessageObject; + private boolean canceledLoading; + + private TLObject currentImageLocation; + private String currentKey; + private String currentThumbKey; + private String currentHttpUrl; + private String currentFilter; + private String currentThumbFilter; + private TLRPC.FileLocation currentThumbLocation; + private int currentSize; + private boolean currentCacheOnly; + private BitmapDrawable currentImage; + private BitmapDrawable currentThumb; + private Drawable staticThumb; + + private boolean needsQualityThumb; + private boolean shouldGenerateQualityThumb; + + private int imageX, imageY, imageW, imageH; private Rect drawRegion = new Rect(); private boolean isVisible = true; - private boolean isAspectFit = false; - private boolean lastCacheOnly = false; - private boolean forcePreview = false; - private int roundRadius = 0; - private BitmapShader bitmapShader = null; - private Paint roundPaint = null; - private RectF roundRect = null; - private RectF bitmapRect = null; - private Matrix shaderMatrix = null; + private boolean isAspectFit; + private boolean forcePreview; + private int roundRadius; + private BitmapShader bitmapShader; + private Paint roundPaint; + private RectF roundRect; + private RectF bitmapRect; + private Matrix shaderMatrix; private int alpha = 255; private boolean isPressed; private boolean disableRecycle; + private ImageReceiverDelegate delegate; public ImageReceiver() { @@ -63,38 +79,64 @@ public class ImageReceiver { parentView = view; } - public void setImage(TLObject path, String filter, Drawable placeholder, boolean cacheOnly) { - setImage(path, null, filter, placeholder, null, 0, cacheOnly); + public void cancelLoadImage() { + ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); + canceledLoading = true; } - public void setImage(TLObject path, String filter, Drawable placeholder, int size, boolean cacheOnly) { - setImage(path, null, filter, placeholder, null, size, cacheOnly); + public void setImage(TLObject path, String filter, Drawable thumb, boolean cacheOnly) { + setImage(path, null, filter, thumb, null, null, 0, cacheOnly); } - public void setImage(String path, String filter, Drawable placeholder, int size) { - setImage(null, path, filter, placeholder, null, size, true); + public void setImage(TLObject path, String filter, Drawable thumb, int size, boolean cacheOnly) { + setImage(path, null, filter, thumb, null, null, size, cacheOnly); } - public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable placeholder, TLRPC.FileLocation placeholderLocation, int size, boolean cacheOnly) { - if ((fileLocation == null && httpUrl == null) || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation) && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation) && !(fileLocation instanceof TLRPC.TL_document))) { - recycleBitmap(null); - currentPath = null; - isPlaceholder = true; - last_path = null; - last_httpUrl = null; - last_filter = null; - lastCacheOnly = false; - bitmapShader = null; - last_placeholder = placeholder; - last_placeholderLocation = placeholderLocation; - last_size = 0; + public void setImage(String httpUrl, String filter, Drawable thumb, int size) { + setImage(null, httpUrl, filter, thumb, null, null, size, true); + } + + public void setImage(TLObject fileLocation, String filter, TLRPC.FileLocation thumbLocation, String thumbFilter, boolean cacheOnly) { + setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, 0, cacheOnly); + } + + public void setImage(TLObject fileLocation, String filter, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, boolean cacheOnly) { + setImage(fileLocation, null, filter, null, thumbLocation, thumbFilter, size, cacheOnly); + } + + public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawable thumb, TLRPC.FileLocation thumbLocation, String thumbFilter, int size, boolean cacheOnly) { + if ((fileLocation == null && httpUrl == null && thumbLocation == null) + || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation) + && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation) + && !(fileLocation instanceof TLRPC.TL_document))) { + recycleBitmap(null, false); + recycleBitmap(null, true); + currentKey = null; + currentThumbKey = null; + currentThumbFilter = null; + currentImageLocation = null; + currentHttpUrl = null; + currentFilter = null; + currentCacheOnly = false; + staticThumb = thumb; + currentThumbLocation = null; + currentSize = 0; currentImage = null; - ImageLoader.getInstance().cancelLoadingForImageView(this); + bitmapShader = null; + ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); if (parentView != null) { parentView.invalidate(); } + if (delegate != null) { + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); + } return; } + + if (!(thumbLocation instanceof TLRPC.TL_fileLocation)) { + thumbLocation = null; + } + String key = null; if (fileLocation != null) { if (fileLocation instanceof TLRPC.FileLocation) { @@ -104,80 +146,61 @@ public class ImageReceiver { TLRPC.Document location = (TLRPC.Document) fileLocation; key = location.dc_id + "_" + location.id; } - } else { + } else if (httpUrl != null) { key = Utilities.MD5(httpUrl); } - if (filter != null) { - key += "@" + filter; - } - boolean sameFile = false; - BitmapDrawable img = null; - if (currentPath != null) { - if (currentPath.equals(key)) { - sameFile = true; - if (currentImage != null) { - return; - } else { - img = ImageLoader.getInstance().getImageFromMemory(fileLocation, httpUrl, filter, this); - } - } else { - img = ImageLoader.getInstance().getImageFromMemory(fileLocation, httpUrl, filter, this); - recycleBitmap(img); + if (key != null) { + if (filter != null) { + key += "@" + filter; } } - img = ImageLoader.getInstance().getImageFromMemory(fileLocation, httpUrl, filter, this); - currentPath = key; - last_path = fileLocation; - last_httpUrl = httpUrl; - last_filter = filter; - if (!sameFile) { - last_placeholder = placeholder; - last_placeholderLocation = placeholderLocation; - } - last_size = size; - lastCacheOnly = cacheOnly; - bitmapShader = null; - if (img == null) { - isPlaceholder = true; - if (!sameFile && last_placeholderLocation != null && last_placeholder == null) { - last_placeholder = ImageLoader.getInstance().getImageFromMemory(last_placeholderLocation, null, null, null); - if (last_placeholder != null) { - try { - Bitmap bitmap = ((BitmapDrawable) last_placeholder).getBitmap(); - bitmap = bitmap.copy(bitmap.getConfig(), true); - Utilities.blurBitmap(bitmap, 1); - last_placeholder = new BitmapDrawable(bitmap); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } - ImageLoader.getInstance().loadImage(fileLocation, httpUrl, this, size, cacheOnly); - if (parentView != null) { - parentView.invalidate(); - } - } else { - setImageBitmap(img, currentPath); - } - } - public void setImageBitmap(BitmapDrawable bitmap, String imgKey) { - if (currentPath == null || !imgKey.equals(currentPath)) { - return; + if (currentKey != null && key != null && currentKey.equals(key)) { + if (delegate != null) { + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); + } + if (!canceledLoading && !forcePreview) { + return; + } } - isPlaceholder = false; - ImageLoader.getInstance().incrementUseCount(currentPath); - currentImage = bitmap; - if (roundRadius != 0) { - bitmapShader = new BitmapShader(bitmap.getBitmap(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - roundPaint.setShader(bitmapShader); - bitmapRect.set(0, 0, bitmap.getBitmap().getWidth(), bitmap.getBitmap().getHeight()); + + String thumbKey = null; + if (thumbLocation != null) { + thumbKey = thumbLocation.volume_id + "_" + thumbLocation.local_id; + if (thumbFilter != null) { + thumbKey += "@" + thumbFilter; + } } + + recycleBitmap(key, false); + recycleBitmap(thumbKey, true); + + currentThumbKey = thumbKey; + currentKey = key; + currentImageLocation = fileLocation; + currentHttpUrl = httpUrl; + currentFilter = filter; + currentThumbFilter = thumbFilter; + currentSize = size; + currentCacheOnly = cacheOnly; + currentThumbLocation = thumbLocation; + staticThumb = thumb; + bitmapShader = null; + + if (delegate != null) { + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); + } + + ImageLoader.getInstance().loadImageForImageReceiver(this); if (parentView != null) { parentView.invalidate(); } } + public void setDelegate(ImageReceiverDelegate delegate) { + this.delegate = delegate; + } + public void setPressed(boolean value) { isPressed = value; } @@ -195,62 +218,50 @@ public class ImageReceiver { } public void setImageBitmap(Drawable bitmap) { - ImageLoader.getInstance().cancelLoadingForImageView(this); - recycleBitmap(null); - last_placeholder = bitmap; - isPlaceholder = true; - last_placeholderLocation = null; - currentPath = null; + ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); + recycleBitmap(null, false); + recycleBitmap(null, true); + staticThumb = bitmap; + currentThumbLocation = null; + currentKey = null; + currentThumbKey = null; currentImage = null; - last_path = null; - last_httpUrl = null; - last_filter = null; + currentThumbFilter = null; + currentImageLocation = null; + currentHttpUrl = null; + currentFilter = null; + currentSize = 0; + currentCacheOnly = false; bitmapShader = null; - last_size = 0; - lastCacheOnly = false; + if (delegate != null) { + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); + } if (parentView != null) { parentView.invalidate(); } } public void clearImage() { - recycleBitmap(null); - } - - private void recycleBitmap(BitmapDrawable newBitmap) { - if (currentImage == null || isPlaceholder || disableRecycle) { - return; - } - if (currentImage instanceof BitmapDrawable) { - if (currentImage != newBitmap) { - if (currentPath != null) { - Bitmap bitmap = ((BitmapDrawable) currentImage).getBitmap(); - boolean canDelete = ImageLoader.getInstance().decrementUseCount(currentPath); - if (!ImageLoader.getInstance().isInCache(currentPath)) { - if (ImageLoader.getInstance().runtimeHack != null) { - ImageLoader.getInstance().runtimeHack.trackAlloc(bitmap.getRowBytes() * bitmap.getHeight()); - } - if (canDelete) { - currentImage = null; - bitmap.recycle(); - } - } else { - currentImage = null; - } - currentPath = null; - } - } + recycleBitmap(null, false); + recycleBitmap(null, true); + if (needsQualityThumb) { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated); + ImageLoader.getInstance().cancelLoadingForImageReceiver(this, 0); } } public boolean draw(Canvas canvas) { try { - Drawable bitmapDrawable = currentImage; - if (forcePreview || bitmapDrawable == null && last_placeholder != null && last_placeholder instanceof BitmapDrawable) { - bitmapDrawable = last_placeholder; + BitmapDrawable bitmapDrawable = null; + if (!forcePreview && currentImage != null) { + bitmapDrawable = currentImage; + } else if (staticThumb instanceof BitmapDrawable) { + bitmapDrawable = (BitmapDrawable) staticThumb; + } else if (currentThumb != null) { + bitmapDrawable = currentThumb; } if (bitmapDrawable != null) { - Paint paint = ((BitmapDrawable) bitmapDrawable).getPaint(); + Paint paint = bitmapDrawable.getPaint(); boolean hasFilter = paint != null && paint.getColorFilter() != null; if (hasFilter && !isPressed) { bitmapDrawable.setColorFilter(null); @@ -285,11 +296,14 @@ public class ImageReceiver { bitmapDrawable.setAlpha(alpha); bitmapDrawable.draw(canvas); } catch (Exception e) { - if (currentPath != null) { - ImageLoader.getInstance().removeImage(currentPath); - currentPath = null; + if (bitmapDrawable == currentImage && currentKey != null) { + ImageLoader.getInstance().removeImage(currentKey); + currentKey = null; + } else if (bitmapDrawable == currentThumb && currentThumbKey != null) { + ImageLoader.getInstance().removeImage(currentThumbKey); + currentThumbKey = null; } - setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly); + setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly); FileLog.e("tmessages", e); } canvas.restore(); @@ -311,11 +325,14 @@ public class ImageReceiver { bitmapDrawable.setAlpha(alpha); bitmapDrawable.draw(canvas); } catch (Exception e) { - if (currentPath != null) { - ImageLoader.getInstance().removeImage(currentPath); - currentPath = null; + if (bitmapDrawable == currentImage && currentKey != null) { + ImageLoader.getInstance().removeImage(currentKey); + currentKey = null; + } else if (bitmapDrawable == currentThumb && currentThumbKey != null) { + ImageLoader.getInstance().removeImage(currentThumbKey); + currentThumbKey = null; } - setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly); + setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly); FileLog.e("tmessages", e); } } @@ -329,11 +346,14 @@ public class ImageReceiver { bitmapDrawable.setAlpha(alpha); bitmapDrawable.draw(canvas); } catch (Exception e) { - if (currentPath != null) { - ImageLoader.getInstance().removeImage(currentPath); - currentPath = null; + if (bitmapDrawable == currentImage && currentKey != null) { + ImageLoader.getInstance().removeImage(currentKey); + currentKey = null; + } else if (bitmapDrawable == currentThumb && currentThumbKey != null) { + ImageLoader.getInstance().removeImage(currentThumbKey); + currentThumbKey = null; } - setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly); + setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentCacheOnly); FileLog.e("tmessages", e); } } @@ -341,19 +361,14 @@ public class ImageReceiver { } } return true; - } else if (last_placeholder != null) { + } else if (staticThumb != null) { drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH); - last_placeholder.setBounds(drawRegion); + staticThumb.setBounds(drawRegion); if (isVisible) { try { - last_placeholder.setAlpha(alpha); - last_placeholder.draw(canvas); + staticThumb.setAlpha(alpha); + staticThumb.draw(canvas); } catch (Exception e) { - if (currentPath != null) { - ImageLoader.getInstance().removeImage(currentPath); - currentPath = null; - } - setImage(last_path, last_httpUrl, last_filter, last_placeholder, last_placeholderLocation, last_size, lastCacheOnly); FileLog.e("tmessages", e); } } @@ -366,10 +381,12 @@ public class ImageReceiver { } public Bitmap getBitmap() { - if (currentImage != null && currentImage instanceof BitmapDrawable) { - return ((BitmapDrawable)currentImage).getBitmap(); - } else if (isPlaceholder && last_placeholder != null && last_placeholder instanceof BitmapDrawable) { - return ((BitmapDrawable)last_placeholder).getBitmap(); + if (currentImage != null) { + return currentImage.getBitmap(); + } else if (currentThumb != null) { + return currentThumb.getBitmap(); + } else if (staticThumb instanceof BitmapDrawable) { + return ((BitmapDrawable) staticThumb).getBitmap(); } return null; } @@ -393,7 +410,7 @@ public class ImageReceiver { } public boolean hasImage() { - return currentImage != null || last_placeholder != null || currentPath != null || last_httpUrl != null; + return currentImage != null || currentThumb != null || currentKey != null || currentHttpUrl != null || staticThumb != null; } public void setAspectFit(boolean value) { @@ -404,14 +421,6 @@ public class ImageReceiver { parentView = view; } - protected Integer getTag() { - return tag; - } - - protected void setTag(Integer tag) { - this.tag = tag; - } - public void setImageCoords(int x, int y, int width, int height) { imageX = x; imageY = y; @@ -444,17 +453,49 @@ public class ImageReceiver { } public String getFilter() { - return last_filter; + return currentFilter; + } + + public String getThumbFilter() { + return currentThumbFilter; } public String getKey() { - return currentPath; + return currentKey; + } + + public String getThumbKey() { + return currentThumbKey; + } + + public int getSize() { + return currentSize; + } + + public TLObject getImageLocation() { + return currentImageLocation; + } + + public TLRPC.FileLocation getThumbLocation() { + return currentThumbLocation; + } + + public String getHttpImageLocation() { + return currentHttpUrl; + } + + public boolean getCacheOnly() { + return currentCacheOnly; } public void setForcePreview(boolean value) { forcePreview = value; } + public boolean isForcePreview() { + return forcePreview; + } + public void setRoundRadius(int value) { roundRadius = value; if (roundRadius != 0) { @@ -475,4 +516,146 @@ public class ImageReceiver { public int getRoundRadius() { return roundRadius; } + + public void setParentMessageObject(MessageObject messageObject) { + parentMessageObject = messageObject; + } + + public MessageObject getParentMessageObject() { + return parentMessageObject; + } + + public void setNeedsQualityThumb(boolean value) { + needsQualityThumb = value; + if (needsQualityThumb) { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageThumbGenerated); + } else { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageThumbGenerated); + } + } + + public boolean isNeedsQualityThumb() { + return needsQualityThumb; + } + + public void setShouldGenerateQualityThumb(boolean value) { + shouldGenerateQualityThumb = value; + } + + public boolean isShouldGenerateQualityThumb() { + return shouldGenerateQualityThumb; + } + + protected Integer getTag(boolean thumb) { + if (thumb) { + return thumbTag; + } else { + return tag; + } + } + + protected void setTag(Integer value, boolean thumb) { + if (thumb) { + thumbTag = value; + } else { + tag = value; + } + } + + protected void setImageBitmapByKey(BitmapDrawable bitmap, String key, boolean thumb) { + if (bitmap == null || key == null) { + return; + } + if (!thumb) { + if (currentKey == null || !key.equals(currentKey)) { + return; + } + ImageLoader.getInstance().incrementUseCount(currentKey); + currentImage = bitmap; + if (roundRadius != 0 && bitmap instanceof BitmapDrawable) { + Bitmap object = bitmap.getBitmap(); + bitmapShader = new BitmapShader(object, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + roundPaint.setShader(bitmapShader); + bitmapRect.set(0, 0, object.getWidth(), object.getHeight()); + } + if (parentView != null) { + parentView.invalidate(); + } + } else if (currentThumb == null && (currentImage == null || forcePreview)) { + if (currentThumbKey == null || !key.equals(currentThumbKey)) { + return; + } + ImageLoader.getInstance().incrementUseCount(currentThumbKey); + currentThumb = bitmap; + if (!(staticThumb instanceof BitmapDrawable) && parentView != null) { + parentView.invalidate(); + } + } + + if (delegate != null) { + delegate.didSetImage(this, currentImage != null || currentThumb != null || staticThumb != null, currentImage == null); + } + } + + private void recycleBitmap(String newKey, boolean thumb) { + String key; + BitmapDrawable image; + if (thumb) { + if (currentThumb == null) { + return; + } + key = currentThumbKey; + image = currentThumb; + } else { + if (currentImage == null) { + return; + } + key = currentKey; + image = currentImage; + } + BitmapDrawable newBitmap = null; + if (newKey != null) { + newBitmap = ImageLoader.getInstance().getImageFromMemory(newKey); + } + if (key == null || image == null || image == newBitmap || disableRecycle) { + return; + } + Bitmap bitmap = image.getBitmap(); + boolean canDelete = ImageLoader.getInstance().decrementUseCount(key); + if (!ImageLoader.getInstance().isInCache(key)) { + if (ImageLoader.getInstance().runtimeHack != null) { + ImageLoader.getInstance().runtimeHack.trackAlloc(bitmap.getRowBytes() * bitmap.getHeight()); + } + if (canDelete) { + bitmap.recycle(); + ImageLoader.getInstance().callGC(); + } + } + if (thumb) { + currentThumb = null; + currentThumbKey = null; + } else { + currentImage = null; + currentKey = null; + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.messageThumbGenerated) { + String key = (String) args[1]; + if (currentThumbKey != null && currentThumbKey.equals(key)) { + if (currentThumb == null) { + ImageLoader.getInstance().incrementUseCount(currentThumbKey); + } + currentThumb = (BitmapDrawable) args[0]; + if (staticThumb instanceof BitmapDrawable) { + staticThumb = null; + } + if (parentView != null) { + parentView.invalidate(); + } + } + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java index 0aae84ab..db7b228b 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/android/LocaleController.java @@ -58,17 +58,19 @@ public class LocaleController { public static FastDateFormat chatDate; public static FastDateFormat chatFullDate; - private HashMap allRules = new HashMap(); + private HashMap allRules = new HashMap<>(); private Locale currentLocale; private Locale systemDefaultLocale; private PluralRules currentPluralRules; private LocaleInfo currentLocaleInfo; private LocaleInfo defaultLocalInfo; - private HashMap localeValues = new HashMap(); + private HashMap localeValues = new HashMap<>(); private String languageOverride; private boolean changingConfiguration = false; + private HashMap translitChars; + private class TimeZoneChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -110,10 +112,10 @@ public class LocaleController { } } - public ArrayList sortedLanguages = new ArrayList(); - public HashMap languagesDict = new HashMap(); + public ArrayList sortedLanguages = new ArrayList<>(); + public HashMap languagesDict = new HashMap<>(); - private ArrayList otherLanguages = new ArrayList(); + private ArrayList otherLanguages = new ArrayList<>(); private static volatile LocaleController Instance = null; public static LocaleController getInstance() { @@ -442,7 +444,7 @@ public class LocaleController { private HashMap getLocaleFileStrings(File file) { try { - HashMap stringMap = new HashMap(); + HashMap stringMap = new HashMap<>(); XmlPullParser parser = Xml.newPullParser(); parser.setInput(new FileInputStream(file), "UTF-8"); int eventType = parser.getEventType(); @@ -818,6 +820,542 @@ public class LocaleController { } } + public String getTranslitString(String src) { + if (translitChars == null) { + translitChars = new HashMap<>(520); + translitChars.put("ȼ", "c"); + translitChars.put("ᶇ", "n"); + translitChars.put("ɖ", "d"); + translitChars.put("ỿ", "y"); + translitChars.put("ᴓ", "o"); + translitChars.put("ø", "o"); + translitChars.put("ḁ", "a"); + translitChars.put("ʯ", "h"); + translitChars.put("ŷ", "y"); + translitChars.put("ʞ", "k"); + translitChars.put("ừ", "u"); + translitChars.put("ꜳ", "aa"); + translitChars.put("ij", "ij"); + translitChars.put("ḽ", "l"); + translitChars.put("ɪ", "i"); + translitChars.put("ḇ", "b"); + translitChars.put("ʀ", "r"); + translitChars.put("ě", "e"); + translitChars.put("ffi", "ffi"); + translitChars.put("ơ", "o"); + translitChars.put("ⱹ", "r"); + translitChars.put("ồ", "o"); + translitChars.put("ǐ", "i"); + translitChars.put("ꝕ", "p"); + translitChars.put("ý", "y"); + translitChars.put("ḝ", "e"); + translitChars.put("ₒ", "o"); + translitChars.put("ⱥ", "a"); + translitChars.put("ʙ", "b"); + translitChars.put("ḛ", "e"); + translitChars.put("ƈ", "c"); + translitChars.put("ɦ", "h"); + translitChars.put("ᵬ", "b"); + translitChars.put("ṣ", "s"); + translitChars.put("đ", "d"); + translitChars.put("ỗ", "o"); + translitChars.put("ɟ", "j"); + translitChars.put("ẚ", "a"); + translitChars.put("ɏ", "y"); + translitChars.put("л", "l"); + translitChars.put("ʌ", "v"); + translitChars.put("ꝓ", "p"); + translitChars.put("fi", "fi"); + translitChars.put("ᶄ", "k"); + translitChars.put("ḏ", "d"); + translitChars.put("ᴌ", "l"); + translitChars.put("ė", "e"); + translitChars.put("ё", "yo"); + translitChars.put("ᴋ", "k"); + translitChars.put("ċ", "c"); + translitChars.put("ʁ", "r"); + translitChars.put("ƕ", "hv"); + translitChars.put("ƀ", "b"); + translitChars.put("ṍ", "o"); + translitChars.put("ȣ", "ou"); + translitChars.put("ǰ", "j"); + translitChars.put("ᶃ", "g"); + translitChars.put("ṋ", "n"); + translitChars.put("ɉ", "j"); + translitChars.put("ǧ", "g"); + translitChars.put("dz", "dz"); + translitChars.put("ź", "z"); + translitChars.put("ꜷ", "au"); + translitChars.put("ǖ", "u"); + translitChars.put("ᵹ", "g"); + translitChars.put("ȯ", "o"); + translitChars.put("ɐ", "a"); + translitChars.put("ą", "a"); + translitChars.put("õ", "o"); + translitChars.put("ɻ", "r"); + translitChars.put("ꝍ", "o"); + translitChars.put("ǟ", "a"); + translitChars.put("ȴ", "l"); + translitChars.put("ʂ", "s"); + translitChars.put("fl", "fl"); + translitChars.put("ȉ", "i"); + translitChars.put("ⱻ", "e"); + translitChars.put("ṉ", "n"); + translitChars.put("ï", "i"); + translitChars.put("ñ", "n"); + translitChars.put("ᴉ", "i"); + translitChars.put("ʇ", "t"); + translitChars.put("ẓ", "z"); + translitChars.put("ỷ", "y"); + translitChars.put("ȳ", "y"); + translitChars.put("ṩ", "s"); + translitChars.put("ɽ", "r"); + translitChars.put("ĝ", "g"); + translitChars.put("в", "v"); + translitChars.put("ᴝ", "u"); + translitChars.put("ḳ", "k"); + translitChars.put("ꝫ", "et"); + translitChars.put("ī", "i"); + translitChars.put("ť", "t"); + translitChars.put("ꜿ", "c"); + translitChars.put("ʟ", "l"); + translitChars.put("ꜹ", "av"); + translitChars.put("û", "u"); + translitChars.put("æ", "ae"); + translitChars.put("и", "i"); + translitChars.put("ă", "a"); + translitChars.put("ǘ", "u"); + translitChars.put("ꞅ", "s"); + translitChars.put("ᵣ", "r"); + translitChars.put("ᴀ", "a"); + translitChars.put("ƃ", "b"); + translitChars.put("ḩ", "h"); + translitChars.put("ṧ", "s"); + translitChars.put("ₑ", "e"); + translitChars.put("ʜ", "h"); + translitChars.put("ẋ", "x"); + translitChars.put("ꝅ", "k"); + translitChars.put("ḋ", "d"); + translitChars.put("ƣ", "oi"); + translitChars.put("ꝑ", "p"); + translitChars.put("ħ", "h"); + translitChars.put("ⱴ", "v"); + translitChars.put("ẇ", "w"); + translitChars.put("ǹ", "n"); + translitChars.put("ɯ", "m"); + translitChars.put("ɡ", "g"); + translitChars.put("ɴ", "n"); + translitChars.put("ᴘ", "p"); + translitChars.put("ᵥ", "v"); + translitChars.put("ū", "u"); + translitChars.put("ḃ", "b"); + translitChars.put("ṗ", "p"); + translitChars.put("ь", ""); + translitChars.put("å", "a"); + translitChars.put("ɕ", "c"); + translitChars.put("ọ", "o"); + translitChars.put("ắ", "a"); + translitChars.put("ƒ", "f"); + translitChars.put("ǣ", "ae"); + translitChars.put("ꝡ", "vy"); + translitChars.put("ff", "ff"); + translitChars.put("ᶉ", "r"); + translitChars.put("ô", "o"); + translitChars.put("ǿ", "o"); + translitChars.put("ṳ", "u"); + translitChars.put("ȥ", "z"); + translitChars.put("ḟ", "f"); + translitChars.put("ḓ", "d"); + translitChars.put("ȇ", "e"); + translitChars.put("ȕ", "u"); + translitChars.put("п", "p"); + translitChars.put("ȵ", "n"); + translitChars.put("ʠ", "q"); + translitChars.put("ấ", "a"); + translitChars.put("ǩ", "k"); + translitChars.put("ĩ", "i"); + translitChars.put("ṵ", "u"); + translitChars.put("ŧ", "t"); + translitChars.put("ɾ", "r"); + translitChars.put("ƙ", "k"); + translitChars.put("ṫ", "t"); + translitChars.put("ꝗ", "q"); + translitChars.put("ậ", "a"); + translitChars.put("н", "n"); + translitChars.put("ʄ", "j"); + translitChars.put("ƚ", "l"); + translitChars.put("ᶂ", "f"); + translitChars.put("д", "d"); + translitChars.put("ᵴ", "s"); + translitChars.put("ꞃ", "r"); + translitChars.put("ᶌ", "v"); + translitChars.put("ɵ", "o"); + translitChars.put("ḉ", "c"); + translitChars.put("ᵤ", "u"); + translitChars.put("ẑ", "z"); + translitChars.put("ṹ", "u"); + translitChars.put("ň", "n"); + translitChars.put("ʍ", "w"); + translitChars.put("ầ", "a"); + translitChars.put("lj", "lj"); + translitChars.put("ɓ", "b"); + translitChars.put("ɼ", "r"); + translitChars.put("ò", "o"); + translitChars.put("ẘ", "w"); + translitChars.put("ɗ", "d"); + translitChars.put("ꜽ", "ay"); + translitChars.put("ư", "u"); + translitChars.put("ᶀ", "b"); + translitChars.put("ǜ", "u"); + translitChars.put("ẹ", "e"); + translitChars.put("ǡ", "a"); + translitChars.put("ɥ", "h"); + translitChars.put("ṏ", "o"); + translitChars.put("ǔ", "u"); + translitChars.put("ʎ", "y"); + translitChars.put("ȱ", "o"); + translitChars.put("ệ", "e"); + translitChars.put("ế", "e"); + translitChars.put("ĭ", "i"); + translitChars.put("ⱸ", "e"); + translitChars.put("ṯ", "t"); + translitChars.put("ᶑ", "d"); + translitChars.put("ḧ", "h"); + translitChars.put("ṥ", "s"); + translitChars.put("ë", "e"); + translitChars.put("ᴍ", "m"); + translitChars.put("ö", "o"); + translitChars.put("é", "e"); + translitChars.put("ı", "i"); + translitChars.put("ď", "d"); + translitChars.put("ᵯ", "m"); + translitChars.put("ỵ", "y"); + translitChars.put("я", "ya"); + translitChars.put("ŵ", "w"); + translitChars.put("ề", "e"); + translitChars.put("ứ", "u"); + translitChars.put("ƶ", "z"); + translitChars.put("ĵ", "j"); + translitChars.put("ḍ", "d"); + translitChars.put("ŭ", "u"); + translitChars.put("ʝ", "j"); + translitChars.put("ж", "zh"); + translitChars.put("ê", "e"); + translitChars.put("ǚ", "u"); + translitChars.put("ġ", "g"); + translitChars.put("ṙ", "r"); + translitChars.put("ƞ", "n"); + translitChars.put("ъ", ""); + translitChars.put("ḗ", "e"); + translitChars.put("ẝ", "s"); + translitChars.put("ᶁ", "d"); + translitChars.put("ķ", "k"); + translitChars.put("ᴂ", "ae"); + translitChars.put("ɘ", "e"); + translitChars.put("ợ", "o"); + translitChars.put("ḿ", "m"); + translitChars.put("ꜰ", "f"); + translitChars.put("а", "a"); + translitChars.put("ẵ", "a"); + translitChars.put("ꝏ", "oo"); + translitChars.put("ᶆ", "m"); + translitChars.put("ᵽ", "p"); + translitChars.put("ц", "ts"); + translitChars.put("ữ", "u"); + translitChars.put("ⱪ", "k"); + translitChars.put("ḥ", "h"); + translitChars.put("ţ", "t"); + translitChars.put("ᵱ", "p"); + translitChars.put("ṁ", "m"); + translitChars.put("á", "a"); + translitChars.put("ᴎ", "n"); + translitChars.put("ꝟ", "v"); + translitChars.put("è", "e"); + translitChars.put("ᶎ", "z"); + translitChars.put("ꝺ", "d"); + translitChars.put("ᶈ", "p"); + translitChars.put("м", "m"); + translitChars.put("ɫ", "l"); + translitChars.put("ᴢ", "z"); + translitChars.put("ɱ", "m"); + translitChars.put("ṝ", "r"); + translitChars.put("ṽ", "v"); + translitChars.put("ũ", "u"); + translitChars.put("ß", "ss"); + translitChars.put("т", "t"); + translitChars.put("ĥ", "h"); + translitChars.put("ᵵ", "t"); + translitChars.put("ʐ", "z"); + translitChars.put("ṟ", "r"); + translitChars.put("ɲ", "n"); + translitChars.put("à", "a"); + translitChars.put("ẙ", "y"); + translitChars.put("ỳ", "y"); + translitChars.put("ᴔ", "oe"); + translitChars.put("ы", "i"); + translitChars.put("ₓ", "x"); + translitChars.put("ȗ", "u"); + translitChars.put("ⱼ", "j"); + translitChars.put("ẫ", "a"); + translitChars.put("ʑ", "z"); + translitChars.put("ẛ", "s"); + translitChars.put("ḭ", "i"); + translitChars.put("ꜵ", "ao"); + translitChars.put("ɀ", "z"); + translitChars.put("ÿ", "y"); + translitChars.put("ǝ", "e"); + translitChars.put("ǭ", "o"); + translitChars.put("ᴅ", "d"); + translitChars.put("ᶅ", "l"); + translitChars.put("ù", "u"); + translitChars.put("ạ", "a"); + translitChars.put("ḅ", "b"); + translitChars.put("ụ", "u"); + translitChars.put("к", "k"); + translitChars.put("ằ", "a"); + translitChars.put("ᴛ", "t"); + translitChars.put("ƴ", "y"); + translitChars.put("ⱦ", "t"); + translitChars.put("з", "z"); + translitChars.put("ⱡ", "l"); + translitChars.put("ȷ", "j"); + translitChars.put("ᵶ", "z"); + translitChars.put("ḫ", "h"); + translitChars.put("ⱳ", "w"); + translitChars.put("ḵ", "k"); + translitChars.put("ờ", "o"); + translitChars.put("î", "i"); + translitChars.put("ģ", "g"); + translitChars.put("ȅ", "e"); + translitChars.put("ȧ", "a"); + translitChars.put("ẳ", "a"); + translitChars.put("щ", "sch"); + translitChars.put("ɋ", "q"); + translitChars.put("ṭ", "t"); + translitChars.put("ꝸ", "um"); + translitChars.put("ᴄ", "c"); + translitChars.put("ẍ", "x"); + translitChars.put("ủ", "u"); + translitChars.put("ỉ", "i"); + translitChars.put("ᴚ", "r"); + translitChars.put("ś", "s"); + translitChars.put("ꝋ", "o"); + translitChars.put("ỹ", "y"); + translitChars.put("ṡ", "s"); + translitChars.put("nj", "nj"); + translitChars.put("ȁ", "a"); + translitChars.put("ẗ", "t"); + translitChars.put("ĺ", "l"); + translitChars.put("ž", "z"); + translitChars.put("ᵺ", "th"); + translitChars.put("ƌ", "d"); + translitChars.put("ș", "s"); + translitChars.put("š", "s"); + translitChars.put("ᶙ", "u"); + translitChars.put("ẽ", "e"); + translitChars.put("ẜ", "s"); + translitChars.put("ɇ", "e"); + translitChars.put("ṷ", "u"); + translitChars.put("ố", "o"); + translitChars.put("ȿ", "s"); + translitChars.put("ᴠ", "v"); + translitChars.put("ꝭ", "is"); + translitChars.put("ᴏ", "o"); + translitChars.put("ɛ", "e"); + translitChars.put("ǻ", "a"); + translitChars.put("ffl", "ffl"); + translitChars.put("ⱺ", "o"); + translitChars.put("ȋ", "i"); + translitChars.put("ᵫ", "ue"); + translitChars.put("ȡ", "d"); + translitChars.put("ⱬ", "z"); + translitChars.put("ẁ", "w"); + translitChars.put("ᶏ", "a"); + translitChars.put("ꞇ", "t"); + translitChars.put("ğ", "g"); + translitChars.put("ɳ", "n"); + translitChars.put("ʛ", "g"); + translitChars.put("ᴜ", "u"); + translitChars.put("ф", "f"); + translitChars.put("ẩ", "a"); + translitChars.put("ṅ", "n"); + translitChars.put("ɨ", "i"); + translitChars.put("ᴙ", "r"); + translitChars.put("ǎ", "a"); + translitChars.put("ſ", "s"); + translitChars.put("у", "u"); + translitChars.put("ȫ", "o"); + translitChars.put("ɿ", "r"); + translitChars.put("ƭ", "t"); + translitChars.put("ḯ", "i"); + translitChars.put("ǽ", "ae"); + translitChars.put("ⱱ", "v"); + translitChars.put("ɶ", "oe"); + translitChars.put("ṃ", "m"); + translitChars.put("ż", "z"); + translitChars.put("ĕ", "e"); + translitChars.put("ꜻ", "av"); + translitChars.put("ở", "o"); + translitChars.put("ễ", "e"); + translitChars.put("ɬ", "l"); + translitChars.put("ị", "i"); + translitChars.put("ᵭ", "d"); + translitChars.put("st", "st"); + translitChars.put("ḷ", "l"); + translitChars.put("ŕ", "r"); + translitChars.put("ᴕ", "ou"); + translitChars.put("ʈ", "t"); + translitChars.put("ā", "a"); + translitChars.put("э", "e"); + translitChars.put("ḙ", "e"); + translitChars.put("ᴑ", "o"); + translitChars.put("ç", "c"); + translitChars.put("ᶊ", "s"); + translitChars.put("ặ", "a"); + translitChars.put("ų", "u"); + translitChars.put("ả", "a"); + translitChars.put("ǥ", "g"); + translitChars.put("р", "r"); + translitChars.put("ꝁ", "k"); + translitChars.put("ẕ", "z"); + translitChars.put("ŝ", "s"); + translitChars.put("ḕ", "e"); + translitChars.put("ɠ", "g"); + translitChars.put("ꝉ", "l"); + translitChars.put("ꝼ", "f"); + translitChars.put("ᶍ", "x"); + translitChars.put("х", "h"); + translitChars.put("ǒ", "o"); + translitChars.put("ę", "e"); + translitChars.put("ổ", "o"); + translitChars.put("ƫ", "t"); + translitChars.put("ǫ", "o"); + translitChars.put("i̇", "i"); + translitChars.put("ṇ", "n"); + translitChars.put("ć", "c"); + translitChars.put("ᵷ", "g"); + translitChars.put("ẅ", "w"); + translitChars.put("ḑ", "d"); + translitChars.put("ḹ", "l"); + translitChars.put("ч", "ch"); + translitChars.put("œ", "oe"); + translitChars.put("ᵳ", "r"); + translitChars.put("ļ", "l"); + translitChars.put("ȑ", "r"); + translitChars.put("ȭ", "o"); + translitChars.put("ᵰ", "n"); + translitChars.put("ᴁ", "ae"); + translitChars.put("ŀ", "l"); + translitChars.put("ä", "a"); + translitChars.put("ƥ", "p"); + translitChars.put("ỏ", "o"); + translitChars.put("į", "i"); + translitChars.put("ȓ", "r"); + translitChars.put("dž", "dz"); + translitChars.put("ḡ", "g"); + translitChars.put("ṻ", "u"); + translitChars.put("ō", "o"); + translitChars.put("ľ", "l"); + translitChars.put("ẃ", "w"); + translitChars.put("ț", "t"); + translitChars.put("ń", "n"); + translitChars.put("ɍ", "r"); + translitChars.put("ȃ", "a"); + translitChars.put("ü", "u"); + translitChars.put("ꞁ", "l"); + translitChars.put("ᴐ", "o"); + translitChars.put("ớ", "o"); + translitChars.put("ᴃ", "b"); + translitChars.put("ɹ", "r"); + translitChars.put("ᵲ", "r"); + translitChars.put("ʏ", "y"); + translitChars.put("ᵮ", "f"); + translitChars.put("ⱨ", "h"); + translitChars.put("ŏ", "o"); + translitChars.put("ú", "u"); + translitChars.put("ṛ", "r"); + translitChars.put("ʮ", "h"); + translitChars.put("ó", "o"); + translitChars.put("ů", "u"); + translitChars.put("ỡ", "o"); + translitChars.put("ṕ", "p"); + translitChars.put("ᶖ", "i"); + translitChars.put("ự", "u"); + translitChars.put("ã", "a"); + translitChars.put("ᵢ", "i"); + translitChars.put("ṱ", "t"); + translitChars.put("ể", "e"); + translitChars.put("ử", "u"); + translitChars.put("í", "i"); + translitChars.put("ɔ", "o"); + translitChars.put("с", "s"); + translitChars.put("й", "i"); + translitChars.put("ɺ", "r"); + translitChars.put("ɢ", "g"); + translitChars.put("ř", "r"); + translitChars.put("ẖ", "h"); + translitChars.put("ű", "u"); + translitChars.put("ȍ", "o"); + translitChars.put("ш", "sh"); + translitChars.put("ḻ", "l"); + translitChars.put("ḣ", "h"); + translitChars.put("ȶ", "t"); + translitChars.put("ņ", "n"); + translitChars.put("ᶒ", "e"); + translitChars.put("ì", "i"); + translitChars.put("ẉ", "w"); + translitChars.put("б", "b"); + translitChars.put("ē", "e"); + translitChars.put("ᴇ", "e"); + translitChars.put("ł", "l"); + translitChars.put("ộ", "o"); + translitChars.put("ɭ", "l"); + translitChars.put("ẏ", "y"); + translitChars.put("ᴊ", "j"); + translitChars.put("ḱ", "k"); + translitChars.put("ṿ", "v"); + translitChars.put("ȩ", "e"); + translitChars.put("â", "a"); + translitChars.put("ş", "s"); + translitChars.put("ŗ", "r"); + translitChars.put("ʋ", "v"); + translitChars.put("ₐ", "a"); + translitChars.put("ↄ", "c"); + translitChars.put("ᶓ", "e"); + translitChars.put("ɰ", "m"); + translitChars.put("е", "e"); + translitChars.put("ᴡ", "w"); + translitChars.put("ȏ", "o"); + translitChars.put("č", "c"); + translitChars.put("ǵ", "g"); + translitChars.put("ĉ", "c"); + translitChars.put("ю", "yu"); + translitChars.put("ᶗ", "o"); + translitChars.put("ꝃ", "k"); + translitChars.put("ꝙ", "q"); + translitChars.put("г", "g"); + translitChars.put("ṑ", "o"); + translitChars.put("ꜱ", "s"); + translitChars.put("ṓ", "o"); + translitChars.put("ȟ", "h"); + translitChars.put("ő", "o"); + translitChars.put("ꜩ", "tz"); + translitChars.put("ẻ", "e"); + translitChars.put("о", "o"); + } + StringBuilder dst = new StringBuilder(src.length()); + int len = src.length(); + for (int a = 0; a < len; a++) { + String ch = src.substring(a, a + 1); + String tch = translitChars.get(ch); + if (tch != null) { + dst.append(tch); + } else { + dst.append(ch); + } + } + return dst.toString(); + } abstract public static class PluralRules { abstract int quantityForNumber(int n); diff --git a/TMessagesProj/src/main/java/org/telegram/android/LruCache.java b/TMessagesProj/src/main/java/org/telegram/android/LruCache.java index 7455bbce..3ed7d3c6 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/LruCache.java +++ b/TMessagesProj/src/main/java/org/telegram/android/LruCache.java @@ -14,7 +14,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.Map; /** * Static library version of {@link android.util.LruCache}. Used to write apps @@ -41,8 +40,8 @@ public class LruCache { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; - this.map = new LinkedHashMap(0, 0.75f, true); - this.mapFilters = new LinkedHashMap>(); + this.map = new LinkedHashMap<>(0, 0.75f, true); + this.mapFilters = new LinkedHashMap<>(); } /** @@ -69,7 +68,7 @@ public class LruCache { public ArrayList getFilterKeys(String key) { ArrayList arr = mapFilters.get(key); if (arr != null) { - return new ArrayList(arr); + return new ArrayList<>(arr); } return null; } @@ -98,14 +97,17 @@ public class LruCache { if (args.length > 1) { ArrayList arr = mapFilters.get(args[0]); if (arr == null) { - arr = new ArrayList(); + arr = new ArrayList<>(); mapFilters.put(args[0], arr); } - arr.add(args[1]); + if (!arr.contains(args[1])) { + arr.add(args[1]); + } } if (previous != null) { entryRemoved(false, key, previous, value); + ImageLoader.getInstance().callGC(); } trimToSize(maxSize, key); @@ -137,15 +139,16 @@ public class LruCache { if (args.length > 1) { ArrayList arr = mapFilters.get(args[0]); if (arr != null) { - arr.remove(key); + arr.remove(args[1]); if (arr.isEmpty()) { - mapFilters.remove(args[1]); + mapFilters.remove(args[0]); } } } entryRemoved(true, key, value, null); } + ImageLoader.getInstance().callGC(); } } @@ -172,14 +175,15 @@ public class LruCache { if (args.length > 1) { ArrayList arr = mapFilters.get(args[0]); if (arr != null) { - arr.remove(key); + arr.remove(args[1]); if (arr.isEmpty()) { - mapFilters.remove(args[1]); + mapFilters.remove(args[0]); } } } entryRemoved(false, key, previous, null); + ImageLoader.getInstance().callGC(); } return previous; diff --git a/TMessagesProj/src/main/java/org/telegram/android/MediaController.java b/TMessagesProj/src/main/java/org/telegram/android/MediaController.java index 2dc688aa..efa53b09 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/android/MediaController.java @@ -122,6 +122,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel public String bucketName; public PhotoEntry coverPhoto; public ArrayList photos = new ArrayList<>(); + public HashMap photosByIds = new HashMap<>(); public AlbumEntry(int bucketId, String bucketName, PhotoEntry coverPhoto) { this.bucketId = bucketId; @@ -131,6 +132,7 @@ public class MediaController implements NotificationCenter.NotificationCenterDel public void addPhoto(PhotoEntry photoEntry) { photos.add(photoEntry); + photosByIds.put(photoEntry.imageId, photoEntry); } } @@ -140,6 +142,8 @@ public class MediaController implements NotificationCenter.NotificationCenterDel public long dateTaken; public String path; public int orientation; + public String thumbPath; + public String imagePath; public PhotoEntry(int bucketId, int imageId, long dateTaken, String path, int orientation) { this.bucketId = bucketId; @@ -1095,11 +1099,11 @@ public class MediaController implements NotificationCenter.NotificationCenterDel @Override public void onSensorChanged(SensorEvent event) { - if (audioTrackPlayer == null && audioPlayer == null || isPaused || (useFrontSpeaker == (event.values[0] == 0))) { + if (proximitySensor != null && audioTrackPlayer == null && audioPlayer == null || isPaused || (useFrontSpeaker == (event.values[0] < proximitySensor.getMaximumRange() / 10))) { return; } ignoreProximity = true; - useFrontSpeaker = event.values[0] == 0; + useFrontSpeaker = event.values[0] < proximitySensor.getMaximumRange() / 10; NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioRouteChanged, useFrontSpeaker); MessageObject currentMessageObject = playingMessageObject; float progress = playingMessageObject.audioProgress; @@ -2450,11 +2454,15 @@ public class MediaController implements NotificationCenter.NotificationCenterDel decoder.start(); final int TIMEOUT_USEC = 2500; - ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers(); - ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); + ByteBuffer[] decoderInputBuffers = null; + ByteBuffer[] encoderOutputBuffers = null; ByteBuffer[] encoderInputBuffers = null; - if (Build.VERSION.SDK_INT < 18) { - encoderInputBuffers = encoder.getInputBuffers(); + if (Build.VERSION.SDK_INT < 21) { + decoderInputBuffers = decoder.getInputBuffers(); + encoderOutputBuffers = encoder.getOutputBuffers(); + if (Build.VERSION.SDK_INT < 18) { + encoderInputBuffers = encoder.getInputBuffers(); + } } checkConversionCanceled(); @@ -2467,7 +2475,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel if (index == videoIndex) { int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC); if (inputBufIndex >= 0) { - ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex]; + ByteBuffer inputBuf = null; + if (Build.VERSION.SDK_INT < 21) { + inputBuf = decoderInputBuffers[inputBufIndex]; + } else { + inputBuf = decoder.getInputBuffer(inputBufIndex); + } int chunkSize = extractor.readSampleData(inputBuf, 0); if (chunkSize < 0) { decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); @@ -2497,7 +2510,9 @@ public class MediaController implements NotificationCenter.NotificationCenterDel if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { encoderOutputAvailable = false; } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { - encoderOutputBuffers = encoder.getOutputBuffers(); + if (Build.VERSION.SDK_INT < 21) { + encoderOutputBuffers = encoder.getOutputBuffers(); + } } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat newFormat = encoder.getOutputFormat(); if (videoTrackIndex == -5) { @@ -2506,7 +2521,12 @@ public class MediaController implements NotificationCenter.NotificationCenterDel } else if (encoderStatus < 0) { throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); } else { - ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; + ByteBuffer encodedData = null; + if (Build.VERSION.SDK_INT < 21) { + encodedData = encoderOutputBuffers[encoderStatus]; + } else { + encodedData = encoder.getOutputBuffer(encoderStatus); + } if (encodedData == null) { throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); } diff --git a/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java index 3274ccf3..1c6c0dd4 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/android/MessageObject.java @@ -8,7 +8,6 @@ package org.telegram.android; -import android.graphics.Bitmap; import android.graphics.Paint; import android.text.Layout; import android.text.Spannable; @@ -42,12 +41,11 @@ public class MessageObject { public CharSequence messageText; public int type; public int contentType; - public ArrayList photoThumbs; - public Bitmap imagePreview; public String dateKey; public boolean deleted = false; public float audioProgress; public int audioProgressSec; + public ArrayList photoThumbs; private static TextPaint textPaint; public int lastLineWidth; @@ -66,11 +64,7 @@ public class MessageObject { public ArrayList textLayoutBlocks; - public MessageObject(TLRPC.Message message, AbstractMap users) { - this(message, users, 1); - } - - public MessageObject(TLRPC.Message message, AbstractMap users, int preview) { + public MessageObject(TLRPC.Message message, AbstractMap users, boolean generateLayout) { if (textPaint == null) { textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); textPaint.setColor(0xff000000); @@ -319,6 +313,9 @@ public class MessageObject { type = 8; } else if (message.media.document.mime_type.equals("image/webp") && isSticker()) { type = 13; + if (messageOwner.media.document.thumb != null && messageOwner.media.document.thumb.location != null) { + messageOwner.media.document.thumb.location.ext = "webp"; + } } else { type = 9; } @@ -355,37 +352,25 @@ public class MessageObject { int dateMonth = rightNow.get(Calendar.MONTH); dateKey = String.format("%d_%02d_%02d", dateYear, dateMonth, dateDay); - if (preview != 0) { + if (generateLayout) { generateLayout(); } - generateThumbs(false, preview); + generateThumbs(false); } - public CharSequence replaceWithLink(CharSequence source, String param, TLRPC.User user) { - String name = ContactsController.formatName(user.first_name, user.last_name); - int start = TextUtils.indexOf(source, param); - URLSpanNoUnderline span = new URLSpanNoUnderline("" + user.id); - SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new String[]{name})); - builder.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - return builder; - } - - public void generateThumbs(boolean update, int preview) { + public void generateThumbs(boolean update) { if (messageOwner instanceof TLRPC.TL_messageService) { if (messageOwner.action instanceof TLRPC.TL_messageActionChatEditPhoto) { if (!update) { - photoThumbs = new ArrayList<>(); - for (TLRPC.PhotoSize size : messageOwner.action.photo.sizes) { - photoThumbs.add(new PhotoObject(size, preview, isSecretMedia())); - } + photoThumbs = new ArrayList<>(messageOwner.action.photo.sizes); } else if (photoThumbs != null && !photoThumbs.isEmpty()) { - for (PhotoObject photoObject : photoThumbs) { + for (TLRPC.PhotoSize photoObject : photoThumbs) { for (TLRPC.PhotoSize size : messageOwner.action.photo.sizes) { if (size instanceof TLRPC.TL_photoSizeEmpty) { continue; } - if (size.type.equals(photoObject.photoOwner.type)) { - photoObject.photoOwner.location = size.location; + if (size.type.equals(photoObject.type)) { + photoObject.location = size.location; break; } } @@ -395,22 +380,15 @@ public class MessageObject { } else if (messageOwner.media != null && !(messageOwner.media instanceof TLRPC.TL_messageMediaEmpty)) { if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { if (!update) { - photoThumbs = new ArrayList<>(); - for (TLRPC.PhotoSize size : messageOwner.media.photo.sizes) { - PhotoObject obj = new PhotoObject(size, preview, isSecretMedia()); - photoThumbs.add(obj); - if (imagePreview == null && obj.image != null) { - imagePreview = obj.image; - } - } + photoThumbs = new ArrayList<>(messageOwner.media.photo.sizes); } else if (photoThumbs != null && !photoThumbs.isEmpty()) { - for (PhotoObject photoObject : photoThumbs) { + for (TLRPC.PhotoSize photoObject : photoThumbs) { for (TLRPC.PhotoSize size : messageOwner.media.photo.sizes) { if (size instanceof TLRPC.TL_photoSizeEmpty) { continue; } - if (size.type.equals(photoObject.photoOwner.type)) { - photoObject.photoOwner.location = size.location; + if (size.type.equals(photoObject.type)) { + photoObject.location = size.location; break; } } @@ -419,36 +397,34 @@ public class MessageObject { } else if (messageOwner.media instanceof TLRPC.TL_messageMediaVideo) { if (!update) { photoThumbs = new ArrayList<>(); - PhotoObject obj = new PhotoObject(messageOwner.media.video.thumb, preview, isSecretMedia()); - photoThumbs.add(obj); - if (imagePreview == null && obj.image != null) { - imagePreview = obj.image; - } + photoThumbs.add(messageOwner.media.video.thumb); } else if (photoThumbs != null && !photoThumbs.isEmpty() && messageOwner.media.video.thumb != null) { - PhotoObject photoObject = photoThumbs.get(0); - photoObject.photoOwner.location = messageOwner.media.video.thumb.location; + TLRPC.PhotoSize photoObject = photoThumbs.get(0); + photoObject.location = messageOwner.media.video.thumb.location; } } else if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (!(messageOwner.media.document.thumb instanceof TLRPC.TL_photoSizeEmpty)) { if (!update) { photoThumbs = new ArrayList<>(); - if (type == 13) { - messageOwner.media.document.thumb.location.ext = "webp"; - } - PhotoObject obj = new PhotoObject(messageOwner.media.document.thumb, preview, isSecretMedia()); - photoThumbs.add(obj); - if (imagePreview == null && obj.image != null) { - imagePreview = obj.image; - } + photoThumbs.add(messageOwner.media.document.thumb); } else if (photoThumbs != null && !photoThumbs.isEmpty() && messageOwner.media.document.thumb != null) { - PhotoObject photoObject = photoThumbs.get(0); - photoObject.photoOwner.location = messageOwner.media.document.thumb.location; + TLRPC.PhotoSize photoObject = photoThumbs.get(0); + photoObject.location = messageOwner.media.document.thumb.location; } } } } } + public CharSequence replaceWithLink(CharSequence source, String param, TLRPC.User user) { + String name = ContactsController.formatName(user.first_name, user.last_name); + int start = TextUtils.indexOf(source, param); + URLSpanNoUnderline span = new URLSpanNoUnderline("" + user.id); + SpannableStringBuilder builder = new SpannableStringBuilder(TextUtils.replace(source, new String[]{param}, new String[]{name})); + builder.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return builder; + } + public String getFileName() { if (messageOwner.media instanceof TLRPC.TL_messageMediaVideo) { return FileLoader.getAttachFileName(messageOwner.media.video); @@ -468,6 +444,19 @@ public class MessageObject { return ""; } + public int getFileType() { + if (messageOwner.media instanceof TLRPC.TL_messageMediaVideo) { + return FileLoader.MEDIA_DIR_VIDEO; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + return FileLoader.MEDIA_DIR_DOCUMENT; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaAudio) { + return FileLoader.MEDIA_DIR_AUDIO; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + return FileLoader.MEDIA_DIR_IMAGE; + } + return FileLoader.MEDIA_DIR_CACHE; + } + private boolean containsUrls(CharSequence message) { if (message == null || message.length() < 3 || message.length() > 1024 * 20) { return false; @@ -787,9 +776,9 @@ public class MessageObject { return ""; } - public boolean isSticker() { - if (messageOwner.media != null && messageOwner.media.document != null) { - for (TLRPC.DocumentAttribute attribute : messageOwner.media.document.attributes) { + public static boolean isStickerMessage(TLRPC.Message message) { + if (message.media != null && message.media.document != null) { + for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) { if (attribute instanceof TLRPC.TL_documentAttributeSticker) { return true; } @@ -797,4 +786,8 @@ public class MessageObject { } return false; } + + public boolean isSticker() { + return isStickerMessage(messageOwner); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java index 1c673e6b..71bd216e 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/android/MessagesController.java @@ -314,9 +314,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter sendingTypings.clear(); loadingFullUsers.clear(); loadedFullUsers.clear(); - loadingFullUsers.clear(); - loadedFullUsers.clear(); reloadingMessages.clear(); + loadingFullChats.clear(); + loadedFullChats.clear(); updatesStartWaitTime = 0; currentDeletingTaskTime = 0; @@ -568,6 +568,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { TLRPC.messages_Messages messagesRes = (TLRPC.messages_Messages) response; + ImageLoader.saveMessagesThumbs(messagesRes.messages); MessagesStorage.getInstance().putMessages(messagesRes, dialog_id); final ArrayList objects = new ArrayList<>(); @@ -578,7 +579,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter for (TLRPC.User u : messagesRes.users) { usersLocal.put(u.id, u); } - objects.add(new MessageObject(message, usersLocal, 2)); + objects.add(new MessageObject(message, usersLocal, true)); } AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -898,151 +899,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter }); } - public void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int offset, int count, int max_id, final boolean fromCache, final int classGuid) { - int lower_part = (int)uid; - if (fromCache && res.messages.isEmpty() && lower_part != 0) { - loadMedia(uid, offset, count, max_id, false, classGuid); - } else { - if (!fromCache) { - MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); - MessagesStorage.getInstance().putMedia(uid, res.messages); - } - - final HashMap usersLocal = new HashMap<>(); - for (TLRPC.User u : res.users) { - usersLocal.put(u.id, u); - } - final ArrayList objects = new ArrayList<>(); - for (TLRPC.Message message : res.messages) { - objects.add(new MessageObject(message, usersLocal)); - } - - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - int totalCount; - if (res instanceof TLRPC.TL_messages_messagesSlice) { - totalCount = res.count; - } else { - totalCount = res.messages.size(); - } - putUsers(res.users, fromCache); - for (TLRPC.Chat chat : res.chats) { - putChat(chat, fromCache); - } - NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaDidLoaded, uid, totalCount, objects, fromCache, classGuid); - } - }); - } - } - - public void loadMedia(final long uid, final int offset, final int count, final int max_id, final boolean fromCache, final int classGuid) { - int lower_part = (int)uid; - if (fromCache || lower_part == 0) { - MessagesStorage.getInstance().loadMedia(uid, offset, count, max_id, classGuid); - } else { - TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); - req.offset = offset; - req.limit = count; - req.max_id = max_id; - req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); - req.q = ""; - if (uid < 0) { - req.peer = new TLRPC.TL_inputPeerChat(); - req.peer.chat_id = -lower_part; - } else { - TLRPC.User user = getUser(lower_part); - if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) { - req.peer = new TLRPC.TL_inputPeerForeign(); - req.peer.access_hash = user.access_hash; - } else { - req.peer = new TLRPC.TL_inputPeerContact(); - } - req.peer.user_id = lower_part; - } - long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; - processLoadedMedia(res, uid, offset, count, max_id, false, classGuid); - } - } - }); - ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid); - } - } - - public void processLoadedMediaCount(final int count, final long uid, final int classGuid, final boolean fromCache) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - int lower_part = (int)uid; - if (fromCache && count == -1 && lower_part != 0) { - getMediaCount(uid, classGuid, false); - } else { - if (!fromCache) { - MessagesStorage.getInstance().putMediaCount(uid, count); - } - NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaCountDidLoaded, uid, (fromCache && count == -1 ? 0 : count), fromCache); - } - } - }); - } - - public void getMediaCount(final long uid, final int classGuid, boolean fromCache) { - int lower_part = (int)uid; - if (fromCache || lower_part == 0) { - MessagesStorage.getInstance().getMediaCount(uid, classGuid); - } else { - TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); - req.offset = 0; - req.limit = 1; - req.max_id = 0; - req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); - req.q = ""; - if (uid < 0) { - req.peer = new TLRPC.TL_inputPeerChat(); - req.peer.chat_id = -lower_part; - } else { - TLRPC.User user = getUser(lower_part); - if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) { - req.peer = new TLRPC.TL_inputPeerForeign(); - req.peer.access_hash = user.access_hash; - } else { - req.peer = new TLRPC.TL_inputPeerContact(); - } - req.peer.user_id = lower_part; - } - long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; - MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); - - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - putUsers(res.users, false); - for (TLRPC.Chat chat : res.chats) { - putChat(chat, false); - } - } - }); - - if (res instanceof TLRPC.TL_messages_messagesSlice) { - processLoadedMediaCount(res.count, uid, classGuid, false); - } else { - processLoadedMediaCount(res.messages.size(), uid, classGuid, false); - } - } - } - }); - ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid); - } - } - public void uploadAndApplyUserAvatar(TLRPC.PhotoSize bigPhoto) { if (bigPhoto != null) { uploadingAvatar = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + bigPhoto.location.volume_id + "_" + bigPhoto.location.local_id + ".jpg"; @@ -1470,6 +1326,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter public void run() { int lower_id = (int)dialog_id; int high_id = (int)(dialog_id >> 32); + if (!isCache) { + ImageLoader.saveMessagesThumbs(messagesRes.messages); + } if (!isCache && allowCache) { MessagesStorage.getInstance().putMessages(messagesRes, dialog_id); } @@ -1490,7 +1349,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter ArrayList messagesToReload = null; for (TLRPC.Message message : messagesRes.messages) { message.dialog_id = dialog_id; - objects.add(new MessageObject(message, usersLocal, 2)); + objects.add(new MessageObject(message, usersLocal, true)); if (isCache && message.media instanceof TLRPC.TL_messageMediaUnsupported) { if (message.media.bytes.length == 0 || message.media.bytes.length == 1 && message.media.bytes[0] < TLRPC.LAYER) { if (messagesToReload == null) { @@ -1553,7 +1412,13 @@ public class MessagesController implements NotificationCenter.NotificationCenter dialog_id = -dialog.peer.chat_id; } if (dialog.notify_settings.mute_until != 0) { - editor.putInt("notify2_" + dialog_id, 2); + if (dialog.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime() + 60 * 60 * 24 * 365) { + editor.putInt("notify2_" + dialog_id, 2); + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } else { + editor.putInt("notify2_" + dialog_id, 3); + editor.putInt("notifyuntil_" + dialog_id, dialog.notify_settings.mute_until); + } } } } @@ -1592,7 +1457,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } for (TLRPC.Message m : dialogsRes.messages) { - new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, 0)); + new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, false)); } for (TLRPC.TL_dialog d : dialogsRes.dialogs) { if (d.last_message_date == 0) { @@ -1701,6 +1566,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter int new_totalDialogsCount; if (!isCache) { + ImageLoader.saveMessagesThumbs(dialogsRes.messages); MessagesStorage.getInstance().putDialogs(dialogsRes); } @@ -1716,7 +1582,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter } for (TLRPC.Message m : dialogsRes.messages) { - new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, 0)); + new_dialogMessage.put(m.id, new MessageObject(m, usersLocal, false)); } for (TLRPC.TL_dialog d : dialogsRes.dialogs) { if (d.last_message_date == 0) { @@ -1990,7 +1856,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newMsg.date = ConnectionsManager.getInstance().getCurrentTime(); newMsg.random_id = 0; UserConfig.saveConfig(false); - MessageObject newMsgObj = new MessageObject(newMsg, users); + MessageObject newMsgObj = new MessageObject(newMsg, users, true); newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; ArrayList objArr = new ArrayList<>(); @@ -2034,7 +1900,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(res.users, false); putChats(res.chats, false); final ArrayList messagesObj = new ArrayList<>(); - messagesObj.add(new MessageObject(res.message, users)); + messagesObj.add(new MessageObject(res.message, users, true)); TLRPC.Chat chat = res.chats.get(0); updateInterfaceWithMessages(-chat.id, messagesObj); NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidCreated, chat.id); @@ -2081,7 +1947,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(res.users, false); putChats(res.chats, false); final ArrayList messagesObj = new ArrayList<>(); - messagesObj.add(new MessageObject(res.message, users)); + messagesObj.add(new MessageObject(res.message, users, true)); TLRPC.Chat chat = res.chats.get(0); updateInterfaceWithMessages(-chat.id, messagesObj); NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); @@ -2163,7 +2029,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter putChats(res.chats, false); if (user.id != UserConfig.getClientUserId()) { final ArrayList messagesObj = new ArrayList<>(); - messagesObj.add(new MessageObject(res.message, users)); + messagesObj.add(new MessageObject(res.message, users, true)); TLRPC.Chat chat = res.chats.get(0); updateInterfaceWithMessages(-chat.id, messagesObj); NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, UPDATE_MASK_CHAT_MEMBERS); @@ -2247,7 +2113,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter putUsers(res.users, false); putChats(res.chats, false); final ArrayList messagesObj = new ArrayList<>(); - messagesObj.add(new MessageObject(res.message, users)); + messagesObj.add(new MessageObject(res.message, users, true)); TLRPC.Chat chat = res.chats.get(0); updateInterfaceWithMessages(-chat.id, messagesObj); NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); @@ -2291,13 +2157,17 @@ public class MessagesController implements NotificationCenter.NotificationCenter final TLRPC.messages_StatedMessage res = (TLRPC.messages_StatedMessage) response; MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); + final ArrayList messages = new ArrayList<>(); + messages.add(res.message); + ImageLoader.saveMessagesThumbs(messages); + AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { putUsers(res.users, false); putChats(res.chats, false); final ArrayList messagesObj = new ArrayList<>(); - messagesObj.add(new MessageObject(res.message, users)); + messagesObj.add(new MessageObject(res.message, users, true)); TLRPC.Chat chat = res.chats.get(0); updateInterfaceWithMessages(-chat.id, messagesObj); NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); @@ -2305,8 +2175,6 @@ public class MessagesController implements NotificationCenter.NotificationCenter } }); - final ArrayList messages = new ArrayList<>(); - messages.add(res.message); MessagesStorage.getInstance().putMessages(messages, true, true, false, 0); processNewDifferenceParams(res.seq, res.pts, -1); } @@ -2616,9 +2484,11 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } + ImageLoader.saveMessagesThumbs(res.new_messages); + final ArrayList pushMessages = new ArrayList<>(); for (TLRPC.Message message : res.new_messages) { - MessageObject obj = new MessageObject(message, usersDict, 2); + MessageObject obj = new MessageObject(message, usersDict, true); long dialog_id = obj.messageOwner.dialog_id; if (dialog_id == 0) { @@ -2763,7 +2633,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter message.media = new TLRPC.TL_messageMediaEmpty(); MessagesStorage.lastSeqValue = updates.seq; MessagesStorage.lastPtsValue = updates.pts; - final MessageObject obj = new MessageObject(message, null); + final MessageObject obj = new MessageObject(message, null, true); final ArrayList objArr = new ArrayList<>(); objArr.add(obj); ArrayList arr = new ArrayList<>(); @@ -2834,7 +2704,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter MessagesStorage.lastSeqValue = updates.seq; MessagesStorage.lastPtsValue = updates.pts; MessagesStorage.lastDateValue = updates.date; - final MessageObject obj = new MessageObject(message, null); + final MessageObject obj = new MessageObject(message, null, true); final ArrayList objArr = new ArrayList<>(); objArr.add(obj); ArrayList arr = new ArrayList<>(); @@ -2976,6 +2846,29 @@ public class MessagesController implements NotificationCenter.NotificationCenter MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); } + private boolean isNotifySettingsMuted(TLRPC.PeerNotifySettings settings) { + return settings instanceof TLRPC.TL_peerNotifySettings && settings.mute_until > ConnectionsManager.getInstance().getCurrentTime(); + } + + public boolean isDialogMuted(long dialog_id) { + TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id); + if (dialog != null) { + return isNotifySettingsMuted(dialog.notify_settings); + } else { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int mute_type = preferences.getInt("notify2_" + dialog_id, 0); + if (mute_type == 2) { + return true; + } else if (mute_type == 3) { + int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) { + return true; + } + } + } + return false; + } + public boolean processUpdateArray(ArrayList updates, final ArrayList usersArr, final ArrayList chatsArr) { if (updates.isEmpty()) { return true; @@ -3043,7 +2936,8 @@ public class MessagesController implements NotificationCenter.NotificationCenter } } messagesArr.add(upd.message); - MessageObject obj = new MessageObject(upd.message, usersDict, 2); + ImageLoader.saveMessageThumbs(upd.message); + MessageObject obj = new MessageObject(upd.message, usersDict, true); if (obj.type == 11) { interfaceUpdateMask |= UPDATE_MASK_CHAT_AVATAR; } else if (obj.type == 10) { @@ -3134,7 +3028,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newMessage.dialog_id = update.user_id; messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict); + MessageObject obj = new MessageObject(newMessage, usersDict, true); ArrayList arr = messages.get(newMessage.dialog_id); if (arr == null) { arr = new ArrayList<>(); @@ -3178,7 +3072,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newMessage.dialog_id = 777000; messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict); + MessageObject obj = new MessageObject(newMessage, usersDict, true); ArrayList arr = messages.get(newMessage.dialog_id); if (arr == null) { arr = new ArrayList<>(); @@ -3200,8 +3094,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter messages.put(uid, arr); } for (TLRPC.Message message : decryptedMessages) { + ImageLoader.saveMessageThumbs(message); messagesArr.add(message); - MessageObject obj = new MessageObject(message, usersDict, 2); + MessageObject obj = new MessageObject(message, usersDict, true); arr.add(obj); pushMessages.add(obj); } @@ -3287,7 +3182,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter newMessage.message = ((TLRPC.TL_updateServiceNotification)update).message; messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict); + MessageObject obj = new MessageObject(newMessage, usersDict, true); ArrayList arr = messages.get(newMessage.dialog_id); if (arr == null) { arr = new ArrayList<>(); @@ -3370,6 +3265,9 @@ public class MessagesController implements NotificationCenter.NotificationCenter } toDbUser.status = update.status; dbUsersStatus.add(toDbUser); + if (update.user_id == UserConfig.getClientUserId()) { + NotificationsController.getInstance().setLastOnlineFromOtherDevice(update.status.expires); + } } else if (update instanceof TLRPC.TL_updateUserName) { if (currentUser != null) { currentUser.first_name = update.first_name; @@ -3405,15 +3303,38 @@ public class MessagesController implements NotificationCenter.NotificationCenter SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); editor = preferences.edit(); } - int dialog_id = update.peer.peer.user_id; + long dialog_id = update.peer.peer.user_id; if (dialog_id == 0) { dialog_id = -update.peer.peer.chat_id; } - if (update.notify_settings.mute_until != 0) { - editor.putInt("notify2_" + dialog_id, 2); - } else { - editor.remove("notify2_" + dialog_id); + TLRPC.TL_dialog dialog = dialogs_dict.get(dialog_id); + if (dialog != null) { + dialog.notify_settings = update.notify_settings; } + if (update.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime()) { + int until = 0; + if (update.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime() + 60 * 60 * 24 * 365) { + editor.putInt("notify2_" + dialog_id, 2); + if (dialog != null) { + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } + } else { + until = update.notify_settings.mute_until; + editor.putInt("notify2_" + dialog_id, 3); + editor.putInt("notifyuntil_" + dialog_id, update.notify_settings.mute_until); + if (dialog != null) { + dialog.notify_settings.mute_until = until; + } + } + MessagesStorage.getInstance().setDialogFlags(dialog_id, ((long)until << 32) | 1); + } else { + if (dialog != null) { + dialog.notify_settings.mute_until = 0; + } + editor.remove("notify2_" + dialog_id); + MessagesStorage.getInstance().setDialogFlags(dialog_id, 0); + } + }/* else if (update.peer instanceof TLRPC.TL_notifyChats) { disable global settings sync if (editor == null) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); diff --git a/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java index 4b319175..87f0e198 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/android/MessagesStorage.java @@ -17,6 +17,7 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteDatabase; import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.android.query.SharedMediaQuery; import org.telegram.messenger.BuffersStorage; import org.telegram.messenger.ByteBufferDesc; import org.telegram.messenger.ConnectionsManager; @@ -109,8 +110,6 @@ public class MessagesStorage { database.executeFast("CREATE TABLE chat_settings(uid INTEGER PRIMARY KEY, participants BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE pending_read(uid INTEGER PRIMARY KEY, max_id INTEGER)").stepThis().dispose(); - database.executeFast("CREATE TABLE media(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, data BLOB)").stepThis().dispose(); - database.executeFast("CREATE TABLE media_counts(uid INTEGER PRIMARY KEY, count INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE wallpapers(uid INTEGER PRIMARY KEY, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE randoms(random_id INTEGER, mid INTEGER, PRIMARY KEY (random_id, mid))").stepThis().dispose(); database.executeFast("CREATE TABLE enc_tasks_v2(mid INTEGER PRIMARY KEY, date INTEGER)").stepThis().dispose(); @@ -123,9 +122,6 @@ public class MessagesStorage { database.executeFast("CREATE TABLE messages_seq(mid INTEGER PRIMARY KEY, seq_in INTEGER, seq_out INTEGER);").stepThis().dispose(); database.executeFast("CREATE TABLE web_recent_v3(id TEXT, type INTEGER, image_url TEXT, thumb_url TEXT, local_url TEXT, width INTEGER, height INTEGER, size INTEGER, date INTEGER, PRIMARY KEY (id, type));").stepThis().dispose(); database.executeFast("CREATE TABLE stickers(id INTEGER PRIMARY KEY, data BLOB, date INTEGER);").stepThis().dispose(); - //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); - - //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); database.executeFast("CREATE TABLE user_contacts_v6(uid INTEGER PRIMARY KEY, fname TEXT, sname TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE user_phones_v6(uid INTEGER, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (uid, phone))").stepThis().dispose(); @@ -134,6 +130,8 @@ public class MessagesStorage { //database.executeFast("CREATE TABLE messages_holes(uid INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, start));").stepThis().dispose(); //database.executeFast("CREATE INDEX IF NOT EXISTS type_uid_end_messages_holes ON messages_holes(uid, end);").stepThis().dispose(); + //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); + //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS type_date_idx_download_queue ON download_queue(type, date);").stepThis().dispose(); @@ -146,10 +144,6 @@ public class MessagesStorage { database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_idx_dialogs ON dialogs(last_mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_idx_dialogs ON dialogs(unread_count);").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_idx_media ON media(uid, mid);").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media(mid);").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_media ON media(uid, date, mid);").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_idx_messages ON messages(uid, mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_messages ON messages(uid, date, mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS mid_out_idx_messages ON messages(mid, out);").stepThis().dispose(); @@ -158,7 +152,17 @@ public class MessagesStorage { database.executeFast("CREATE INDEX IF NOT EXISTS seq_idx_messages_seq ON messages_seq(seq_in, seq_out);").stepThis().dispose(); - database.executeFast("PRAGMA user_version = 12").stepThis().dispose(); + //shared media + database.executeFast("CREATE TABLE media_v2(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, type INTEGER, data BLOB)").stepThis().dispose(); + database.executeFast("CREATE TABLE media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media ON media_v2(uid, mid, type, date);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media_v2(mid);").stepThis().dispose(); + + //kev-value + database.executeFast("CREATE TABLE keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose(); + + //version + database.executeFast("PRAGMA user_version = 13").stepThis().dispose(); } else { try { SQLiteCursor cursor = database.queryFinalized("SELECT seq, pts, date, qts, lsv, sg, pbytes FROM params WHERE id = 1"); @@ -189,7 +193,7 @@ public class MessagesStorage { } } int version = database.executeInt("PRAGMA user_version"); - if (version < 12) { + if (version < 13) { updateDbToLastVersion(version); } } @@ -208,9 +212,6 @@ public class MessagesStorage { if (version < 4) { database.executeFast("CREATE TABLE IF NOT EXISTS user_photos(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media(mid);").stepThis().dispose(); - database.executeFast("CREATE INDEX IF NOT EXISTS uid_date_mid_idx_media ON media(uid, date, mid);").stepThis().dispose(); - database.executeFast("DROP INDEX IF EXISTS read_state_out_idx_messages;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS ttl_idx_messages;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS date_idx_messages;").stepThis().dispose(); @@ -350,6 +351,23 @@ public class MessagesStorage { database.executeFast("PRAGMA user_version = 12").stepThis().dispose(); version = 12; } + if (version == 12 && version < 13) { + database.executeFast("DROP INDEX IF EXISTS uid_mid_idx_media;").stepThis().dispose(); + database.executeFast("DROP INDEX IF EXISTS mid_idx_media;").stepThis().dispose(); + database.executeFast("DROP INDEX IF EXISTS uid_date_mid_idx_media;").stepThis().dispose(); + database.executeFast("DROP TABLE IF EXISTS media;").stepThis().dispose(); + database.executeFast("DROP TABLE IF EXISTS media_counts;").stepThis().dispose(); + + database.executeFast("CREATE TABLE IF NOT EXISTS media_v2(mid INTEGER PRIMARY KEY, uid INTEGER, date INTEGER, type INTEGER, data BLOB)").stepThis().dispose(); + database.executeFast("CREATE TABLE IF NOT EXISTS media_counts_v2(uid INTEGER, type INTEGER, count INTEGER, PRIMARY KEY(uid, type))").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS uid_mid_type_date_idx_media ON media_v2(uid, mid, type, date);").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS mid_idx_media ON media_v2(mid);").stepThis().dispose(); + + database.executeFast("CREATE TABLE IF NOT EXISTS keyvalue(id TEXT PRIMARY KEY, value TEXT)").stepThis().dispose(); + + database.executeFast("PRAGMA user_version = 13").stepThis().dispose(); + version = 13; + } } catch (Exception e) { FileLog.e("tmessages", e); } @@ -445,7 +463,7 @@ public class MessagesStorage { }); } - public void setDialogFlags(final long did, final int flags) { + public void setDialogFlags(final long did, final long flags) { storageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -821,9 +839,9 @@ public class MessagesStorage { } } database.executeFast("UPDATE dialogs SET unread_count = 0 WHERE did = " + did).stepThis().dispose(); - database.executeFast("DELETE FROM media_counts WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM messages WHERE uid = " + did).stepThis().dispose(); - database.executeFast("DELETE FROM media WHERE uid = " + did).stepThis().dispose(); + database.executeFast("DELETE FROM media_counts_v2 WHERE uid = " + did).stepThis().dispose(); + database.executeFast("DELETE FROM media_v2 WHERE uid = " + did).stepThis().dispose(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -1479,149 +1497,6 @@ public class MessagesStorage { }); } - public void putMediaCount(final long uid, final int count) { - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - try { - SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_counts VALUES(?, ?)"); - state2.requery(); - state2.bindLong(1, uid); - state2.bindInteger(2, count); - state2.step(); - state2.dispose(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void getMediaCount(final long uid, final int classGuid) { - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - try { - int count = -1; - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts WHERE uid = %d LIMIT 1", uid)); - if (cursor.next()) { - count = cursor.intValue(0); - } - cursor.dispose(); - int lower_part = (int)uid; - if (count == -1 && lower_part == 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media WHERE uid = %d LIMIT 1", uid)); - if (cursor.next()) { - count = cursor.intValue(0); - } - cursor.dispose(); - if (count != -1) { - putMediaCount(uid, count); - } - } - MessagesController.getInstance().processLoadedMediaCount(count, uid, classGuid, true); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void loadMedia(final long uid, final int offset, final int count, final int max_id, final int classGuid) { - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); - try { - ArrayList loadedUsers = new ArrayList<>(); - ArrayList fromUser = new ArrayList<>(); - - SQLiteCursor cursor; - - if ((int)uid != 0) { - if (max_id != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media WHERE uid = %d AND mid < %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, count)); - } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media WHERE uid = %d ORDER BY date DESC, mid DESC LIMIT %d,%d", uid, offset, count)); - } - } else { - if (max_id != 0) { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, count)); - } else { - cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d ORDER BY m.mid ASC LIMIT %d,%d", uid, offset, count)); - } - } - - while (cursor.next()) { - ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { - TLRPC.Message message = (TLRPC.Message)TLClassStore.Instance().TLdeserialize(data, data.readInt32()); - message.id = cursor.intValue(1); - message.dialog_id = uid; - if ((int)uid == 0) { - message.random_id = cursor.longValue(2); - } - res.messages.add(message); - fromUser.add(message.from_id); - } - buffersStorage.reuseFreeBuffer(data); - } - cursor.dispose(); - - StringBuilder usersToLoad = new StringBuilder(); - for (int uid : fromUser) { - if (!loadedUsers.contains(uid)) { - if (usersToLoad.length() != 0) { - usersToLoad.append(","); - } - usersToLoad.append(uid); - loadedUsers.add(uid); - } - } - if (usersToLoad.length() != 0) { - getUsersInternal(usersToLoad.toString(), res.users); - } - } catch (Exception e) { - res.messages.clear(); - res.chats.clear(); - res.users.clear(); - FileLog.e("tmessages", e); - } finally { - MessagesController.getInstance().processLoadedMedia(res, uid, offset, count, max_id, true, classGuid); - } - } - }); - } - - public void putMedia(final long uid, final ArrayList messages) { - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - try { - database.beginTransaction(); - SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)"); - for (TLRPC.Message message : messages) { - if (message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaPhoto) { - state2.requery(); - ByteBufferDesc data = buffersStorage.getFreeBuffer(message.getObjectSize()); - message.serializeToStream(data); - state2.bindInteger(1, message.id); - state2.bindLong(2, uid); - state2.bindInteger(3, message.date); - state2.bindByteBuffer(4, data.buffer); - state2.step(); - buffersStorage.reuseFreeBuffer(data); - } - } - state2.dispose(); - database.commitTransaction(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - public void getUnsentMessages(final int count) { storageQueue.postRunnable(new Runnable() { @Override @@ -2105,10 +1980,11 @@ public class MessagesStorage { storageQueue.postRunnable(new Runnable() { @Override public void run() { + SQLitePreparedStatement state = null; try { String id = Utilities.MD5(path); if (id != null) { - SQLitePreparedStatement state = database.executeFast("REPLACE INTO sent_files_v2 VALUES(?, ?, ?)"); + state = database.executeFast("REPLACE INTO sent_files_v2 VALUES(?, ?, ?)"); state.requery(); ByteBufferDesc data = buffersStorage.getFreeBuffer(file.getObjectSize()); file.serializeToStream(data); @@ -2116,11 +1992,14 @@ public class MessagesStorage { state.bindInteger(2, type); state.bindByteBuffer(3, data.buffer); state.step(); - state.dispose(); buffersStorage.reuseFreeBuffer(data); } } catch (Exception e) { FileLog.e("tmessages", e); + } finally { + if (state != null) { + state.dispose(); + } } } }); @@ -2439,7 +2318,7 @@ public class MessagesStorage { state.dispose(); } - private void getUsersInternal(String usersToLoad, ArrayList result) throws Exception { + public void getUsersInternal(String usersToLoad, ArrayList result) throws Exception { if (usersToLoad == null || usersToLoad.length() == 0 || result == null) { return; } @@ -2464,7 +2343,7 @@ public class MessagesStorage { cursor.dispose(); } - private void getChatsInternal(String chatsToLoad, ArrayList result) throws Exception { + public void getChatsInternal(String chatsToLoad, ArrayList result) throws Exception { if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) { return; } @@ -2486,7 +2365,7 @@ public class MessagesStorage { cursor.dispose(); } - private void getEncryptedChatsInternal(String chatsToLoad, ArrayList result, ArrayList usersToLoad) throws Exception { + public void getEncryptedChatsInternal(String chatsToLoad, ArrayList result, ArrayList usersToLoad) throws Exception { if (chatsToLoad == null || chatsToLoad.length() == 0 || result == null) { return; } @@ -2633,15 +2512,6 @@ public class MessagesStorage { }); } - private boolean canAddMessageToMedia(TLRPC.Message message) { - if (message instanceof TLRPC.TL_message_secret && message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60) { - return false; - } else if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) { - return true; - } - return false; - } - private int getMessageMediaType(TLRPC.Message message) { if (message instanceof TLRPC.TL_message_secret && ( message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60 || @@ -2661,13 +2531,14 @@ public class MessagesStorage { } HashMap messagesMap = new HashMap<>(); HashMap messagesCounts = new HashMap<>(); - HashMap mediaCounts = new HashMap<>(); + HashMap> mediaCounts = new HashMap<>(); + HashMap mediaTypes = new HashMap<>(); HashMap messagesIdsMap = new HashMap<>(); HashMap messagesMediaIdsMap = new HashMap<>(); StringBuilder messageIds = new StringBuilder(); StringBuilder messageMediaIds = new StringBuilder(); SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); - SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO randoms VALUES(?, ?)"); SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO download_queue VALUES(?, ?, ?, ?)"); @@ -2689,12 +2560,39 @@ public class MessagesStorage { messagesIdsMap.put(message.id, dialog_id); } - if (canAddMessageToMedia(message)) { + if (SharedMediaQuery.canAddMessageToMedia(message)) { if (messageMediaIds.length() > 0) { messageMediaIds.append(","); } messageMediaIds.append(message.id); messagesMediaIdsMap.put(message.id, dialog_id); + mediaTypes.put(message.id, SharedMediaQuery.getMediaType(message)); + } + } + + if (messageMediaIds.length() > 0) { + SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM media_v2 WHERE mid IN(" + messageMediaIds.toString() + ")"); + while (cursor.next()) { + int mid = cursor.intValue(0); + messagesMediaIdsMap.remove(mid); + } + cursor.dispose(); + for (HashMap.Entry entry : messagesMediaIdsMap.entrySet()) { + Integer type = mediaTypes.get(entry.getKey()); + HashMap counts = mediaCounts.get(type); + Integer count; + if (counts == null) { + counts = new HashMap<>(); + count = 0; + mediaCounts.put(type, counts); + } else { + count = counts.get(entry.getValue()); + } + if (count == null) { + count = 0; + } + count++; + counts.put(entry.getValue(), count); } } @@ -2715,23 +2613,6 @@ public class MessagesStorage { } } - if (messageMediaIds.length() > 0) { - SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM media WHERE mid IN(" + messageMediaIds.toString() + ")"); - while (cursor.next()) { - int mid = cursor.intValue(0); - messagesMediaIdsMap.remove(mid); - } - cursor.dispose(); - for (Long dialog_id : messagesMediaIdsMap.values()) { - Integer count = mediaCounts.get(dialog_id); - if (count == null) { - count = 0; - } - count++; - mediaCounts.put(dialog_id, count); - } - } - int downloadMediaMask = 0; for (TLRPC.Message message : messages) { fixUnsupportedMedia(message); @@ -2784,12 +2665,13 @@ public class MessagesStorage { state3.step(); } - if (canAddMessageToMedia(message)) { + if (SharedMediaQuery.canAddMessageToMedia(message)) { state2.requery(); state2.bindInteger(1, messageId); state2.bindLong(2, dialog_id); state2.bindInteger(3, message.date); - state2.bindByteBuffer(4, data.buffer); + state2.bindInteger(4, SharedMediaQuery.getMediaType(message)); + state2.bindByteBuffer(5, data.buffer); state2.step(); } buffersStorage.reuseFreeBuffer(data); @@ -2883,33 +2765,37 @@ public class MessagesStorage { state.step(); } state.dispose(); + + if (!mediaCounts.isEmpty()) { + state = database.executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?)"); + for (HashMap.Entry> counts : mediaCounts.entrySet()) { + Integer type = counts.getKey(); + for (HashMap.Entry pair : counts.getValue().entrySet()) { + long uid = pair.getKey(); + int lower_part = (int) uid; + int count = -1; + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); + if (cursor.next()) { + count = cursor.intValue(0); + } + cursor.dispose(); + if (count != -1) { + state.requery(); + count += pair.getValue(); + state.bindLong(1, uid); + state.bindInteger(2, type); + state.bindInteger(3, count); + state.step(); + } + } + } + state.dispose(); + } if (withTransaction) { database.commitTransaction(); } MessagesController.getInstance().processDialogsUpdateRead(messagesCounts); - if (!mediaCounts.isEmpty()) { - state = database.executeFast("REPLACE INTO media_counts VALUES(?, ?)"); - for (HashMap.Entry pair : mediaCounts.entrySet()) { - long uid = pair.getKey(); - int lower_part = (int)uid; - int count = -1; - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts WHERE uid = %d LIMIT 1", uid)); - if (cursor.next()) { - count = cursor.intValue(0); - } - if (count != -1) { - state.requery(); - count += pair.getValue(); - state.bindLong(1, uid); - state.bindInteger(2, count); - state.step(); - } - cursor.dispose(); - } - state.dispose(); - } - if (downloadMediaMask != 0) { final int downloadMediaMaskFinal = downloadMediaMask; AndroidUtilities.runOnUIThread(new Runnable() { @@ -3078,6 +2964,12 @@ public class MessagesStorage { state.bindInteger(2, oldId); state.step(); } catch (Exception e) { + try { + database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid = %d", oldId)).stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid = %d", oldId)).stepThis().dispose(); + } catch (Exception e2) { + FileLog.e("tmessages", e2); + } FileLog.e("tmessages", e); } finally { if (state != null) { @@ -3087,11 +2979,16 @@ public class MessagesStorage { } try { - state = database.executeFast("UPDATE media SET mid = ? WHERE mid = ?"); + state = database.executeFast("UPDATE media_v2 SET mid = ? WHERE mid = ?"); state.bindInteger(1, newId); state.bindInteger(2, oldId); state.step(); } catch (Exception e) { + try { + database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid = %d", oldId)).stepThis().dispose(); + } catch (Exception e2) { + FileLog.e("tmessages", e2); + } FileLog.e("tmessages", e); } finally { if (state != null) { @@ -3352,8 +3249,8 @@ public class MessagesStorage { FileLoader.getInstance().deleteFiles(filesToDelete); database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid IN(%s)", ids)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid IN(%s)", ids)).stepThis().dispose(); - database.executeFast(String.format(Locale.US, "DELETE FROM media WHERE mid IN(%s)", ids)).stepThis().dispose(); - database.executeFast("DELETE FROM media_counts WHERE 1").stepThis().dispose(); + database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid IN(%s)", ids)).stepThis().dispose(); + database.executeFast("DELETE FROM media_counts_v2 WHERE 1").stepThis().dispose(); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -3521,7 +3418,7 @@ public class MessagesStorage { database.beginTransaction(); if (!messages.messages.isEmpty()) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); - SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); for (TLRPC.Message message : messages.messages) { fixUnsupportedMedia(message); state.requery(); @@ -3538,12 +3435,13 @@ public class MessagesStorage { state.bindInteger(9, 0); state.step(); - if (message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaPhoto) { + if (SharedMediaQuery.canAddMessageToMedia(message)) { state2.requery(); state2.bindInteger(1, message.id); state2.bindLong(2, dialog_id); state2.bindInteger(3, message.date); - state2.bindByteBuffer(4, data.buffer); + state2.bindInteger(4, SharedMediaQuery.getMediaType(message)); + state2.bindByteBuffer(5, data.buffer); state2.step(); } buffersStorage.reuseFreeBuffer(data); @@ -3573,13 +3471,22 @@ public class MessagesStorage { usersToLoad.add(UserConfig.getClientUserId()); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid ORDER BY d.date DESC LIMIT %d,%d", offset, count)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.date DESC LIMIT %d,%d", offset, count)); while (cursor.next()) { TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = cursor.longValue(0); dialog.top_message = cursor.intValue(1); dialog.unread_count = cursor.intValue(2); dialog.last_message_date = cursor.intValue(3); + long flags = cursor.longValue(8); + int low_flags = (int)flags; + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + if ((low_flags & 1) != 0) { + dialog.notify_settings.mute_until = (int)(flags >> 32); + if (dialog.notify_settings.mute_until == 0) { + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } + } dialogs.dialogs.add(dialog); ByteBufferDesc data = buffersStorage.getFreeBuffer(cursor.byteArrayLength(4)); @@ -3680,7 +3587,7 @@ public class MessagesStorage { if (!dialogs.dialogs.isEmpty()) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs(did, date, unread_count, last_mid) VALUES(?, ?, ?, ?)"); - SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media VALUES(?, ?, ?, ?)"); + SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); for (TLRPC.TL_dialog dialog : dialogs.dialogs) { @@ -3717,12 +3624,13 @@ public class MessagesStorage { state4.bindInteger(2, dialog.notify_settings.mute_until != 0 ? 1 : 0); state4.step(); - if (message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaPhoto) { + if (SharedMediaQuery.canAddMessageToMedia(message)) { state3.requery(); state3.bindLong(1, message.id); state3.bindInteger(2, uid); state3.bindInteger(3, message.date); - state3.bindByteBuffer(4, data.buffer); + state3.bindInteger(4, SharedMediaQuery.getMediaType(message)); + state3.bindByteBuffer(5, data.buffer); state3.step(); } buffersStorage.reuseFreeBuffer(data); diff --git a/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java index 4500948a..6e045c32 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/android/NotificationCenter.java @@ -52,6 +52,8 @@ public class NotificationCenter { public static final int httpFileDidLoaded = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++; + public static final int messageThumbGenerated = totalEvents++; + public static final int wallpapersDidLoaded = totalEvents++; public static final int closeOtherAppActivities = totalEvents++; public static final int didUpdatedConnectionState = totalEvents++; @@ -70,16 +72,16 @@ public class NotificationCenter { public static final int FileNewChunkAvailable = totalEvents++; public static final int FilePreparingFailed = totalEvents++; - public final static int audioProgressDidChanged = totalEvents++; - public final static int audioDidReset = totalEvents++; - public final static int recordProgressChanged = totalEvents++; - public final static int recordStarted = totalEvents++; - public final static int recordStartError = totalEvents++; - public final static int recordStopped = totalEvents++; - public final static int screenshotTook = totalEvents++; - public final static int albumsDidLoaded = totalEvents++; - public final static int audioDidSent = totalEvents++; - public final static int audioDidStarted = totalEvents++; + public static final int audioProgressDidChanged = totalEvents++; + public static final int audioDidReset = totalEvents++; + public static final int recordProgressChanged = totalEvents++; + public static final int recordStarted = totalEvents++; + public static final int recordStartError = totalEvents++; + public static final int recordStopped = totalEvents++; + public static final int screenshotTook = totalEvents++; + public static final int albumsDidLoaded = totalEvents++; + public static final int audioDidSent = totalEvents++; + public static final int audioDidStarted = totalEvents++; public static final int audioRouteChanged = totalEvents++; final private HashMap> observers = new HashMap<>(); diff --git a/TMessagesProj/src/main/java/org/telegram/android/NotificationDelay.java b/TMessagesProj/src/main/java/org/telegram/android/NotificationDelay.java new file mode 100644 index 00000000..c47af63f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/android/NotificationDelay.java @@ -0,0 +1,29 @@ +/* + * This is the source code of Telegram for Android v. 2.0.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2014. + */ + +package org.telegram.android; + +import android.app.IntentService; +import android.content.Intent; + +public class NotificationDelay extends IntentService { + + public NotificationDelay() { + super("NotificationDelay"); + } + + @Override + protected void onHandleIntent(Intent intent) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationsController.getInstance().notificationDelayReached(); + } + }); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java index 9d461166..c0cc5d4f 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/android/NotificationsController.java @@ -31,6 +31,8 @@ import org.json.JSONObject; import org.telegram.messenger.ConnectionsManager; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.RPCRequest; +import org.telegram.messenger.TLObject; import org.telegram.messenger.TLRPC; import org.telegram.messenger.UserConfig; import org.telegram.messenger.ApplicationLoader; @@ -57,6 +59,7 @@ public class NotificationsController { private int total_unread_count = 0; private int personal_count = 0; private boolean notifyCheck = false; + private int lastOnlineFromOtherDevice = 0; private static volatile NotificationsController Instance = null; public static NotificationsController getInstance() { @@ -256,6 +259,27 @@ public class NotificationsController { } } + private void scheduleNotificationDelay(boolean onlineReason) { + try { + FileLog.e("tmessages", "delay notification start"); + AlarmManager alarm = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE); + PendingIntent pintent = PendingIntent.getService(ApplicationLoader.applicationContext, 0, new Intent(ApplicationLoader.applicationContext, NotificationDelay.class), 0); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (onlineReason) { + alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 3 * 1000, pintent); + } else { + alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 500, pintent); + } + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + + protected void notificationDelayReached() { + FileLog.e("tmessages", "delay reached"); + showOrUpdateNotification(true); + } + protected void repeatNotificationMaybe() { int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); if (hour >= 11 && hour <= 22) { @@ -266,6 +290,15 @@ public class NotificationsController { } } + public void setLastOnlineFromOtherDevice(final int time) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + lastOnlineFromOtherDevice = time; + } + }); + } + private void showOrUpdateNotification(boolean notifyAboutLast) { if (!UserConfig.isClientActivated() || pushMessages.isEmpty()) { dismissNotification(); @@ -307,6 +340,12 @@ public class NotificationsController { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); int notify_override = preferences.getInt("notify2_" + dialog_id, 0); + if (notify_override == 3) { + int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) { + notify_override = 2; + } + } if (!notifyAboutLast || notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || chat_id != 0 && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0) { notifyDisabled = true; } @@ -490,7 +529,7 @@ public class NotificationsController { } if (photoPath != null) { - BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50", null); + BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50"); if (img != null) { mBuilder.setLargeIcon(img.getBitmap()); } @@ -773,6 +812,12 @@ public class NotificationsController { popup = (int)dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0); if (value == null) { int notify_override = preferences.getInt("notify2_" + dialog_id, 0); + if (notify_override == 3) { + int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) { + notify_override = 2; + } + } value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || isChat && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0); settingsCache.put(dialog_id, value); } @@ -808,6 +853,12 @@ public class NotificationsController { long dialog_id = entry.getKey(); int notify_override = preferences.getInt("notify2_" + dialog_id, 0); + if (notify_override == 3) { + int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) { + notify_override = 2; + } + } boolean canAddValue = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int)dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0); Integer currentCount = pushDialogs.get(dialog_id); @@ -842,6 +893,14 @@ public class NotificationsController { pushDialogs.put(dialog_id, newCount); } } + /*if (old_unread_count != total_unread_count) { TODO + if (lastOnlineFromOtherDevice > ConnectionsManager.getInstance().getCurrentTime()) { + showOrUpdateNotification(false); + scheduleNotificationDelay(true); + } else { + showOrUpdateNotification(notifyCheck); + } + }*/ if (old_unread_count != total_unread_count) { showOrUpdateNotification(notifyCheck); } @@ -869,6 +928,12 @@ public class NotificationsController { Boolean value = settingsCache.get(dialog_id); if (value == null) { int notify_override = preferences.getInt("notify2_" + dialog_id, 0); + if (notify_override == 3) { + int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) { + notify_override = 2; + } + } value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0); settingsCache.put(dialog_id, value); } @@ -884,7 +949,7 @@ public class NotificationsController { if (pushMessagesDict.containsKey(message.id)) { continue; } - MessageObject messageObject = new MessageObject(message, null, 0); + MessageObject messageObject = new MessageObject(message, null, false); if (isPersonalMessage(messageObject)) { personal_count++; } @@ -892,6 +957,12 @@ public class NotificationsController { Boolean value = settingsCache.get(dialog_id); if (value == null) { int notify_override = preferences.getInt("notify2_" + dialog_id, 0); + if (notify_override == 3) { + int mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + if (mute_until >= ConnectionsManager.getInstance().getCurrentTime()) { + notify_override = 2; + } + } value = !(notify_override == 2 || (!preferences.getBoolean("EnableAll", true) || ((int) dialog_id < 0) && !preferences.getBoolean("EnableGroup", true)) && notify_override == 0); settingsCache.put(dialog_id, value); } @@ -957,4 +1028,49 @@ public class NotificationsController { return messageObject.messageOwner.to_id != null && messageObject.messageOwner.to_id.chat_id == 0 && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); } + + public static void updateServerNotificationsSettings(long dialog_id) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.notificationsSettingsUpdated); + if ((int)dialog_id == 0) { + return; + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings(); + req.settings = new TLRPC.TL_inputPeerNotifySettings(); + req.settings.sound = "default"; + req.settings.events_mask = 0; + int mute_type = preferences.getInt("notify2_" + dialog_id, 0); + if (mute_type == 3) { + req.settings.mute_until = preferences.getInt("notifyuntil_" + dialog_id, 0); + } else { + req.settings.mute_until = mute_type != 2 ? 0 : Integer.MAX_VALUE; + } + req.settings.show_previews = preferences.getBoolean("preview_" + dialog_id, true); + + req.peer = new TLRPC.TL_inputNotifyPeer(); + + if ((int)dialog_id < 0) { + ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerChat(); + ((TLRPC.TL_inputNotifyPeer)req.peer).peer.chat_id = -(int)dialog_id; + } else { + TLRPC.User user = MessagesController.getInstance().getUser((int)dialog_id); + if (user == null) { + return; + } + if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) { + ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerForeign(); + ((TLRPC.TL_inputNotifyPeer)req.peer).peer.access_hash = user.access_hash; + } else { + ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerContact(); + } + ((TLRPC.TL_inputNotifyPeer)req.peer).peer.user_id = (int)dialog_id; + } + + ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/android/PhotoObject.java b/TMessagesProj/src/main/java/org/telegram/android/PhotoObject.java deleted file mode 100644 index d0d16e57..00000000 --- a/TMessagesProj/src/main/java/org/telegram/android/PhotoObject.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 1.3.2. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013. - */ - -package org.telegram.android; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; - -import org.telegram.messenger.FileLog; -import org.telegram.messenger.TLRPC; -import org.telegram.messenger.Utilities; - -import java.nio.ByteBuffer; -import java.util.ArrayList; - -public class PhotoObject { - - public TLRPC.PhotoSize photoOwner; - public Bitmap image; - - public PhotoObject(TLRPC.PhotoSize photo, int preview, boolean secret) { - photoOwner = photo; - - if (preview != 0 && photo instanceof TLRPC.TL_photoCachedSize) { - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inPreferredConfig = Bitmap.Config.ARGB_8888; - opts.inDither = false; - opts.outWidth = photo.w; - opts.outHeight = photo.h; - try { - if (photo.location.ext != null) { - ByteBuffer buffer = ByteBuffer.allocateDirect(photo.bytes.length); - buffer.put(photo.bytes); - image = Utilities.loadWebpImage(buffer, buffer.limit(), null); - } else { - image = BitmapFactory.decodeByteArray(photoOwner.bytes, 0, photoOwner.bytes.length, opts); - } - if (image != null) { - if (preview == 2) { - if (secret) { - Utilities.blurBitmap(image, 7); - Utilities.blurBitmap(image, 7); - Utilities.blurBitmap(image, 7); - } else { - if (photo.location.ext != null) { - Utilities.blurBitmap(image, 1); - } else { - Utilities.blurBitmap(image, 3); - } - } - } - if (ImageLoader.getInstance().runtimeHack != null) { - ImageLoader.getInstance().runtimeHack.trackFree(image.getRowBytes() * image.getHeight()); - } - } - } catch (Throwable throwable) { - FileLog.e("tmessages", throwable); - } - } - } - - public static PhotoObject getClosestImageWithSize(ArrayList arr, int side) { - if (arr == null) { - return null; - } - - int lastSide = 0; - PhotoObject closestObject = null; - for (PhotoObject obj : arr) { - if (obj == null || obj.photoOwner == null) { - continue; - } - int currentSide = obj.photoOwner.w >= obj.photoOwner.h ? obj.photoOwner.w : obj.photoOwner.h; - if (closestObject == null || closestObject.photoOwner instanceof TLRPC.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) { - closestObject = obj; - lastSide = currentSide; - } - } - return closestObject; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java index d9fd56a1..6a114024 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/android/SecretChatHelper.java @@ -478,7 +478,7 @@ public class SecretChatHelper { reqSend.action.ttl_seconds = encryptedChat.ttl; message = createServiceSecretMessage(encryptedChat, reqSend.action); - MessageObject newMsgObj = new MessageObject(message, null); + MessageObject newMsgObj = new MessageObject(message, null, false); newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; ArrayList objArr = new ArrayList<>(); objArr.add(newMsgObj); @@ -514,7 +514,7 @@ public class SecretChatHelper { reqSend.action.random_ids = random_ids; message = createServiceSecretMessage(encryptedChat, reqSend.action); - MessageObject newMsgObj = new MessageObject(message, null); + MessageObject newMsgObj = new MessageObject(message, null, false); newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; ArrayList objArr = new ArrayList<>(); objArr.add(newMsgObj); @@ -547,7 +547,7 @@ public class SecretChatHelper { arr.add(newMsg); MessagesStorage.getInstance().putMessages(arr, false, true, false, 0); - MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.photo, 3); + //MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.photo, 3); } else if (newMsg.media instanceof TLRPC.TL_messageMediaVideo && newMsg.media.video != null) { TLRPC.Video video = newMsg.media.video; newMsg.media.video = new TLRPC.TL_videoEncrypted(); @@ -578,7 +578,7 @@ public class SecretChatHelper { arr.add(newMsg); MessagesStorage.getInstance().putMessages(arr, false, true, false, 0); - MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.video, 5); + //MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.video, 5); } else if (newMsg.media instanceof TLRPC.TL_messageMediaDocument && newMsg.media.document != null) { TLRPC.Document document = newMsg.media.document; newMsg.media.document = new TLRPC.TL_documentEncrypted(); @@ -605,7 +605,7 @@ public class SecretChatHelper { arr.add(newMsg); MessagesStorage.getInstance().putMessages(arr, false, true, false, 0); - MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.document, 4); + //MessagesStorage.getInstance().putSentFile(originalPath, newMsg.media.document, 4); } else if (newMsg.media instanceof TLRPC.TL_messageMediaAudio && newMsg.media.audio != null) { TLRPC.Audio audio = newMsg.media.audio; newMsg.media.audio = new TLRPC.TL_audioEncrypted(); @@ -656,7 +656,8 @@ public class SecretChatHelper { TLObject toEncryptObject = null; if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 17) { TLRPC.TL_decryptedMessageLayer layer = new TLRPC.TL_decryptedMessageLayer(); - layer.layer = Math.min(17, AndroidUtilities.getPeerLayerVersion(chat.layer)); + int myLayer = Math.max(17, AndroidUtilities.getMyLayerVersion(chat.layer)); + layer.layer = Math.min(myLayer, AndroidUtilities.getPeerLayerVersion(chat.layer)); layer.message = req; layer.random_bytes = new byte[Math.max(1, (int) Math.ceil(Utilities.random.nextDouble() * 16))]; Utilities.random.nextBytes(layer.random_bytes); @@ -698,6 +699,7 @@ public class SecretChatHelper { toEncryptObject = req; } + int len = toEncryptObject.getObjectSize(); ByteBufferDesc toEncrypt = BuffersStorage.getInstance().getFreeBuffer(4 + len); toEncrypt.writeInt32(len); diff --git a/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java index ff92dab3..b7dc050d 100644 --- a/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/android/SendMessagesHelper.java @@ -301,7 +301,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter public void run() { if (message.documentLocation.thumb.location instanceof TLRPC.TL_fileLocationUnavailable) { try { - Bitmap bitmap = ImageLoader.loadBitmap(cacheFile.getAbsolutePath(), null, 90, 90); + Bitmap bitmap = ImageLoader.loadBitmap(cacheFile.getAbsolutePath(), null, 90, 90, true); if (bitmap != null) { message.documentLocation.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, message.sendEncryptedRequest != null); } @@ -753,7 +753,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } } - MessageObject newMsgObj = new MessageObject(newMsg, null, 2); + MessageObject newMsgObj = new MessageObject(newMsg, null, true); newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; ArrayList objArr = new ArrayList<>(); @@ -1302,7 +1302,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (isBroadcast) { for (TLRPC.Message message : sentMessages) { ArrayList arr = new ArrayList<>(); - MessageObject messageObject = new MessageObject(message, null, 0); + MessageObject messageObject = new MessageObject(message, null, false); arr.add(messageObject); MessagesController.getInstance().updateInterfaceWithMessages(messageObject.getDialogId(), arr, isBroadcast); } @@ -1369,7 +1369,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName + ".jpg"); File cacheFile2 = null; - if (sentMessage.media.photo.sizes.size() == 1 || size.w > 80 || size.h > 80) { + if (sentMessage.media.photo.sizes.size() == 1 || size.w > 90 || size.h > 90) { cacheFile2 = FileLoader.getPathToAttach(size); } else { cacheFile2 = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName2 + ".jpg"); @@ -1431,6 +1431,8 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter ImageLoader.getInstance().replaceImageInCache(fileName, fileName2); size2.location = size.location; } + } else if (MessageObject.isStickerMessage(sentMessage) && size2.location != null) { + size.location = size2.location; } newMsg.media.document.dc_id = sentMessage.media.document.dc_id; @@ -1501,7 +1503,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter MessagesController.getInstance().putChats(chats, true); MessagesController.getInstance().putEncryptedChats(encryptedChats, true); for (TLRPC.Message message : messages) { - MessageObject messageObject = new MessageObject(message, null, 0); + MessageObject messageObject = new MessageObject(message, null, false); retrySendMessage(messageObject, true); } } @@ -1509,9 +1511,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } public TLRPC.TL_photo generatePhotoSizes(String path, Uri imageUri) { - Bitmap bitmap = ImageLoader.loadBitmap(path, imageUri, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize()); + Bitmap bitmap = ImageLoader.loadBitmap(path, imageUri, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize(), true); if (bitmap == null && AndroidUtilities.getPhotoSize() != 800) { - bitmap = ImageLoader.loadBitmap(path, imageUri, 800, 800); + bitmap = ImageLoader.loadBitmap(path, imageUri, 800, 800, true); } ArrayList sizes = new ArrayList<>(); TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, true); @@ -1578,9 +1580,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter originalPath += "" + f.length(); } - TLRPC.TL_document document = (TLRPC.TL_document)MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4); - if (document == null && !path.equals(originalPath)) { - document = (TLRPC.TL_document)MessagesStorage.getInstance().getSentFile(path + f.length(), !isEncrypted ? 1 : 4); + TLRPC.TL_document document = null; + if (!isEncrypted) { + document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4); + if (document == null && !path.equals(originalPath) && !isEncrypted) { + document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(path + f.length(), !isEncrypted ? 1 : 4); + } } if (document == null) { document = new TLRPC.TL_document(); @@ -1603,7 +1608,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (document.mime_type.equals("image/gif")) { try { - Bitmap bitmap = ImageLoader.loadBitmap(f.getAbsolutePath(), null, 90, 90); + Bitmap bitmap = ImageLoader.loadBitmap(f.getAbsolutePath(), null, 90, 90, true); if (bitmap != null) { document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, isEncrypted); } @@ -1727,7 +1732,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter boolean isEncrypted = (int)dialog_id == 0; for (final MediaController.SearchImage searchImage : photos) { if (searchImage.type == 1) { - TLRPC.TL_document document = (TLRPC.TL_document)MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 1 : 4); + TLRPC.TL_document document = null; + if (!isEncrypted) { + document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 1 : 4); + } String md5 = Utilities.MD5(searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.imageUrl); File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); if (document == null) { @@ -1753,7 +1761,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } if (thumbFile != null) { try { - Bitmap bitmap = ImageLoader.loadBitmap(thumbFile.getAbsolutePath(), null, 90, 90); + Bitmap bitmap = ImageLoader.loadBitmap(thumbFile.getAbsolutePath(), null, 90, 90, true); if (bitmap != null) { document.thumb = ImageLoader.scaleAndSaveImage(bitmap, 90, 90, 55, isEncrypted); } @@ -1781,7 +1789,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter }); } else { boolean needDownloadHttp = true; - TLRPC.TL_photo photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 0 : 3); + TLRPC.TL_photo photo = null; + if (!isEncrypted) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(searchImage.imageUrl, !isEncrypted ? 0 : 3); + } if (photo == null) { String md5 = Utilities.MD5(searchImage.imageUrl) + "." + ImageLoader.getHttpUrlExtension(searchImage.imageUrl); File cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), md5); @@ -1891,9 +1902,12 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter } else { originalPath = null; } - TLRPC.TL_photo photo = (TLRPC.TL_photo)MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3); - if (photo == null && uri != null) { - photo = (TLRPC.TL_photo)MessagesStorage.getInstance().getSentFile(Utilities.getPath(uri), !isEncrypted ? 0 : 3); + TLRPC.TL_photo photo = null; + if (!isEncrypted) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 0 : 3); + if (photo == null && uri != null) { + photo = (TLRPC.TL_photo) MessagesStorage.getInstance().getSentFile(Utilities.getPath(uri), !isEncrypted ? 0 : 3); + } } if (photo == null) { photo = SendMessagesHelper.getInstance().generatePhotoSizes(path, uri); @@ -1935,7 +1949,10 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter if (videoEditedInfo != null) { originalPath += duration + "_" + videoEditedInfo.startTime + "_" + videoEditedInfo.endTime; } - TLRPC.TL_video video = (TLRPC.TL_video)MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5); + TLRPC.TL_video video = null; + if (!isEncrypted) { + video = (TLRPC.TL_video) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 2 : 5); + } if (video == null) { Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoPath, MediaStore.Video.Thumbnails.MINI_KIND); TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(thumb, 90, 90, 55, isEncrypted); diff --git a/TMessagesProj/src/main/java/org/telegram/android/query/SharedMediaQuery.java b/TMessagesProj/src/main/java/org/telegram/android/query/SharedMediaQuery.java new file mode 100644 index 00000000..aab9255e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/android/query/SharedMediaQuery.java @@ -0,0 +1,398 @@ +/* + * This is the source code of Telegram for Android v. 2.0.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2014. + */ + +package org.telegram.android.query; + +import org.telegram.SQLite.SQLiteCursor; +import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.android.AndroidUtilities; +import org.telegram.android.ImageLoader; +import org.telegram.android.MessageObject; +import org.telegram.android.MessagesController; +import org.telegram.android.MessagesStorage; +import org.telegram.android.NotificationCenter; +import org.telegram.messenger.ByteBufferDesc; +import org.telegram.messenger.ConnectionsManager; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.RPCRequest; +import org.telegram.messenger.TLClassStore; +import org.telegram.messenger.TLObject; +import org.telegram.messenger.TLRPC; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; + +public class SharedMediaQuery { + + public final static int MEDIA_PHOTOVIDEO = 0; + public final static int MEDIA_FILE = 1; + public final static int MEDIA_AUDIO = 2; + + public static void loadMedia(final long uid, final int offset, final int count, final int max_id, final int type, final boolean fromCache, final int classGuid) { + int lower_part = (int)uid; + if (fromCache || lower_part == 0) { + loadMediaDatabase(uid, offset, count, max_id, type, classGuid); + } else { + TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); + req.offset = offset; + req.limit = count; + req.max_id = max_id; + if (type == MEDIA_PHOTOVIDEO) { + req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); + } else if (type == MEDIA_FILE) { + req.filter = new TLRPC.TL_inputMessagesFilterDocument(); + } else if (type == MEDIA_AUDIO) { + req.filter = new TLRPC.TL_inputMessagesFilterAudio(); + } + req.q = ""; + if (uid < 0) { + req.peer = new TLRPC.TL_inputPeerChat(); + req.peer.chat_id = -lower_part; + } else { + TLRPC.User user = MessagesController.getInstance().getUser(lower_part); + if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) { + req.peer = new TLRPC.TL_inputPeerForeign(); + req.peer.access_hash = user.access_hash; + } else { + req.peer = new TLRPC.TL_inputPeerContact(); + } + req.peer.user_id = lower_part; + } + long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + processLoadedMedia(res, uid, offset, count, max_id, type, false, classGuid); + } + } + }); + ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid); + } + } + + public static void getMediaCount(final long uid, final int type, final int classGuid, boolean fromCache) { + int lower_part = (int)uid; + if (fromCache || lower_part == 0) { + getMediaCountDatabase(uid, type, classGuid); + } else { + TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); + req.offset = 0; + req.limit = 1; + req.max_id = 0; + if (type == MEDIA_PHOTOVIDEO) { + req.filter = new TLRPC.TL_inputMessagesFilterPhotoVideo(); + } else if (type == MEDIA_FILE) { + req.filter = new TLRPC.TL_inputMessagesFilterDocument(); + } else if (type == MEDIA_AUDIO) { + req.filter = new TLRPC.TL_inputMessagesFilterAudio(); + } + req.q = ""; + if (uid < 0) { + req.peer = new TLRPC.TL_inputPeerChat(); + req.peer.chat_id = -lower_part; + } else { + TLRPC.User user = MessagesController.getInstance().getUser(lower_part); + if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) { + req.peer = new TLRPC.TL_inputPeerForeign(); + req.peer.access_hash = user.access_hash; + } else { + req.peer = new TLRPC.TL_inputPeerContact(); + } + req.peer.user_id = lower_part; + } + long reqId = ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + final TLRPC.messages_Messages res = (TLRPC.messages_Messages) response; + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().putUsers(res.users, false); + MessagesController.getInstance().putChats(res.chats, false); + } + }); + + if (res instanceof TLRPC.TL_messages_messagesSlice) { + processLoadedMediaCount(res.count, uid, type, classGuid, false); + } else { + processLoadedMediaCount(res.messages.size(), uid, type, classGuid, false); + } + } + } + }); + ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid); + } + } + + public static int getMediaType(TLRPC.Message message) { + if (message == null) { + return -1; + } + if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) { + return SharedMediaQuery.MEDIA_PHOTOVIDEO; + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + if (MessageObject.isStickerMessage(message)) { + return -1; + } else { + return SharedMediaQuery.MEDIA_FILE; + } + } else if (message.media instanceof TLRPC.TL_messageMediaAudio) { + return SharedMediaQuery.MEDIA_AUDIO; + } + return -1; + } + + public static boolean canAddMessageToMedia(TLRPC.Message message) { + if (message instanceof TLRPC.TL_message_secret && message.media instanceof TLRPC.TL_messageMediaPhoto && message.ttl != 0 && message.ttl <= 60) { + return false; + } else if (message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo || message.media instanceof TLRPC.TL_messageMediaDocument || message.media instanceof TLRPC.TL_messageMediaAudio) { + return true; + } + return false; + } + + private static void processLoadedMedia(final TLRPC.messages_Messages res, final long uid, int offset, int count, int max_id, final int type, final boolean fromCache, final int classGuid) { + int lower_part = (int)uid; + if (fromCache && res.messages.isEmpty() && lower_part != 0) { + loadMedia(uid, offset, count, max_id, type, false, classGuid); + } else { + if (!fromCache) { + ImageLoader.saveMessagesThumbs(res.messages); + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, true, true); + putMediaDatabase(uid, type, res.messages); + } + + final HashMap usersLocal = new HashMap<>(); + for (TLRPC.User u : res.users) { + usersLocal.put(u.id, u); + } + final ArrayList objects = new ArrayList<>(); + for (TLRPC.Message message : res.messages) { + objects.add(new MessageObject(message, usersLocal, false)); + } + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + int totalCount; + if (res instanceof TLRPC.TL_messages_messagesSlice) { + totalCount = res.count; + } else { + totalCount = res.messages.size(); + } + MessagesController.getInstance().putUsers(res.users, fromCache); + MessagesController.getInstance().putChats(res.chats, fromCache); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaDidLoaded, uid, totalCount, objects, fromCache, classGuid, type); + } + }); + } + } + + private static void processLoadedMediaCount(final int count, final long uid, final int type, final int classGuid, final boolean fromCache) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + int lower_part = (int)uid; + if (fromCache && count == -1 && lower_part != 0) { + getMediaCount(uid, type, classGuid, false); + } else { + if (!fromCache) { + putMediaCountDatabase(uid, type, count); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.mediaCountDidLoaded, uid, (fromCache && count == -1 ? 0 : count), fromCache, type); + } + } + }); + } + + private static void putMediaCountDatabase(final long uid, final int type, final int count) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + SQLitePreparedStatement state2 = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO media_counts_v2 VALUES(?, ?, ?)"); + state2.requery(); + state2.bindLong(1, uid); + state2.bindInteger(2, type); + state2.bindInteger(3, count); + state2.step(); + state2.dispose(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + + private static void getMediaCountDatabase(final long uid, final int type, final int classGuid) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + int count = -1; + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT count FROM media_counts_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); + if (cursor.next()) { + count = cursor.intValue(0); + } + cursor.dispose(); + int lower_part = (int)uid; + if (count == -1 && lower_part == 0) { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM media_v2 WHERE uid = %d AND type = %d LIMIT 1", uid, type)); + if (cursor.next()) { + count = cursor.intValue(0); + } + cursor.dispose(); + + /*cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, send_state, date FROM messages WHERE uid = %d ORDER BY mid ASC LIMIT %d", uid, 1000)); + ArrayList photos = new ArrayList<>(); + ArrayList docs = new ArrayList<>(); + while (cursor.next()) { + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(1)); + if (data != null && cursor.byteBufferValue(1, data.buffer) != 0) { + TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); + MessageObject.setIsUnread(message, cursor.intValue(0) != 1); + message.date = cursor.intValue(2); + message.send_state = cursor.intValue(1); + message.dialog_id = uid; + if (message.ttl > 60 && message.media instanceof TLRPC.TL_messageMediaPhoto || message.media instanceof TLRPC.TL_messageMediaVideo) { + photos.add(message); + } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { + docs.add(message); + } + } + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + } + cursor.dispose(); + if (!photos.isEmpty() || !docs.isEmpty()) { + MessagesStorage.getInstance().getDatabase().beginTransaction(); + if (!photos.isEmpty()) { + putMediaDatabaseInternal(uid, MEDIA_PHOTOVIDEO, photos); + } + if (docs.isEmpty()) { + putMediaDatabaseInternal(uid, MEDIA_FILE, docs); + } + MessagesStorage.getInstance().getDatabase().commitTransaction(); + }*/ + + if (count != -1) { + putMediaCountDatabase(uid, type, count); + } + } + processLoadedMediaCount(count, uid, type, classGuid, true); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + }); + } + + private static void loadMediaDatabase(final long uid, final int offset, final int count, final int max_id, final int type, final int classGuid) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + TLRPC.TL_messages_messages res = new TLRPC.TL_messages_messages(); + try { + ArrayList loadedUsers = new ArrayList<>(); + ArrayList fromUser = new ArrayList<>(); + + SQLiteCursor cursor; + + if ((int)uid != 0) { + if (max_id != 0) { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND mid < %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d", uid, max_id, type, count)); + } else { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, mid FROM media_v2 WHERE uid = %d AND type = %d ORDER BY date DESC, mid DESC LIMIT %d,%d", uid, type, offset, count)); + } + } else { + if (max_id != 0) { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d AND type = %d ORDER BY m.mid ASC LIMIT %d", uid, max_id, type, count)); + } else { + cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT m.data, m.mid, r.random_id FROM media_v2 as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND type = %d ORDER BY m.mid ASC LIMIT %d,%d", uid, type, offset, count)); + } + } + + while (cursor.next()) { + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { + TLRPC.Message message = (TLRPC.Message) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); + message.id = cursor.intValue(1); + message.dialog_id = uid; + if ((int)uid == 0) { + message.random_id = cursor.longValue(2); + } + res.messages.add(message); + fromUser.add(message.from_id); + } + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + } + cursor.dispose(); + + StringBuilder usersToLoad = new StringBuilder(); + for (int uid : fromUser) { + if (!loadedUsers.contains(uid)) { + if (usersToLoad.length() != 0) { + usersToLoad.append(","); + } + usersToLoad.append(uid); + loadedUsers.add(uid); + } + } + if (usersToLoad.length() != 0) { + MessagesStorage.getInstance().getUsersInternal(usersToLoad.toString(), res.users); + } + } catch (Exception e) { + res.messages.clear(); + res.chats.clear(); + res.users.clear(); + FileLog.e("tmessages", e); + } finally { + processLoadedMedia(res, uid, offset, count, max_id, type, true, classGuid); + } + } + }); + } + + private static void putMediaDatabaseInternal(final long uid, final int type, final ArrayList messages) { + try { + MessagesStorage.getInstance().getDatabase().beginTransaction(); + SQLitePreparedStatement state2 = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); + for (TLRPC.Message message : messages) { + if (canAddMessageToMedia(message)) { + state2.requery(); + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(message.getObjectSize()); + message.serializeToStream(data); + state2.bindInteger(1, message.id); + state2.bindLong(2, uid); + state2.bindInteger(3, message.date); + state2.bindInteger(4, type); + state2.bindByteBuffer(5, data.buffer); + state2.step(); + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + } + } + state2.dispose(); + MessagesStorage.getInstance().getDatabase().commitTransaction(); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + } + + private static void putMediaDatabase(final long uid, final int type, final ArrayList messages) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + putMediaDatabaseInternal(uid, type, messages); + } + }); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java index b3c50750..bc3dd299 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ConnectionsManager.java @@ -14,6 +14,7 @@ import android.content.pm.PackageInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; +import android.os.PowerManager; import android.util.Base64; import org.telegram.android.AndroidUtilities; @@ -81,6 +82,8 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. private volatile long nextCallToken = 1; + private PowerManager.WakeLock wakeLock = null; + private static volatile ConnectionsManager Instance = null; public static ConnectionsManager getInstance() { ConnectionsManager localInstance = Instance; @@ -213,6 +216,14 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. } Utilities.stageQueue.postRunnable(stageRunnable, 1000); + + try { + PowerManager pm = (PowerManager)ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock"); + wakeLock.setReferenceCounted(false); + } catch (Exception e) { + FileLog.e("tmessages", e); + } } public int getConnectionState() { @@ -456,7 +467,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. } else { Datacenter datacenter = new Datacenter(); datacenter.datacenterId = 1; - datacenter.addAddressAndPort("173.240.5.253", 443); + datacenter.addAddressAndPort("149.154.175.10", 443); datacenters.put(datacenter.datacenterId, datacenter); datacenter = new Datacenter(); @@ -566,25 +577,33 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - while (requestQueue.size() != 0) { - RPCRequest request = requestQueue.get(0); - requestQueue.remove(0); + for (int a = 0; a < requestQueue.size(); a++) { + RPCRequest request = requestQueue.get(a); + if ((request.flags & RPCRequest.RPCRequestClassWithoutLogin) != 0) { + continue; + } + requestQueue.remove(a); if (request.completionBlock != null) { TLRPC.TL_error implicitError = new TLRPC.TL_error(); implicitError.code = -1000; implicitError.text = ""; request.completionBlock.run(null, implicitError); } + a--; } - while (runningRequests.size() != 0) { - RPCRequest request = runningRequests.get(0); - runningRequests.remove(0); + for (int a = 0; a < runningRequests.size(); a++) { + RPCRequest request = runningRequests.get(a); + if ((request.flags & RPCRequest.RPCRequestClassWithoutLogin) != 0) { + continue; + } + runningRequests.remove(a); if (request.completionBlock != null) { TLRPC.TL_error implicitError = new TLRPC.TL_error(); implicitError.code = -1000; implicitError.text = ""; request.completionBlock.run(null, implicitError); } + a--; } pingIdToDate.clear(); quickAckIdToRequestIds.clear(); @@ -2140,6 +2159,15 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. } else { if (resultContainer.result instanceof TLRPC.updates_Difference) { pushMessagesReceived = true; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (wakeLock.isHeld()) { + FileLog.e("tmessages", "release wakelock"); + wakeLock.release(); + } + } + }); } if (request.rawRequest instanceof TLRPC.TL_auth_checkPassword) { UserConfig.setWaitingForPasswordEnter(false); @@ -2337,9 +2365,25 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. if (paused) { pushMessagesReceived = false; } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + FileLog.e("tmessages", "acquire wakelock"); + wakeLock.acquire(20000); + } + }); resumeNetworkInternal(); } else { pushMessagesReceived = true; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (wakeLock.isHeld()) { + FileLog.e("tmessages", "release wakelock"); + wakeLock.release(); + } + } + }); MessagesController.getInstance().processUpdates((TLRPC.Updates) message, false); } } else { @@ -2444,7 +2488,25 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. } }); } else if ((connection.transportRequestClass & RPCRequest.RPCRequestClassPush) != 0) { - FileLog.e("tmessages", "call connection closed"); + FileLog.e("tmessages", "push connection closed"); + if (BuildVars.DEBUG_VERSION) { + try { + ConnectivityManager cm = (ConnectivityManager)ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo[] networkInfos = cm.getAllNetworkInfo(); + for (int a = 0; a < 2; a++) { + if (a >= networkInfos.length) { + break; + } + NetworkInfo info = networkInfos[a]; + FileLog.e("tmessages", "Network: " + info.getTypeName() + " status: " + info.getState() + " info: " + info.getExtraInfo() + " object: " + info.getDetailedState() + " other: " + info); + } + if (networkInfos.length == 0) { + FileLog.e("tmessages", "no network available"); + } + } catch (Exception e) { + FileLog.e("tmessages", "NETWORK STATE GET ERROR", e); + } + } sendingPushPing = false; lastPushPingTime = System.currentTimeMillis() - 60000 * 3 + 4000; } @@ -2456,7 +2518,10 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. if (datacenter.authKey != null) { if ((connection.transportRequestClass & RPCRequest.RPCRequestClassPush) != 0) { sendingPushPing = false; - lastPushPingTime = System.currentTimeMillis() - 60000 * 3 + 4000; + //lastPushPingTime = System.currentTimeMillis() - 60000 * 3 + 4000; //TODO check this + //FileLog.e("tmessages", "schedule push ping in 4 seconds"); + lastPushPingTime = System.currentTimeMillis(); + generatePing(datacenter, true); } else { if (paused && lastPauseTime != 0) { lastPauseTime = System.currentTimeMillis(); @@ -2541,6 +2606,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. } else { if (datacenter.authKeyId == 0 || keyId != datacenter.authKeyId) { FileLog.e("tmessages", "Error: invalid auth key id " + connection); + datacenter.switchTo443Port(); connection.suspendConnection(true); connection.connect(); return; @@ -2581,6 +2647,7 @@ public class ConnectionsManager implements Action.ActionDelegate, TcpConnection. if (!Utilities.arraysEquals(messageKey, 0, realMessageKeyFull, realMessageKeyFull.length - 16)) { FileLog.e("tmessages", "***** Error: invalid message key"); + datacenter.switchTo443Port(); connection.suspendConnection(true); connection.connect(); return; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java index b37088d3..61c63d2a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Datacenter.java @@ -115,6 +115,16 @@ public class Datacenter { readCurrentAddressAndPortNum(); } + public void switchTo443Port() { + for (int a = 0; a < addresses.size(); a++) { + if (ports.get(addresses.get(a)) == 443) { + currentAddressNum = a; + currentPortNum = 0; + break; + } + } + } + public String getCurrentAddress() { if (addresses.isEmpty()) { return null; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java index c3200a7f..462098cc 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java @@ -55,7 +55,7 @@ public class FileLoadOperation { private boolean isForceRequest = false; public static interface FileLoadOperationDelegate { - public abstract void didFinishLoadingFile(FileLoadOperation operation, File finalFile, File tempFile); + public abstract void didFinishLoadingFile(FileLoadOperation operation, File finalFile); public abstract void didFailedLoadingFile(FileLoadOperation operation, int state); public abstract void didChangedLoadProgress(FileLoadOperation operation, float progress); } @@ -341,9 +341,11 @@ public class FileLoadOperation { cacheIvTemp.delete(); } if (cacheFileTemp != null) { - cacheFileTemp.renameTo(cacheFileFinal); + if (!cacheFileTemp.renameTo(cacheFileFinal)) { + cacheFileFinal = cacheFileTemp; + } } - delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal, cacheFileTemp); + delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal); } private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) { @@ -373,7 +375,8 @@ public class FileLoadOperation { fiv.seek(0); fiv.write(iv); } - downloadedBytes += requestInfo.response.bytes.limit(); + int currentBytesSize = requestInfo.response.bytes.limit(); + downloadedBytes += currentBytesSize; if (totalBytesCount > 0 && state == stateDownloading) { delegate.didChangedLoadProgress(FileLoadOperation.this, Math.min(1.0f, (float)downloadedBytes / (float)totalBytesCount)); } @@ -390,10 +393,14 @@ public class FileLoadOperation { } } - if (totalBytesCount != downloadedBytes && downloadedBytes % downloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) { - startDownloadRequest(); - } else { + if (currentBytesSize != downloadChunkSize) { onFinishLoadingFile(); + } else { + if (totalBytesCount != downloadedBytes && downloadedBytes % downloadChunkSize == 0 || totalBytesCount > 0 && totalBytesCount > downloadedBytes) { + startDownloadRequest(); + } else { + onFinishLoadingFile(); + } } } catch (Exception e) { cleanup(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java index 48f5dda0..b0e1ac4e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java @@ -23,7 +23,7 @@ public class FileLoader { public abstract void fileUploadProgressChanged(String location, float progress, boolean isEncrypted); public abstract void fileDidUploaded(String location, TLRPC.InputFile inputFile, TLRPC.InputEncryptedFile inputEncryptedFile); public abstract void fileDidFailedUpload(String location, boolean isEncrypted); - public abstract void fileDidLoaded(String location, File finalFile, File tempFile); + public abstract void fileDidLoaded(String location, File finalFile, int type); public abstract void fileDidFailedLoad(String location, int state); public abstract void fileLoadProgressChanged(String location, float progress); } @@ -395,53 +395,50 @@ public class FileLoader { File tempDir = getDirectory(MEDIA_DIR_CACHE); File storeDir = tempDir; + int type = MEDIA_DIR_CACHE; if (video != null) { operation = new FileLoadOperation(video); - if (!cacheOnly) { - storeDir = getDirectory(MEDIA_DIR_VIDEO); - } + type = MEDIA_DIR_VIDEO; } else if (location != null) { operation = new FileLoadOperation(location, locationSize); - if (!cacheOnly) { - storeDir = getDirectory(MEDIA_DIR_IMAGE); - } + type = MEDIA_DIR_IMAGE; } else if (document != null) { operation = new FileLoadOperation(document); - if (!cacheOnly) { - storeDir = getDirectory(MEDIA_DIR_DOCUMENT); - } + type = MEDIA_DIR_DOCUMENT; } else if (audio != null) { operation = new FileLoadOperation(audio); - if (!cacheOnly) { - storeDir = getDirectory(MEDIA_DIR_AUDIO); - } + type = MEDIA_DIR_AUDIO; + } + if (!cacheOnly) { + storeDir = getDirectory(type); } operation.setPaths(storeDir, tempDir); - final String arg1 = fileName; + final String finalFileName = fileName; + final int finalType = type; loadOperationPaths.put(fileName, operation); operation.setDelegate(new FileLoadOperation.FileLoadOperationDelegate() { @Override - public void didFinishLoadingFile(FileLoadOperation operation, File finalFile, File tempFile) { + public void didFinishLoadingFile(FileLoadOperation operation, File finalFile) { if (delegate != null) { - delegate.fileDidLoaded(arg1, finalFile, tempFile); + delegate.fileDidLoaded(finalFileName, finalFile, finalType); } - checkDownloadQueue(audio, location, arg1); + checkDownloadQueue(audio, location, finalFileName); } @Override public void didFailedLoadingFile(FileLoadOperation operation, int canceled) { - checkDownloadQueue(audio, location, arg1); + checkDownloadQueue(audio, location, finalFileName); if (delegate != null) { - delegate.fileDidFailedLoad(arg1, canceled); + delegate.fileDidFailedLoad(finalFileName, canceled); } } @Override public void didChangedLoadProgress(FileLoadOperation operation, float progress) { if (delegate != null) { - delegate.fileLoadProgressChanged(arg1, progress); + delegate.fileLoadProgressChanged(finalFileName, progress); } } }); @@ -641,7 +638,7 @@ public class FileLoader { continue; } int currentSide = obj.w >= obj.h ? obj.w : obj.h; - if (closestObject == null || closestObject instanceof TLRPC.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) { + if (closestObject == null || obj instanceof TLRPC.TL_photoCachedSize || currentSide <= side && lastSide < currentSide) { closestObject = obj; lastSide = currentSide; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java index ceee714f..3d01798f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java @@ -160,6 +160,26 @@ public class FileLog { } } + public static void w(final String tag, final String message) { + if (!BuildVars.DEBUG_VERSION) { + return; + } + Log.w(tag, message); + if (getInstance().streamWriter != null) { + getInstance().logQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " W/" + tag + ": " + message + "\n"); + getInstance().streamWriter.flush(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + public static void cleanupLogs() { ArrayList uris = new ArrayList(); File sdCard = ApplicationLoader.applicationContext.getExternalFilesDir(null); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java b/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java index a2afb811..406c0513 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/TcpConnection.java @@ -299,6 +299,9 @@ public class TcpConnection extends ConnectionContext { if (canReuse) { BuffersStorage.getInstance().reuseFreeBuffer(buff); } + if (BuildConfig.DEBUG) { + FileLog.e("tmessages", TcpConnection.this + " disconnected, don't send data"); + } return; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java index 629942b4..188fdf76 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java @@ -48,6 +48,7 @@ public class ActionBar extends FrameLayout { private boolean allowOverlayTitle; private CharSequence lastTitle; private boolean showingOverlayTitle; + private boolean castShadows = true; protected boolean isSearchFieldVisible; protected int itemsBackgroundResourceId; @@ -485,6 +486,14 @@ public class ActionBar extends FrameLayout { } } + public void setCastShadows(boolean value) { + castShadows = value; + } + + public boolean getCastShadows() { + return castShadows; + } + @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java index c8c6658d..bf295a53 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java @@ -68,8 +68,10 @@ public class ActionBarLayout extends FrameLayout { continue; } if (view instanceof ActionBar && view.getVisibility() == VISIBLE) { - actionBarHeight = view.getMeasuredHeight(); - wasActionBar = true; + if (((ActionBar) view).getCastShadows()) { + actionBarHeight = view.getMeasuredHeight(); + wasActionBar = true; + } break; } } @@ -597,7 +599,7 @@ public class ActionBarLayout extends FrameLayout { if (useAlphaAnimations && fragmentsStack.size() == 1) { presentFragmentInternalRemoveOld(removeLast, currentFragment); - ArrayList animators = new ArrayList(); + ArrayList animators = new ArrayList<>(); animators.add(ObjectAnimatorProxy.ofFloat(this, "alpha", 0.0f, 1.0f)); if (backgroundView != null) { backgroundView.setVisibility(VISIBLE); @@ -783,7 +785,7 @@ public class ActionBarLayout extends FrameLayout { } }; - ArrayList animators = new ArrayList(); + ArrayList animators = new ArrayList<>(); animators.add(ObjectAnimatorProxy.ofFloat(this, "alpha", 1.0f, 0.0f)); if (backgroundView != null) { animators.add(ObjectAnimatorProxy.ofFloat(backgroundView, "alpha", 1.0f, 0.0f)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java index 7b9d202a..188804be 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java @@ -14,7 +14,6 @@ import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.LinearLayout; import org.telegram.android.AndroidUtilities; @@ -78,11 +77,10 @@ public class ActionBarMenu extends LinearLayout { public ActionBarMenuItem addItem(int id, int icon, int backgroundResource, Drawable drawable, int width) { ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundResource); menuItem.setTag(id); - menuItem.setScaleType(ImageView.ScaleType.CENTER); if (drawable != null) { - menuItem.setImageDrawable(drawable); + menuItem.iconView.setImageDrawable(drawable); } else { - menuItem.setImageResource(icon); + menuItem.iconView.setImageResource(icon); } addView(menuItem); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)menuItem.getLayoutParams(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index da715be0..96650548 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -34,10 +34,11 @@ import org.telegram.android.AndroidUtilities; import org.telegram.android.LocaleController; import org.telegram.messenger.R; import org.telegram.ui.AnimationCompat.ViewProxy; +import org.telegram.ui.Components.FrameLayoutFixed; import java.lang.reflect.Field; -public class ActionBarMenuItem extends ImageView { +public class ActionBarMenuItem extends FrameLayoutFixed { public static class ActionBarMenuItemSearchListener { public void onSearchExpand() { } @@ -51,6 +52,7 @@ public class ActionBarMenuItem extends ImageView { private ActionBarPopupWindow popupWindow; private EditText searchField; private ImageView clearButton; + protected ImageView iconView; private FrameLayout searchContainer; private boolean isSearchField = false; private ActionBarMenuItemSearchListener listener; @@ -61,11 +63,20 @@ public class ActionBarMenuItem extends ImageView { private boolean showFromBottom; private int menuHeight = AndroidUtilities.dp(16); private boolean needOffset = Build.VERSION.SDK_INT >= 21; + private int subMenuOpenSide = 0; public ActionBarMenuItem(Context context, ActionBarMenu menu, int background) { super(context); setBackgroundResource(background); parentMenu = menu; + + iconView = new ImageView(context); + iconView.setScaleType(ImageView.ScaleType.CENTER); + addView(iconView); + LayoutParams layoutParams = (LayoutParams) iconView.getLayoutParams(); + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = LayoutParams.MATCH_PARENT; + iconView.setLayoutParams(layoutParams); } @Override @@ -145,6 +156,10 @@ public class ActionBarMenuItem extends ImageView { needOffset = Build.VERSION.SDK_INT >= 21 && value; } + public void setSubMenuOpenSide(int side) { + subMenuOpenSide = side; + } + public TextView addSubItem(int id, String text, int icon) { if (popupLayout == null) { rect = new Rect(); @@ -256,18 +271,27 @@ public class ActionBarMenuItem extends ImageView { } popupWindow.setFocusable(true); if (popupLayout.getMeasuredWidth() == 0) { - if (showFromBottom) { - popupWindow.showAsDropDown(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY()); - popupWindow.update(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY(), -1, -1); + if (subMenuOpenSide == 0) { + if (showFromBottom) { + popupWindow.showAsDropDown(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY()); + popupWindow.update(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY(), -1, -1); + } else { + popupWindow.showAsDropDown(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY()); + popupWindow.update(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY(), -1, -1); + } } else { - popupWindow.showAsDropDown(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY()); - popupWindow.update(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY(), -1, -1); + popupWindow.showAsDropDown(this, -AndroidUtilities.dp(8), getOffsetY()); + popupWindow.update(this, -AndroidUtilities.dp(8), getOffsetY(), -1, -1); } } else { - if (showFromBottom) { - popupWindow.showAsDropDown(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY()); + if (subMenuOpenSide == 0) { + if (showFromBottom) { + popupWindow.showAsDropDown(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY()); + } else { + popupWindow.showAsDropDown(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY()); + } } else { - popupWindow.showAsDropDown(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY()); + popupWindow.showAsDropDown(this, -AndroidUtilities.dp(8), getOffsetY()); } } } @@ -427,7 +451,7 @@ public class ActionBarMenuItem extends ImageView { clearButton = new ImageView(getContext()); clearButton.setImageResource(R.drawable.ic_close_white); - clearButton.setScaleType(ScaleType.CENTER); + clearButton.setScaleType(ImageView.ScaleType.CENTER); clearButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -459,10 +483,14 @@ public class ActionBarMenuItem extends ImageView { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (popupWindow != null && popupWindow.isShowing()) { - if (showFromBottom) { - popupWindow.update(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY(), -1, -1); + if (subMenuOpenSide == 0) { + if (showFromBottom) { + popupWindow.update(this, -popupLayout.getMeasuredWidth() + getMeasuredWidth(), getOffsetY(), -1, -1); + } else { + popupWindow.update(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY(), -1, -1); + } } else { - popupWindow.update(this, parentMenu.parentActionBar.getMeasuredWidth() - popupLayout.getMeasuredWidth() - getLeft() - parentMenu.getLeft(), getOffsetY(), -1, -1); + popupWindow.update(this, -AndroidUtilities.dp(8), getOffsetY(), -1, -1); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsSearchAdapter.java index 82ec545f..e68e9be5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsSearchAdapter.java @@ -99,12 +99,21 @@ public class ContactsSearchAdapter extends BaseContactsSearchAdapter { Utilities.searchQueue.postRunnable(new Runnable() { @Override public void run() { - String q = query.trim().toLowerCase(); - if (q.length() == 0) { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { updateSearchResults(new ArrayList(), new ArrayList()); return; } - long time = System.currentTimeMillis(); + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String search[] = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; + } + ArrayList resultArray = new ArrayList<>(); ArrayList resultArrayNames = new ArrayList<>(); @@ -117,19 +126,22 @@ public class ContactsSearchAdapter extends BaseContactsSearchAdapter { String name = ContactsController.formatName(user.first_name, user.last_name).toLowerCase(); int found = 0; - if (name.startsWith(q) || name.contains(" " + q)) { - found = 1; - } else if (user.username != null && user.username.startsWith(q)) { - found = 2; - } - - if (found != 0) { - if (found == 1) { - resultArrayNames.add(Utilities.generateSearchName(user.first_name, user.last_name, q)); - } else { - resultArrayNames.add(Utilities.generateSearchName("@" + user.username, null, "@" + q)); + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q)) { + found = 1; + } else if (user.username != null && user.username.startsWith(q)) { + found = 2; + } + + if (found != 0) { + if (found == 1) { + resultArrayNames.add(Utilities.generateSearchName(user.first_name, user.last_name, q)); + } else { + resultArrayNames.add(Utilities.generateSearchName("@" + user.username, null, "@" + q)); + } + resultArray.add(user); + break; } - resultArray.add(user); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java index b48dc508..6031ed21 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -112,7 +112,7 @@ public class DialogsAdapter extends BaseFragmentAdapter { } } MessageObject message = MessagesController.getInstance().dialogMessage.get(dialog.top_message); - ((DialogCell) view).setDialog(dialog.id, message, true, dialog.last_message_date, dialog.unread_count); + ((DialogCell) view).setDialog(dialog.id, message, true, dialog.last_message_date, dialog.unread_count, MessagesController.getInstance().isDialogMuted(dialog.id)); } return view; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index 225f89a3..2424062c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -134,7 +134,7 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { searchResultMessages.clear(); } for (TLRPC.Message message : res.messages) { - searchResultMessages.add(new MessageObject(message, null, 0)); + searchResultMessages.add(new MessageObject(message, null, false)); } messagesSearchEndReached = res.messages.size() != 20; notifyDataSetChanged(); @@ -155,12 +155,21 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { @Override public void run() { try { - String q = query.trim().toLowerCase(); - if (q.length() == 0) { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { lastSearchId = -1; updateSearchResults(new ArrayList(), new ArrayList(), new ArrayList(), lastSearchId); return; } + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String search[] = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; + } ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); @@ -212,30 +221,33 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { username = name.substring(usernamePos + 3); } int found = 0; - if (name.startsWith(q) || name.contains(" " + q)) { - found = 1; - } else if (username != null && username.startsWith(q)) { - found = 2; - } - if (found != 0) { - ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { - TLRPC.User user = (TLRPC.User) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); - if (user.id != UserConfig.getClientUserId()) { - DialogSearchResult dialogSearchResult = dialogsResult.get((long)user.id); - if (user.status != null) { - user.status.expires = cursor.intValue(1); - } - if (found == 1) { - dialogSearchResult.name = Utilities.generateSearchName(user.first_name, user.last_name, q); - } else { - dialogSearchResult.name = Utilities.generateSearchName("@" + user.username, null, "@" + q); - } - dialogSearchResult.object = user; - resultCount++; - } + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q)) { + found = 1; + } else if (username != null && username.startsWith(q)) { + found = 2; + } + if (found != 0) { + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { + TLRPC.User user = (TLRPC.User) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); + if (user.id != UserConfig.getClientUserId()) { + DialogSearchResult dialogSearchResult = dialogsResult.get((long) user.id); + if (user.status != null) { + user.status.expires = cursor.intValue(1); + } + if (found == 1) { + dialogSearchResult.name = Utilities.generateSearchName(user.first_name, user.last_name, q); + } else { + dialogSearchResult.name = Utilities.generateSearchName("@" + user.username, null, "@" + q); + } + dialogSearchResult.object = user; + resultCount++; + } + } + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + break; } - MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); } } cursor.dispose(); @@ -245,23 +257,25 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { cursor = MessagesStorage.getInstance().getDatabase().queryFinalized(String.format(Locale.US, "SELECT data, name FROM chats WHERE uid IN(%s)", TextUtils.join(",", chatsToLoad))); while (cursor.next()) { String name = cursor.stringValue(1); - String[] args = name.split(" "); - if (name.startsWith(q) || name.contains(" " + q)) { - ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { - TLRPC.Chat chat = (TLRPC.Chat) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); - long dialog_id; - if (chat.id > 0) { - dialog_id = -chat.id; - } else { - dialog_id = AndroidUtilities.makeBroadcastId(chat.id); + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q)) { + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { + TLRPC.Chat chat = (TLRPC.Chat) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); + long dialog_id; + if (chat.id > 0) { + dialog_id = -chat.id; + } else { + dialog_id = AndroidUtilities.makeBroadcastId(chat.id); + } + DialogSearchResult dialogSearchResult = dialogsResult.get(dialog_id); + dialogSearchResult.name = Utilities.generateSearchName(chat.title, null, q); + dialogSearchResult.object = chat; + resultCount++; } - DialogSearchResult dialogSearchResult = dialogsResult.get(dialog_id); - dialogSearchResult.name = Utilities.generateSearchName(chat.title, null, q); - dialogSearchResult.object = chat; - resultCount++; + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + break; } - MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); } } cursor.dispose(); @@ -278,50 +292,53 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { username = name.substring(usernamePos + 2); } int found = 0; - if (name.startsWith(q) || name.contains(" " + q)) { - found = 1; - } else if (username != null && username.startsWith(q)) { - found = 2; - } - - if (found != 0) { - ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); - ByteBufferDesc data2 = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(6)); - if (data != null && cursor.byteBufferValue(0, data.buffer) != 0 && cursor.byteBufferValue(6, data2.buffer) != 0) { - TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); - DialogSearchResult dialogSearchResult = dialogsResult.get((long)chat.id << 32); - - chat.user_id = cursor.intValue(2); - chat.a_or_b = cursor.byteArrayValue(3); - chat.auth_key = cursor.byteArrayValue(4); - chat.ttl = cursor.intValue(5); - chat.layer = cursor.intValue(8); - chat.seq_in = cursor.intValue(9); - chat.seq_out = cursor.intValue(10); - int use_count = cursor.intValue(11); - chat.key_use_count_in = (short)(use_count >> 16); - chat.key_use_count_out = (short)(use_count); - chat.exchange_id = cursor.longValue(12); - chat.key_create_date = cursor.intValue(13); - chat.future_key_fingerprint = cursor.longValue(14); - chat.future_auth_key = cursor.byteArrayValue(15); - chat.key_hash = cursor.byteArrayValue(16); - - TLRPC.User user = (TLRPC.User)TLClassStore.Instance().TLdeserialize(data2, data2.readInt32()); - if (user.status != null) { - user.status.expires = cursor.intValue(7); - } - if (found == 1) { - dialogSearchResult.name = Html.fromHtml("" + ContactsController.formatName(user.first_name, user.last_name) + ""); - } else { - dialogSearchResult.name = Utilities.generateSearchName("@" + user.username, null, "@" + q); - } - dialogSearchResult.object = chat; - encUsers.add(user); - resultCount++; + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q)) { + found = 1; + } else if (username != null && username.startsWith(q)) { + found = 2; + } + + if (found != 0) { + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); + ByteBufferDesc data2 = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(6)); + if (data != null && cursor.byteBufferValue(0, data.buffer) != 0 && cursor.byteBufferValue(6, data2.buffer) != 0) { + TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); + DialogSearchResult dialogSearchResult = dialogsResult.get((long) chat.id << 32); + + chat.user_id = cursor.intValue(2); + chat.a_or_b = cursor.byteArrayValue(3); + chat.auth_key = cursor.byteArrayValue(4); + chat.ttl = cursor.intValue(5); + chat.layer = cursor.intValue(8); + chat.seq_in = cursor.intValue(9); + chat.seq_out = cursor.intValue(10); + int use_count = cursor.intValue(11); + chat.key_use_count_in = (short) (use_count >> 16); + chat.key_use_count_out = (short) (use_count); + chat.exchange_id = cursor.longValue(12); + chat.key_create_date = cursor.intValue(13); + chat.future_key_fingerprint = cursor.longValue(14); + chat.future_auth_key = cursor.byteArrayValue(15); + chat.key_hash = cursor.byteArrayValue(16); + + TLRPC.User user = (TLRPC.User) TLClassStore.Instance().TLdeserialize(data2, data2.readInt32()); + if (user.status != null) { + user.status.expires = cursor.intValue(7); + } + if (found == 1) { + dialogSearchResult.name = Html.fromHtml("" + ContactsController.formatName(user.first_name, user.last_name) + ""); + } else { + dialogSearchResult.name = Utilities.generateSearchName("@" + user.username, null, "@" + q); + } + dialogSearchResult.object = chat; + encUsers.add(user); + resultCount++; + } + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data2); + break; } - MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); - MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data2); } } cursor.dispose(); @@ -367,28 +384,31 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { username = name.substring(usernamePos + 3); } int found = 0; - if (name.startsWith(q) || name.contains(" " + q)) { - found = 1; - } else if (username != null && username.startsWith(q)) { - found = 2; - } - if (found != 0) { - ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); - if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { - TLRPC.User user = (TLRPC.User) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); - if (user.id != UserConfig.getClientUserId()) { - if (user.status != null) { - user.status.expires = cursor.intValue(1); - } - if (found == 1) { - resultArrayNames.add(Utilities.generateSearchName(user.first_name, user.last_name, q)); - } else { - resultArrayNames.add(Utilities.generateSearchName("@" + user.username, null, "@" + q)); - } - resultArray.add(user); - } + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q)) { + found = 1; + } else if (username != null && username.startsWith(q)) { + found = 2; + } + if (found != 0) { + ByteBufferDesc data = MessagesStorage.getInstance().getBuffersStorage().getFreeBuffer(cursor.byteArrayLength(0)); + if (data != null && cursor.byteBufferValue(0, data.buffer) != 0) { + TLRPC.User user = (TLRPC.User) TLClassStore.Instance().TLdeserialize(data, data.readInt32()); + if (user.id != UserConfig.getClientUserId()) { + if (user.status != null) { + user.status.expires = cursor.intValue(1); + } + if (found == 1) { + resultArrayNames.add(Utilities.generateSearchName(user.first_name, user.last_name, q)); + } else { + resultArrayNames.add(Utilities.generateSearchName("@" + user.username, null, "@" + q)); + } + resultArray.add(user); + } + } + MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); + break; } - MessagesStorage.getInstance().getBuffersStorage().reuseFreeBuffer(data); } } cursor.dispose(); @@ -598,7 +618,7 @@ public class DialogsSearchAdapter extends BaseContactsSearchAdapter { } ((DialogCell) view).useSeparator = (i != getCount() - 1); MessageObject messageObject = (MessageObject)getItem(i); - ((DialogCell) view).setDialog(messageObject.getDialogId(), messageObject, false, messageObject.messageOwner.date, 0); + ((DialogCell) view).setDialog(messageObject.getDialogId(), messageObject, false, messageObject.messageOwner.date, 0, false); } else if (type == 3) { if (view == null) { view = new LoadingCell(mContext); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java index c0764d16..f07555cf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java @@ -25,7 +25,7 @@ import org.telegram.android.AndroidUtilities; import org.telegram.android.ImageReceiver; import org.telegram.android.MessageObject; import org.telegram.android.MessagesController; -import org.telegram.android.PhotoObject; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.TLRPC; @@ -105,13 +105,9 @@ public class ChatActionCell extends BaseCell { if (currentMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { imageReceiver.setImage(currentMessageObject.messageOwner.action.newUserPhoto.photo_small, "50_50", avatarDrawable, false); } else { - PhotoObject photo = PhotoObject.getClosestImageWithSize(currentMessageObject.photoThumbs, AndroidUtilities.dp(64)); + TLRPC.PhotoSize photo = FileLoader.getClosestPhotoSizeWithSize(currentMessageObject.photoThumbs, AndroidUtilities.dp(64)); if (photo != null) { - if (photo.image != null) { - imageReceiver.setImageBitmap(photo.image); - } else { - imageReceiver.setImage(photo.photoOwner.location, "50_50", avatarDrawable, false); - } + imageReceiver.setImage(photo.location, "50_50", avatarDrawable, false); } else { imageReceiver.setImageBitmap(avatarDrawable); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java index a128d341..87825a1c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMediaCell.java @@ -33,7 +33,6 @@ import org.telegram.messenger.R; import org.telegram.messenger.TLRPC; import org.telegram.messenger.Utilities; import org.telegram.android.MessageObject; -import org.telegram.android.PhotoObject; import org.telegram.ui.Components.RadialProgress; import org.telegram.ui.PhotoViewer; import org.telegram.ui.Components.GifDrawable; @@ -67,8 +66,8 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD private int photoWidth; private int photoHeight; - private PhotoObject currentPhotoObject; - private PhotoObject currentPhotoObjectThumb; + private TLRPC.PhotoSize currentPhotoObject; + private TLRPC.PhotoSize currentPhotoObjectThumb; private String currentUrl; private String currentPhotoFilter; private ImageReceiver photoImage; @@ -339,11 +338,7 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD cancelLoading = false; radialProgress.setProgress(0, false); if (currentMessageObject.type == 1) { - if (currentMessageObject.imagePreview != null) { - photoImage.setImage(currentPhotoObject.photoOwner.location, currentPhotoFilter, new BitmapDrawable(currentMessageObject.imagePreview), currentPhotoObject.photoOwner.size, false); - } else { - photoImage.setImage(currentPhotoObject.photoOwner.location, currentPhotoFilter, null, currentPhotoObject.photoOwner.size, false); - } + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, currentPhotoObject.size, false); } else if (currentMessageObject.type == 8 || currentMessageObject.type == 9) { FileLoader.getInstance().loadFile(currentMessageObject.messageOwner.media.document, true, false); lastDownloadedGifMessage = currentMessageObject; @@ -361,7 +356,7 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD } else { cancelLoading = true; if (currentMessageObject.type == 1) { - ImageLoader.getInstance().cancelLoadingForImageView(photoImage); + photoImage.cancelLoadImage(); } else if (currentMessageObject.type == 8 || currentMessageObject.type == 9) { FileLoader.getInstance().cancelLoadFile(currentMessageObject.messageOwner.media.document); if (lastDownloadedGifMessage != null && lastDownloadedGifMessage.messageOwner.id == currentMessageObject.messageOwner.id) { @@ -402,7 +397,7 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD if (!url.equals(currentUrl)) { return true; } - } else if (currentPhotoObject == null || currentPhotoObject.photoOwner.location instanceof TLRPC.TL_fileLocationUnavailable) { + } else if (currentPhotoObject == null || currentPhotoObject.location instanceof TLRPC.TL_fileLocationUnavailable) { return true; } else if (currentMessageObject != null && photoNotSet) { File cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); @@ -429,6 +424,7 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD photoNotSet = false; drawBackground = true; + photoImage.setForcePreview(messageObject.isSecretPhoto()); if (messageObject.type == 9) { String name = messageObject.getDocumentName(); if (name == null || name.length() == 0) { @@ -499,23 +495,21 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD nameLayout = null; updateSecretTimeText(); } - - if (messageObject.type == 9) { + if (messageObject.type == 9) { //doc photoWidth = AndroidUtilities.dp(86); photoHeight = AndroidUtilities.dp(86); backgroundWidth = photoWidth + Math.max(nameWidth, infoWidth) + AndroidUtilities.dp(68); - currentPhotoObject = PhotoObject.getClosestImageWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + photoImage.setNeedsQualityThumb(true); + photoImage.setShouldGenerateQualityThumb(true); + photoImage.setParentMessageObject(messageObject); if (currentPhotoObject != null) { - if (currentPhotoObject.image != null) { - photoImage.setImageBitmap(currentPhotoObject.image); - } else { - currentPhotoFilter = String.format(Locale.US, "%d_%d_b", photoWidth, photoHeight); - photoImage.setImage(currentPhotoObject.photoOwner.location, currentPhotoFilter, null, 0, false); - } + currentPhotoFilter = String.format(Locale.US, "%d_%d_b", photoWidth, photoHeight); + photoImage.setImage(null, null, null, null, currentPhotoObject.location, currentPhotoFilter, 0, true); } else { - photoImage.setImageBitmap((BitmapDrawable)null); + photoImage.setImageBitmap((BitmapDrawable) null); } - } else if (messageObject.type == 4) { + } else if (messageObject.type == 4) { //geo photoWidth = AndroidUtilities.dp(100); photoHeight = AndroidUtilities.dp(100); backgroundWidth = photoWidth + AndroidUtilities.dp(12); @@ -523,8 +517,11 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD double lat = messageObject.messageOwner.media.geo.lat; double lon = messageObject.messageOwner.media.geo._long; currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=13&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int)Math.ceil(AndroidUtilities.density)), lat, lon); + photoImage.setNeedsQualityThumb(false); + photoImage.setShouldGenerateQualityThumb(false); + photoImage.setParentMessageObject(null); photoImage.setImage(currentUrl, null, null, 0); - } else if (messageObject.type == 13) { + } else if (messageObject.type == 13) { //webp drawBackground = false; for (TLRPC.DocumentAttribute attribute : messageObject.messageOwner.media.document.attributes) { if (attribute instanceof TLRPC.TL_documentAttributeImageSize) { @@ -548,20 +545,26 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD photoWidth = (int)maxWidth; } backgroundWidth = photoWidth + AndroidUtilities.dp(12); + currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); + photoImage.setNeedsQualityThumb(false); + photoImage.setShouldGenerateQualityThumb(false); + photoImage.setParentMessageObject(null); if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() > 0) { File f = new File(currentMessageObject.messageOwner.attachPath); if (f.exists()) { photoImage.setImage(null, currentMessageObject.messageOwner.attachPath, String.format(Locale.US, "%d_%d", photoWidth, photoHeight), - messageObject.imagePreview != null ? new BitmapDrawable(messageObject.imagePreview) : null, null, + currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, + "b1", currentMessageObject.messageOwner.media.document.size, true); } } else if (currentMessageObject.messageOwner.media.document.id != 0) { photoImage.setImage(currentMessageObject.messageOwner.media.document, null, String.format(Locale.US, "%d_%d", photoWidth, photoHeight), - messageObject.imagePreview != null ? new BitmapDrawable(messageObject.imagePreview) : null, - messageObject.messageOwner.media.document.thumb.location, + null, + currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, + "b1", currentMessageObject.messageOwner.media.document.size, true); } } else { @@ -579,23 +582,38 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD photoHeight = AndroidUtilities.getPhotoSize(); } - currentPhotoObject = PhotoObject.getClosestImageWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); if (messageObject.type == 1) { - currentPhotoObjectThumb = PhotoObject.getClosestImageWithSize(messageObject.photoThumbs, 80); + photoImage.setNeedsQualityThumb(false); + photoImage.setShouldGenerateQualityThumb(false); + photoImage.setParentMessageObject(null); + currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); + } else if (messageObject.type == 3) { + photoImage.setNeedsQualityThumb(true); + photoImage.setShouldGenerateQualityThumb(true); + photoImage.setParentMessageObject(messageObject); + } else if (messageObject.type == 8) { + photoImage.setNeedsQualityThumb(true); + photoImage.setShouldGenerateQualityThumb(true); + photoImage.setParentMessageObject(messageObject); } + //8 - gif, 1 - photo, 3 - video + + + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + if (currentPhotoObject != null) { boolean noSize = false; if (currentMessageObject.type == 3 || currentMessageObject.type == 8) { noSize = true; } - float scale = (float) currentPhotoObject.photoOwner.w / (float) photoWidth; + float scale = (float) currentPhotoObject.w / (float) photoWidth; - if (!noSize && currentPhotoObject.photoOwner.size == 0) { - currentPhotoObject.photoOwner.size = -1; + if (!noSize && currentPhotoObject.size == 0) { + currentPhotoObject.size = -1; } - int w = (int) (currentPhotoObject.photoOwner.w / scale); - int h = (int) (currentPhotoObject.photoOwner.h / scale); + int w = (int) (currentPhotoObject.w / scale); + int h = (int) (currentPhotoObject.h / scale); if (w == 0) { if (messageObject.type == 3) { w = infoWidth + infoOffset + AndroidUtilities.dp(16); @@ -613,9 +631,9 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD w = (int) (w / scale2); } else if (h < AndroidUtilities.dp(120)) { h = AndroidUtilities.dp(120); - float hScale = (float) currentPhotoObject.photoOwner.h / h; - if (currentPhotoObject.photoOwner.w / hScale < photoWidth) { - w = (int) (currentPhotoObject.photoOwner.w / hScale); + float hScale = (float) currentPhotoObject.h / h; + if (currentPhotoObject.w / hScale < photoWidth) { + w = (int) (currentPhotoObject.w / hScale); } } int timeWidthTotal = timeWidth + AndroidUtilities.dp(14 + (currentMessageObject.isOut() ? 20 : 0)); @@ -634,49 +652,50 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD photoWidth = w; photoHeight = h; backgroundWidth = w + AndroidUtilities.dp(12); + currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / AndroidUtilities.density), (int) (h / AndroidUtilities.density)); if (messageObject.photoThumbs.size() > 1 || messageObject.type == 3 || messageObject.type == 8) { - currentPhotoFilter += "_b"; + if (messageObject.isSecretPhoto()) { + currentPhotoFilter += "_b2"; + } else { + currentPhotoFilter += "_b"; + } } - if (currentPhotoObject.image != null) { - photoImage.setImageBitmap(currentPhotoObject.image); - } else { + String fileName = FileLoader.getAttachFileName(currentPhotoObject); + if (messageObject.type == 1) { boolean photoExist = true; - String fileName = FileLoader.getAttachFileName(currentPhotoObject.photoOwner); - if (messageObject.type == 1) { - File cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); - if (!cacheFile.exists()) { - photoExist = false; - } else { - MediaController.getInstance().removeLoadingFileObserver(this); - } + File cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); + if (!cacheFile.exists()) { + photoExist = false; + } else { + MediaController.getInstance().removeLoadingFileObserver(this); } + if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - if (allowedToSetPhoto || ImageLoader.getInstance().getImageFromMemory(currentPhotoObject.photoOwner.location, null, currentPhotoFilter, null) != null) { + if (allowedToSetPhoto || ImageLoader.getInstance().getImageFromMemory(currentPhotoObject.location, null, currentPhotoFilter) != null) { allowedToSetPhoto = true; - if (messageObject.imagePreview != null) { - photoImage.setImage(currentPhotoObject.photoOwner.location, currentPhotoFilter, new BitmapDrawable(messageObject.imagePreview), noSize ? 0 : currentPhotoObject.photoOwner.size, false); - } else { - photoImage.setImage(currentPhotoObject.photoOwner.location, currentPhotoFilter, null, noSize ? 0 : currentPhotoObject.photoOwner.size, false); - } + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, false); + } else if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, false); } else { - photoImage.setImageBitmap(messageObject.imagePreview); + photoImage.setImageBitmap((Drawable) null); } } else { photoNotSet = true; - if (messageObject.imagePreview != null) { - photoImage.setImageBitmap(messageObject.imagePreview); - } else if (currentPhotoObjectThumb != null) { - photoImage.setImage(currentPhotoObjectThumb.photoOwner.location, currentPhotoFilter, null, 0, true); + if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, false); + } else { + photoImage.setImageBitmap((Drawable) null); } } + } else { + photoImage.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, false); } } else { photoImage.setImageBitmap((Bitmap)null); } } - photoImage.setForcePreview(messageObject.isSecretPhoto()); invalidate(); } @@ -694,7 +713,7 @@ public class ChatMediaCell extends ChatBaseCell implements MediaController.FileD if (currentPhotoObject == null) { return; } - fileName = FileLoader.getAttachFileName(currentPhotoObject.photoOwner); + fileName = FileLoader.getAttachFileName(currentPhotoObject); cacheFile = FileLoader.getPathToMessage(currentMessageObject.messageOwner); } else if (currentMessageObject.type == 8 || currentMessageObject.type == 3 || currentMessageObject.type == 9) { if (currentMessageObject.messageOwner.attachPath != null && currentMessageObject.messageOwner.attachPath.length() != 0) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index 027b8cbf..d04e80cc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -50,6 +50,7 @@ public class DialogCell extends BaseCell { private static Drawable countDrawable; private static Drawable groupDrawable; private static Drawable broadcastDrawable; + private static Drawable muteDrawable; private static Paint linePaint; @@ -58,6 +59,7 @@ public class DialogCell extends BaseCell { private int lastMessageDate; private int unreadCount; private boolean lastUnreadState; + private boolean dialogMuted; private MessageObject message; private ImageReceiver avatarImage; @@ -76,6 +78,7 @@ public class DialogCell extends BaseCell { private boolean drawNameLock; private boolean drawNameGroup; private boolean drawNameBroadcast; + private int nameMuteLeft; private int nameLockLeft; private int nameLockTop; @@ -151,6 +154,7 @@ public class DialogCell extends BaseCell { countDrawable = getResources().getDrawable(R.drawable.dialogs_badge); groupDrawable = getResources().getDrawable(R.drawable.list_group); broadcastDrawable = getResources().getDrawable(R.drawable.list_broadcast); + muteDrawable = getResources().getDrawable(R.drawable.mute_grey); } } @@ -162,12 +166,13 @@ public class DialogCell extends BaseCell { avatarDrawable = new AvatarDrawable(); } - public void setDialog(long dialog_id, MessageObject messageObject, boolean usePrintStrings, int date, int unread) { + public void setDialog(long dialog_id, MessageObject messageObject, boolean usePrintStrings, int date, int unread, boolean muted) { currentDialogId = dialog_id; message = messageObject; allowPrintStrings = usePrintStrings; lastMessageDate = date; unreadCount = unread; + dialogMuted = muted; lastUnreadState = messageObject != null && messageObject.isUnread(); update(0); } @@ -463,6 +468,14 @@ public class DialogCell extends BaseCell { } } + if (dialogMuted) { + int w = AndroidUtilities.dp(6) + muteDrawable.getIntrinsicWidth(); + nameWidth -= w; + if (LocaleController.isRTL) { + nameLeft += w; + } + } + nameWidth = Math.max(AndroidUtilities.dp(12), nameWidth); CharSequence nameStringFinal = TextUtils.ellipsize(nameString.replace("\n", " "), currentNamePaint, nameWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); try { @@ -536,6 +549,9 @@ public class DialogCell extends BaseCell { nameLeft += (nameWidth - widthpx); } } + if (dialogMuted) { + nameMuteLeft = (nameLeft - AndroidUtilities.dp(6) - muteDrawable.getIntrinsicWidth()); + } } if (messageLayout != null && messageLayout.getLineCount() > 0) { left = messageLayout.getLineLeft(0); @@ -555,6 +571,9 @@ public class DialogCell extends BaseCell { nameLeft -= (nameWidth - widthpx); } } + if (dialogMuted) { + nameMuteLeft = (int) (nameLeft + left + AndroidUtilities.dp(6)); + } } if (messageLayout != null && messageLayout.getLineCount() > 0) { left = messageLayout.getLineRight(0); @@ -710,6 +729,11 @@ public class DialogCell extends BaseCell { } } + if (dialogMuted) { + setDrawableBounds(muteDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); + muteDrawable.draw(canvas); + } + if (drawError) { setDrawableBounds(errorDrawable, errorLeft, errorTop); errorDrawable.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java new file mode 100644 index 00000000..b6be0e98 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java @@ -0,0 +1,59 @@ +/* + * This is the source code of Telegram for Android v. 2.0.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2014. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.android.AndroidUtilities; +import org.telegram.ui.Components.FrameLayoutFixed; + +public class PhotoEditToolCell extends FrameLayoutFixed { + + private ImageView iconImage; + private TextView nameTextView; + private TextView valueTextView; + + public PhotoEditToolCell(Context context) { + super(context); + + iconImage = new ImageView(context); + iconImage.setScaleType(ImageView.ScaleType.CENTER); + addView(iconImage); + LayoutParams layoutParams = (LayoutParams) iconImage.getLayoutParams(); + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = LayoutParams.MATCH_PARENT; + layoutParams.bottomMargin = AndroidUtilities.dp(20); + iconImage.setLayoutParams(layoutParams); + + nameTextView = new TextView(context); + nameTextView.setGravity(Gravity.CENTER); + nameTextView.setTextColor(0xffffffff); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + addView(nameTextView); + layoutParams = (LayoutParams) nameTextView.getLayoutParams(); + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = AndroidUtilities.dp(20); + layoutParams.gravity = Gravity.LEFT | Gravity.BOTTOM; + nameTextView.setLayoutParams(layoutParams); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(80), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); + } + + public void setIconAndText(int resId, String text) { + iconImage.setImageResource(resId); + nameTextView.setText(text); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java index f0043492..4879a0c0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java @@ -11,6 +11,7 @@ package org.telegram.ui.Cells; import android.content.Context; import android.view.Gravity; import android.widget.FrameLayout; +import android.widget.ImageView; import org.telegram.android.AndroidUtilities; import org.telegram.messenger.R; @@ -22,6 +23,7 @@ public class PhotoPickerPhotoCell extends FrameLayout { public BackupImageView photoImage; public FrameLayout checkFrame; public CheckBox checkBox; + public ImageView editedImage; public int itemWidth; public PhotoPickerPhotoCell(Context context) { @@ -55,6 +57,16 @@ public class PhotoPickerPhotoCell extends FrameLayout { layoutParams.topMargin = AndroidUtilities.dp(6); layoutParams.rightMargin = AndroidUtilities.dp(6); checkBox.setLayoutParams(layoutParams); + + editedImage = new ImageView(context); + editedImage.setImageResource(R.drawable.photo_edit); + editedImage.setScaleType(ImageView.ScaleType.CENTER); + addView(editedImage); + layoutParams = (LayoutParams) editedImage.getLayoutParams(); + layoutParams.width = AndroidUtilities.dp(42); + layoutParams.height = AndroidUtilities.dp(42); + layoutParams.gravity = Gravity.LEFT | Gravity.TOP; + editedImage.setLayoutParams(layoutParams); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java new file mode 100644 index 00000000..2ddd7307 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java @@ -0,0 +1,381 @@ +/* + * This is the source code of Telegram for Android v. 2.0.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2014. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.android.AndroidUtilities; +import org.telegram.android.ImageLoader; +import org.telegram.android.ImageReceiver; +import org.telegram.android.LocaleController; +import org.telegram.android.MediaController; +import org.telegram.android.MessageObject; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.R; +import org.telegram.messenger.TLRPC; +import org.telegram.messenger.Utilities; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.CheckBox; +import org.telegram.ui.Components.LineProgressView; + +import java.io.File; +import java.util.Date; + +public class SharedDocumentCell extends FrameLayout implements MediaController.FileDownloadProgressListener { + + private ImageView placeholderImabeView; + private BackupImageView thumbImageView; + private TextView nameTextView; + private TextView extTextView; + private TextView dateTextView; + private ImageView statusImageView; + private LineProgressView progressView; + private CheckBox checkBox; + + private boolean needDivider; + + private static Paint paint; + + private int TAG; + + private MessageObject message; + private boolean loading; + private boolean loaded; + + private int icons[] = { + R.drawable.media_doc_blue, + R.drawable.media_doc_green, + R.drawable.media_doc_red, + R.drawable.media_doc_yellow + }; + + public SharedDocumentCell(Context context) { + super(context); + + if (paint == null) { + paint = new Paint(); + paint.setColor(0xffd9d9d9); + paint.setStrokeWidth(1); + } + + TAG = MediaController.getInstance().generateObserverTag(); + + placeholderImabeView = new ImageView(context); + addView(placeholderImabeView); + LayoutParams layoutParams = (LayoutParams) placeholderImabeView.getLayoutParams(); + layoutParams.width = AndroidUtilities.dp(40); + layoutParams.height = AndroidUtilities.dp(40); + layoutParams.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(12); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(12) : 0; + layoutParams.topMargin = AndroidUtilities.dp(8); + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + placeholderImabeView.setLayoutParams(layoutParams); + + extTextView = new TextView(context); + extTextView.setTextColor(0xffffffff); + extTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + extTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + extTextView.setLines(1); + extTextView.setMaxLines(1); + extTextView.setSingleLine(true); + extTextView.setGravity(Gravity.CENTER); + extTextView.setEllipsize(TextUtils.TruncateAt.END); + addView(extTextView); + layoutParams = (LayoutParams) extTextView.getLayoutParams(); + layoutParams.width = AndroidUtilities.dp(32); + layoutParams.height = LayoutParams.WRAP_CONTENT; + layoutParams.topMargin = AndroidUtilities.dp(22); + layoutParams.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(16); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(16) : 0; + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + extTextView.setLayoutParams(layoutParams); + + thumbImageView = new BackupImageView(context); + thumbImageView.imageReceiver.setDelegate(new ImageReceiver.ImageReceiverDelegate() { + @Override + public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) { + extTextView.setVisibility(set ? GONE : VISIBLE); + placeholderImabeView.setVisibility(set ? GONE : VISIBLE); + } + }); + addView(thumbImageView); + layoutParams = (LayoutParams) thumbImageView.getLayoutParams(); + layoutParams.width = AndroidUtilities.dp(40); + layoutParams.height = AndroidUtilities.dp(40); + layoutParams.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(12); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(12) : 0; + layoutParams.topMargin = AndroidUtilities.dp(8); + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + thumbImageView.setLayoutParams(layoutParams); + + nameTextView = new TextView(context); + nameTextView.setTextColor(0xff222222); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + nameTextView.setLines(1); + nameTextView.setMaxLines(1); + nameTextView.setSingleLine(true); + nameTextView.setEllipsize(TextUtils.TruncateAt.END); + nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(nameTextView); + layoutParams = (LayoutParams) nameTextView.getLayoutParams(); + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = LayoutParams.WRAP_CONTENT; + layoutParams.topMargin = AndroidUtilities.dp(5); + layoutParams.leftMargin = LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(72); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(72) : AndroidUtilities.dp(8); + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + nameTextView.setLayoutParams(layoutParams); + + statusImageView = new ImageView(context); + statusImageView.setVisibility(GONE); + addView(statusImageView); + layoutParams = (LayoutParams) statusImageView.getLayoutParams(); + layoutParams.width = LayoutParams.WRAP_CONTENT; + layoutParams.height = LayoutParams.WRAP_CONTENT; + layoutParams.topMargin = AndroidUtilities.dp(35); + layoutParams.leftMargin = LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(72); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(72) : AndroidUtilities.dp(8); + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + statusImageView.setLayoutParams(layoutParams); + + dateTextView = new TextView(context); + dateTextView.setTextColor(0xff999999); + dateTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + dateTextView.setLines(1); + dateTextView.setMaxLines(1); + dateTextView.setSingleLine(true); + dateTextView.setEllipsize(TextUtils.TruncateAt.END); + dateTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(dateTextView); + layoutParams = (LayoutParams) dateTextView.getLayoutParams(); + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = LayoutParams.WRAP_CONTENT; + layoutParams.topMargin = AndroidUtilities.dp(30); + layoutParams.leftMargin = LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(72); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(72) : AndroidUtilities.dp(8); + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + dateTextView.setLayoutParams(layoutParams); + + progressView = new LineProgressView(context); + addView(progressView); + layoutParams = (LayoutParams) progressView.getLayoutParams(); + layoutParams.width = LayoutParams.MATCH_PARENT; + layoutParams.height = AndroidUtilities.dp(2); + layoutParams.topMargin = AndroidUtilities.dp(54); + layoutParams.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(72); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(72) : 0; + layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; + progressView.setLayoutParams(layoutParams); + + checkBox = new CheckBox(context, R.drawable.round_check2); + checkBox.setVisibility(GONE); + addView(checkBox); + layoutParams = (LayoutParams) checkBox.getLayoutParams(); + layoutParams.width = AndroidUtilities.dp(22); + layoutParams.height = AndroidUtilities.dp(22); + layoutParams.topMargin = AndroidUtilities.dp(30); + layoutParams.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(34); + layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(34) : 0; + layoutParams.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + checkBox.setLayoutParams(layoutParams); + } + + private int getThumbForNameOrMime(String name, String mime) { + if (name != null && name.length() != 0) { + int color = -1; + if (name.contains(".doc") || name.contains(".txt") || name.contains(".psd")) { + color = 0; + } else if (name.contains(".xls") || name.contains(".csv")) { + color = 1; + } else if (name.contains(".pdf") || name.contains(".ppt") || name.contains(".key")) { + color = 2; + } else if (name.contains(".zip") || name.contains(".rar") || name.contains(".ai") || name.contains(".mp3") || name.contains(".mov") || name.contains(".avi")) { + color = 3; + } + if (color == -1) { + int idx; + String ext = (idx = name.lastIndexOf(".")) == -1 ? "" : name.substring(idx + 1); + if (ext.length() != 0) { + color = ext.charAt(0) % icons.length; + } else { + color = name.charAt(0) % icons.length; + } + } + return icons[color]; + } + return icons[0]; + } + + public void setTextAndValueAndTypeAndThumb(String text, String value, String type, String thumb, int resId) { + nameTextView.setText(text); + dateTextView.setText(value); + if (type != null) { + extTextView.setVisibility(VISIBLE); + extTextView.setText(type); + } else { + extTextView.setVisibility(GONE); + } + if (resId == 0) { + placeholderImabeView.setImageResource(getThumbForNameOrMime(text, type)); + placeholderImabeView.setVisibility(VISIBLE); + } else { + placeholderImabeView.setVisibility(GONE); + } + if (thumb != null || resId != 0) { + if (thumb != null) { + thumbImageView.setImage(thumb, "40_40", null); + } else { + thumbImageView.setImageResource(resId); + } + thumbImageView.setVisibility(VISIBLE); + } else { + thumbImageView.setVisibility(GONE); + } + } + + public void setChecked(boolean checked, boolean animated) { + if (checkBox.getVisibility() != VISIBLE) { + checkBox.setVisibility(VISIBLE); + } + checkBox.setChecked(checked, animated); + } + + public void setDocument(MessageObject document, boolean divider) { + needDivider = divider; + message = document; + loaded = false; + loading = false; + + int idx = -1; + String name = FileLoader.getDocumentFileName(document.messageOwner.media.document); + placeholderImabeView.setVisibility(VISIBLE); + extTextView.setVisibility(VISIBLE); + placeholderImabeView.setImageResource(getThumbForNameOrMime(name, document.messageOwner.media.document.mime_type)); + nameTextView.setText(name); + extTextView.setText((idx = name.lastIndexOf(".")) == -1 ? "" : name.substring(idx + 1).toLowerCase()); + if (document.messageOwner.media.document.thumb instanceof TLRPC.TL_photoSizeEmpty) { + thumbImageView.setVisibility(GONE); + thumbImageView.setImageBitmap(null); + } else { + thumbImageView.setVisibility(VISIBLE); + thumbImageView.setImage(document.messageOwner.media.document.thumb.location, "40_40", (Drawable) null); + } + long date = (long) document.messageOwner.date * 1000; + dateTextView.setText(String.format("%s, %s", Utilities.formatFileSize(document.messageOwner.media.document.size), LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.formatterYear.format(new Date(date)), LocaleController.formatterDay.format(new Date(date))))); + setWillNotDraw(!needDivider); + progressView.setProgress(0, false); + + updateFileExistIcon(); + } + + public void updateFileExistIcon() { + if (message != null) { + String fileName = null; + File cacheFile = null; + if (message.messageOwner.attachPath == null || message.messageOwner.attachPath.length() == 0 || !(new File(message.messageOwner.attachPath).exists())) { + cacheFile = FileLoader.getPathToMessage(message.messageOwner); + if (!cacheFile.exists()) { + fileName = FileLoader.getAttachFileName(message.messageOwner.media.document); + } + } + loaded = false; + if (fileName == null) { + statusImageView.setVisibility(GONE); + dateTextView.setPadding(0, 0, 0, 0); + loading = false; + loaded = true; + MediaController.getInstance().removeLoadingFileObserver(this); + } else { + MediaController.getInstance().addLoadingFileObserver(fileName, this); + loading = FileLoader.getInstance().isLoadingFile(fileName); + statusImageView.setVisibility(VISIBLE); + statusImageView.setImageResource(loading ? R.drawable.media_doc_pause : R.drawable.media_doc_load); + dateTextView.setPadding(LocaleController.isRTL ? 0 : AndroidUtilities.dp(14), 0, LocaleController.isRTL ? AndroidUtilities.dp(14) : 0, 0); + if (loading) { + progressView.setVisibility(VISIBLE); + Float progress = ImageLoader.getInstance().getFileProgress(fileName); + if (progress == null) { + progress = 0.0f; + } + progressView.setProgress(progress, false); + } else { + progressView.setVisibility(GONE); + } + } + } else { + loading = false; + loaded = true; + progressView.setVisibility(GONE); + progressView.setProgress(0, false); + statusImageView.setVisibility(GONE); + dateTextView.setPadding(0, 0, 0, 0); + MediaController.getInstance().removeLoadingFileObserver(this); + } + } + + public MessageObject getDocument() { + return message; + } + + public boolean isLoaded() { + return loaded; + } + + public boolean isLoading() { + return loading; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + } + + @Override + protected void onDraw(Canvas canvas) { + if (needDivider) { + canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + } + } + + @Override + public void onFailedDownload(String name) { + updateFileExistIcon(); + } + + @Override + public void onSuccessDownload(String name) { + progressView.setProgress(1, true); + updateFileExistIcon(); + } + + @Override + public void onProgressDownload(String fileName, float progress) { + progressView.setProgress(progress, true); + } + + @Override + public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { + + } + + @Override + public int getObserverTag() { + return TAG; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailDocumentsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailDocumentsCell.java deleted file mode 100644 index 66b5363b..00000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailDocumentsCell.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 1.7.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2014. - */ - -package org.telegram.ui.Cells; - -import android.content.Context; -import android.graphics.Typeface; -import android.text.TextUtils; -import android.util.TypedValue; -import android.view.Gravity; -import android.widget.FrameLayout; -import android.widget.TextView; - -import org.telegram.android.AndroidUtilities; -import org.telegram.android.LocaleController; -import org.telegram.messenger.R; -import org.telegram.ui.Components.BackupImageView; -import org.telegram.ui.Components.CheckBox; - -public class TextDetailDocumentsCell extends FrameLayout { - - private TextView textView; - private TextView valueTextView; - private TextView typeTextView; - private BackupImageView imageView; - private CheckBox checkBox; - - public TextDetailDocumentsCell(Context context) { - super(context); - - textView = new TextView(context); - textView.setTextColor(0xff212121); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - textView.setLines(1); - textView.setMaxLines(1); - textView.setSingleLine(true); - textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - addView(textView); - LayoutParams layoutParams = (LayoutParams) textView.getLayoutParams(); - layoutParams.width = LayoutParams.WRAP_CONTENT; - layoutParams.height = LayoutParams.WRAP_CONTENT; - layoutParams.topMargin = AndroidUtilities.dp(10); - layoutParams.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? 16 : 71); - layoutParams.rightMargin = AndroidUtilities.dp(LocaleController.isRTL ? 71 : 16); - layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; - textView.setLayoutParams(layoutParams); - - valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); - valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - valueTextView.setLines(1); - valueTextView.setMaxLines(1); - valueTextView.setSingleLine(true); - valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - addView(valueTextView); - layoutParams = (LayoutParams) valueTextView.getLayoutParams(); - layoutParams.width = LayoutParams.WRAP_CONTENT; - layoutParams.height = LayoutParams.WRAP_CONTENT; - layoutParams.topMargin = AndroidUtilities.dp(35); - layoutParams.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? 16 : 71); - layoutParams.rightMargin = AndroidUtilities.dp(LocaleController.isRTL ? 71 : 16); - layoutParams.gravity = LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT; - valueTextView.setLayoutParams(layoutParams); - - typeTextView = new TextView(context); - typeTextView.setBackgroundColor(0xff757575); - typeTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE); - typeTextView.setGravity(Gravity.CENTER); - typeTextView.setSingleLine(true); - typeTextView.setTextColor(0xffd1d1d1); - typeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - typeTextView.setTypeface(Typeface.DEFAULT_BOLD); - addView(typeTextView); - layoutParams = (LayoutParams) typeTextView.getLayoutParams(); - layoutParams.width = AndroidUtilities.dp(40); - layoutParams.height = AndroidUtilities.dp(40); - layoutParams.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? 0 : 16); - layoutParams.rightMargin = AndroidUtilities.dp(LocaleController.isRTL ? 16 : 0); - layoutParams.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL; - typeTextView.setLayoutParams(layoutParams); - - imageView = new BackupImageView(context); - addView(imageView); - layoutParams = (LayoutParams) imageView.getLayoutParams(); - layoutParams.width = AndroidUtilities.dp(40); - layoutParams.height = AndroidUtilities.dp(40); - layoutParams.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? 0 : 16); - layoutParams.rightMargin = AndroidUtilities.dp(LocaleController.isRTL ? 16 : 0); - layoutParams.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL; - imageView.setLayoutParams(layoutParams); - - checkBox = new CheckBox(context, R.drawable.round_check2); - checkBox.setVisibility(GONE); - addView(checkBox); - layoutParams = (LayoutParams) checkBox.getLayoutParams(); - layoutParams.width = AndroidUtilities.dp(22); - layoutParams.height = AndroidUtilities.dp(22); - layoutParams.topMargin = AndroidUtilities.dp(34); - layoutParams.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(38); - layoutParams.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(38) : 0; - layoutParams.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - checkBox.setLayoutParams(layoutParams); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(64), MeasureSpec.EXACTLY)); - } - - public void setTextAndValueAndTypeAndThumb(String text, String value, String type, String thumb, int resId) { - textView.setText(text); - valueTextView.setText(value); - if (type != null) { - typeTextView.setVisibility(VISIBLE); - typeTextView.setText(type); - } else { - typeTextView.setVisibility(GONE); - } - if (thumb != null || resId != 0) { - if (thumb != null) { - imageView.setImage(thumb, "40_40", null); - } else { - imageView.setImageResource(resId); - } - imageView.setVisibility(VISIBLE); - } else { - imageView.setVisibility(GONE); - } - } - - public void setChecked(boolean checked, boolean animated) { - if (checkBox.getVisibility() != VISIBLE) { - checkBox.setVisibility(VISIBLE); - } - checkBox.setChecked(checked, animated); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index adc0c247..7b459010 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -134,6 +134,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private RecyclerListView stickersListView; private StickersAdapter stickersAdapter; private View stickersPanel; + private TextView muteItem; private boolean allowStickersPanel; private AnimatorSetProxy runningAnimation; @@ -213,6 +214,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private final static int clear_history = 11; private final static int delete_chat = 12; private final static int share_contact = 13; + private final static int mute = 14; AdapterView.OnItemLongClickListener onItemLongClickListener = new AdapterView.OnItemLongClickListener() { @Override @@ -446,6 +448,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidStarted); NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateMessageMedia); NotificationCenter.getInstance().addObserver(this, NotificationCenter.replaceMessagesObjects); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.notificationsSettingsUpdated); super.onFragmentCreate(); @@ -505,6 +508,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidStarted); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateMessageMedia); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.replaceMessagesObjects); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.notificationsSettingsUpdated); if (AndroidUtilities.isTablet()) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); } @@ -770,6 +774,58 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showAlertDialog(builder); } + } else if (id == mute) { + boolean muted = MessagesController.getInstance().isDialogMuted(dialog_id); + if (!muted) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("Notifications", R.string.Notifications)); + CharSequence[] items = new CharSequence[]{ + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Hours", 1)), + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Hours", 8)), + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Days", 2)) + }; + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + int untilTime = ConnectionsManager.getInstance().getCurrentTime(); + if (i == 0) { + untilTime += 60 * 60; + } else if (i == 1) { + untilTime += 60 * 60 * 8; + } else if (i == 2) { + untilTime += 60 * 60 * 48; + } + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("notify2_" + dialog_id, 3); + editor.putInt("notifyuntil_" + dialog_id, untilTime); + long flags = ((long)untilTime << 32) | 1; + MessagesStorage.getInstance().setDialogFlags(dialog_id, flags); + editor.commit(); + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + dialog.notify_settings.mute_until = untilTime; + } + NotificationsController.updateServerNotificationsSettings(dialog_id); + } + } + ); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showAlertDialog(builder); + } else { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("notify2_" + dialog_id, 0); + MessagesStorage.getInstance().setDialogFlags(dialog_id, 0); + editor.commit(); + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + } + NotificationsController.updateServerNotificationsSettings(dialog_id); + } } } }); @@ -861,6 +917,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not nameTextView.setSingleLine(true); nameTextView.setEllipsize(TextUtils.TruncateAt.END); nameTextView.setGravity(Gravity.LEFT); + nameTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); avatarContainer.addView(nameTextView); layoutParams2 = (FrameLayout.LayoutParams) nameTextView.getLayoutParams(); @@ -888,14 +945,6 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not layoutParams2.gravity = Gravity.BOTTOM; onlineTextView.setLayoutParams(layoutParams2); - updateTitle(); - updateSubtitle(); - - if (currentEncryptedChat != null) { - nameTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_header, 0, 0, 0); - nameTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); - } - ActionBarMenu menu = actionBar.createMenu(); headerItem = menu.addItem(0, R.drawable.ic_ab_other); @@ -911,11 +960,16 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { headerItem.addSubItem(delete_chat, LocaleController.getString("DeleteChatUser", R.string.DeleteChatUser), 0); } + muteItem = headerItem.addSubItem(mute, null, 0); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) headerItem.getLayoutParams(); layoutParams.rightMargin = AndroidUtilities.dp(-48); headerItem.setLayoutParams(layoutParams); + updateTitle(); + updateSubtitle(); + updateTitleIcons(); + attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other); attachItem.addSubItem(attach_photo, LocaleController.getString("ChatTakePhoto", R.string.ChatTakePhoto), R.drawable.ic_attach_photo); attachItem.addSubItem(attach_gallery, LocaleController.getString("ChatGallery", R.string.ChatGallery), R.drawable.ic_attach_gallery); @@ -1731,12 +1785,34 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentUser.phone != null && currentUser.phone.length() != 0) { nameTextView.setText(PhoneFormat.getInstance().format("+" + currentUser.phone)); } else { - nameTextView.setText(ContactsController.formatName(currentUser.first_name, currentUser.last_name)); + if (currentUser instanceof TLRPC.TL_userDeleted) { + nameTextView.setText(LocaleController.getString("HiddenName", R.string.HiddenName)); + } else { + nameTextView.setText(ContactsController.formatName(currentUser.first_name, currentUser.last_name)); + } } } else { nameTextView.setText(ContactsController.formatName(currentUser.first_name, currentUser.last_name)); } } + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); + if (dialog != null && dialog.notify_settings != null) { + + } else { + nameTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.mute_blue, 0); + } + } + + private void updateTitleIcons() { + int leftIcon = currentEncryptedChat != null ? R.drawable.ic_lock_header : 0; + int rightIcon = MessagesController.getInstance().isDialogMuted(dialog_id) ? R.drawable.mute_fixed : 0; + nameTextView.setCompoundDrawablesWithIntrinsicBounds(leftIcon, 0, rightIcon, 0); + + if (rightIcon != 0) { + muteItem.setText(LocaleController.getString("UnmuteNotifications", R.string.UnmuteNotifications)); + } else { + muteItem.setText(LocaleController.getString("MuteNotifications", R.string.MuteNotifications)); + } } private void updateSubtitle() { @@ -2076,7 +2152,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.Message dateMsg = new TLRPC.Message(); dateMsg.message = LocaleController.formatDateChat(obj.messageOwner.date); dateMsg.id = 0; - MessageObject dateObj = new MessageObject(dateMsg, null); + MessageObject dateObj = new MessageObject(dateMsg, null, false); dateObj.type = 10; dateObj.contentType = 4; if (load_type == 1) { @@ -2099,7 +2175,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.Message dateMsg = new TLRPC.Message(); dateMsg.message = ""; dateMsg.id = 0; - MessageObject dateObj = new MessageObject(dateMsg, null); + MessageObject dateObj = new MessageObject(dateMsg, null, false); dateObj.contentType = dateObj.type = 6; boolean dateAdded = true; if (a != messArr.size() - 1) { @@ -2395,7 +2471,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.Message dateMsg = new TLRPC.Message(); dateMsg.message = LocaleController.formatDateChat(obj.messageOwner.date); dateMsg.id = 0; - MessageObject dateObj = new MessageObject(dateMsg, null); + MessageObject dateObj = new MessageObject(dateMsg, null, false); dateObj.type = 10; dateObj.contentType = 4; messages.add(0, dateObj); @@ -2534,7 +2610,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not TLRPC.Message newMsgObj = (TLRPC.Message)args[2]; if (newMsgObj != null) { obj.messageOwner.media = newMsgObj.media; - obj.generateThumbs(true, 1); + obj.generateThumbs(true); } messagesDict.remove(msgId); messagesDict.put(newMsgId, obj); @@ -2691,7 +2767,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (existMessageObject != null) { existMessageObject.messageOwner.media = messageObject.messageOwner.media; existMessageObject.messageOwner.attachPath = messageObject.messageOwner.attachPath; - existMessageObject.generateThumbs(false, 1); + existMessageObject.generateThumbs(false); } updateVisibleRows(); } else if (id == NotificationCenter.replaceMessagesObjects) { @@ -2713,6 +2789,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not chatAdapter.notifyDataSetChanged(); } } + } else if (id == NotificationCenter.notificationsSettingsUpdated) { + updateTitleIcons(); } } @@ -2746,8 +2824,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (currentChat != null && (currentChat instanceof TLRPC.TL_chatForbidden || currentChat.left) || currentUser != null && (currentUser instanceof TLRPC.TL_userDeleted || currentUser instanceof TLRPC.TL_userEmpty || userBlocked)) { bottomOverlayChat.setVisibility(View.VISIBLE); + muteItem.setVisibility(View.GONE); chatActivityEnterView.setFieldFocused(false); } else { + muteItem.setVisibility(View.VISIBLE); bottomOverlayChat.setVisibility(View.GONE); } } @@ -2925,6 +3005,13 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else { selectedMessagesCountTextView.setTextSize(20); } + if (AndroidUtilities.isTablet()) { + if (AndroidUtilities.isSmallTablet() && getParentActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + } else { + actionBar.setBackButtonImage(R.drawable.ic_close_white); + } + } int padding = (AndroidUtilities.getCurrentActionBarHeight() - AndroidUtilities.dp(48)) / 2; avatarContainer.setPadding(avatarContainer.getPaddingLeft(), padding, avatarContainer.getPaddingRight(), padding); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)avatarContainer.getLayoutParams(); @@ -3007,10 +3094,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (type == 3) { items = new CharSequence[]{LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("Delete", R.string.Delete)}; } else if (type == 4) { - items = new CharSequence[]{LocaleController.getString(selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? "SaveToDownloads" : "SaveToGallery", - selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? R.string.SaveToDownloads : R.string.SaveToGallery), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)}; + items = new CharSequence[]{LocaleController.getString(selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? "ShareFile" : "SaveToGallery", + selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? R.string.ShareFile : R.string.SaveToGallery), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)}; } else if (type == 5) { - items = new CharSequence[]{LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile), LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)}; + items = new CharSequence[]{LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile), LocaleController.getString("ShareFile", R.string.ShareFile), LocaleController.getString("Forward", R.string.Forward), LocaleController.getString("Delete", R.string.Delete)}; } } else { if (type == 2) { @@ -3018,8 +3105,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (type == 3) { items = new CharSequence[]{LocaleController.getString("Copy", R.string.Copy), LocaleController.getString("Delete", R.string.Delete)}; } else if (type == 4) { - items = new CharSequence[]{LocaleController.getString(selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? "SaveToDownloads" : "SaveToGallery", - selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? R.string.SaveToDownloads : R.string.SaveToGallery), LocaleController.getString("Delete", R.string.Delete)}; + items = new CharSequence[]{LocaleController.getString(selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? "ShareFile" : "SaveToGallery", + selectedObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument ? R.string.ShareFile : R.string.SaveToGallery), LocaleController.getString("Delete", R.string.Delete)}; } else if (type == 5) { items = new CharSequence[]{LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile), LocaleController.getString("Delete", R.string.Delete)}; } @@ -3206,7 +3293,10 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (selectedObject.type == 1) { MediaController.saveFile(path, getParentActivity(), 0, null); } else if (selectedObject.type == 8 || selectedObject.type == 9) { - MediaController.saveFile(path, getParentActivity(), 2, selectedObject.getDocumentName()); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType(selectedObject.messageOwner.media.document.mime_type); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(path))); + getParentActivity().startActivity(Intent.createChooser(intent, "")); } } else if (option == 5) { File locFile = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java index 87904fe9..ec5c9fd8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java @@ -32,11 +32,11 @@ public class AvatarDrawable extends Drawable { private static TextPaint namePaint; private static int[] arrColors = {0xffe56555, 0xfff28c48, 0xffeec764, 0xff76c84d, 0xff5fbed5, 0xff549cdd, 0xff8e85ee, 0xfff2749a}; private static int[] arrColorsProfiles = {0xffd86f65, 0xfff69d61, 0xfffabb3c, 0xff67b35d, 0xff56a2bb, 0xff5c98cd, 0xff8c79d2, 0xfff37fa6}; - private static int[] arrColorsProfilesBack = {0xffca6056, 0xfff18944, 0xfff2b02c, 0xff56a14c, 0xff4492ac, 0xff4c84b6, 0xff7d6ac4, 0xffe66b94}; - private static int[] arrColorsProfilesText = {0xfff9cbc5, 0xfffdddc8, 0xfffce5bb, 0xffc0edba, 0xffb8e2f0, 0xffb3d7f7, 0xffcdc4ed, 0xfffed1e0}; - private static int[] arrColorsNames = {0xffca5650, 0xffd87b29, 0xffc7a21c, 0xff50b232, 0xff42b1a8, 0xff4e92cc, 0xff4e92cc, 0xffdb5b9d}; - private static int[] arrColorsButtons = {R.drawable.bar_selector_red, R.drawable.bar_selector_orange, R.drawable.bar_selector_yellow, - R.drawable.bar_selector_green, R.drawable.bar_selector_cyan, R.drawable.bar_selector_blue, R.drawable.bar_selector_violet, R.drawable.bar_selector_pink}; + private static int[] arrColorsProfilesBack = {0xffca6056, 0xfff18944, 0xff7d6ac4, 0xff56a14c, 0xff4492ac, 0xff4c84b6, 0xff7d6ac4, 0xff4c84b6}; + private static int[] arrColorsProfilesText = {0xfff9cbc5, 0xfffdddc8, 0xffcdc4ed, 0xffc0edba, 0xffb8e2f0, 0xffb3d7f7, 0xffcdc4ed, 0xffb3d7f7}; + private static int[] arrColorsNames = {0xffca5650, 0xffd87b29, 0xff4e92cc, 0xff50b232, 0xff42b1a8, 0xff4e92cc, 0xff4e92cc, 0xff4e92cc}; + private static int[] arrColorsButtons = {R.drawable.bar_selector_red, R.drawable.bar_selector_orange, R.drawable.bar_selector_violet, + R.drawable.bar_selector_green, R.drawable.bar_selector_cyan, R.drawable.bar_selector_blue, R.drawable.bar_selector_violet, R.drawable.bar_selector_blue}; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java index 07edc7ad..2b0150e5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java @@ -29,7 +29,7 @@ import org.telegram.ui.ActionBar.BaseFragment; import java.io.File; -public class AvatarUpdater implements NotificationCenter.NotificationCenterDelegate, PhotoCropActivity.PhotoCropActivityDelegate { +public class AvatarUpdater implements NotificationCenter.NotificationCenterDelegate, PhotoCropActivity.PhotoEditActivityDelegate { public String currentPicturePath; private TLRPC.PhotoSize smallPhoto; private TLRPC.PhotoSize bigPhoto; @@ -94,7 +94,7 @@ public class AvatarUpdater implements NotificationCenter.NotificationCenterDeleg activity.presentFragment(photoCropActivity); } catch (Exception e) { FileLog.e("tmessages", e); - Bitmap bitmap = ImageLoader.loadBitmap(path, uri, 800, 800); + Bitmap bitmap = ImageLoader.loadBitmap(path, uri, 800, 800, true); processBitmap(bitmap); } } @@ -137,7 +137,7 @@ public class AvatarUpdater implements NotificationCenter.NotificationCenterDeleg } @Override - public void didFinishCrop(Bitmap bitmap) { + public void didFinishEdit(Bitmap bitmap, Bundle args) { processBitmap(bitmap); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java index 81b4af53..7f30f265 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java @@ -18,6 +18,7 @@ import android.view.View; import org.telegram.android.ImageReceiver; import org.telegram.messenger.TLObject; +import org.telegram.messenger.TLRPC; public class BackupImageView extends View { @@ -43,34 +44,35 @@ public class BackupImageView extends View { imageReceiver = new ImageReceiver(this); } - public void setImage(TLObject path, String filter, Drawable placeholder) { - setImage(path, null, filter, placeholder, null, 0); + public void setImage(TLObject path, String filter, Drawable thumb) { + setImage(path, null, filter, thumb, null, null, null, 0); } - public void setImage(TLObject path, String filter, Bitmap placeholderBitmap) { - setImage(path, null, filter, null, placeholderBitmap, 0); + public void setImage(TLObject path, String filter, Bitmap thumb) { + setImage(path, null, filter, null, thumb, null, null, 0); } - public void setImage(TLObject path, String filter, Drawable placeholder, int size) { - setImage(path, null, filter, placeholder, null, size); + public void setImage(TLObject path, String filter, Drawable thumb, int size) { + setImage(path, null, filter, thumb, null, null, null, size); } - public void setImage(TLObject path, String filter, Bitmap placeholderBitmap, int size) { - setImage(path, null, filter, null, placeholderBitmap, size); + public void setImage(TLObject path, String filter, Bitmap thumb, int size) { + setImage(path, null, filter, null, thumb, null, null, size); } - public void setImage(String path, String filter, Drawable placeholder) { - setImage(null, path, filter, placeholder, null, 0); + public void setImage(TLObject path, String filter, TLRPC.FileLocation thumb, int size) { + setImage(path, null, filter, null, null, thumb, null, size); } - public void setImage(TLObject path, String httpUrl, String filter, Drawable placeholder, Bitmap placeholderBitmap, int size) { - Drawable placeholderDrawable = null; - if (placeholderBitmap != null) { - placeholderDrawable = new BitmapDrawable(null, placeholderBitmap); - } else if (placeholder != null) { - placeholderDrawable = placeholder; + public void setImage(String path, String filter, Drawable thumb) { + setImage(null, path, filter, thumb, null, null, null, 0); + } + + public void setImage(TLObject path, String httpUrl, String filter, Drawable thumb, Bitmap thumbBitmap, TLRPC.FileLocation thumbLocation, String thumbFilter, int size) { + if (thumbBitmap != null) { + thumb = new BitmapDrawable(null, thumbBitmap); } - imageReceiver.setImage(path, httpUrl, filter, placeholderDrawable, null, size, false); + imageReceiver.setImage(path, httpUrl, filter, thumb, thumbLocation, thumbFilter, size, false); } public void setImageBitmap(Bitmap bitmap) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java new file mode 100644 index 00000000..60c2522e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java @@ -0,0 +1,98 @@ +/* + * This is the source code of Telegram for Android v. 2.0.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2014. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +import org.telegram.android.AndroidUtilities; + +public class LineProgressView extends View { + + private long lastUpdateTime = 0; + private float currentProgress = 0; + private float animationProgressStart = 0; + private long currentProgressTime = 0; + private float animatedProgressValue = 0; + private float animatedAlphaValue = 1.0f; + + private static DecelerateInterpolator decelerateInterpolator = null; + private static Paint progressPaint = null; + + public LineProgressView(Context context) { + super(context); + + if (decelerateInterpolator == null) { + decelerateInterpolator = new DecelerateInterpolator(); + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setStyle(Paint.Style.STROKE); + progressPaint.setStrokeCap(Paint.Cap.ROUND); + progressPaint.setStrokeWidth(AndroidUtilities.dp(2)); + progressPaint.setColor(0xff36a2ee); + } + } + + private void updateAnimation() { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + lastUpdateTime = newTime; + + if (animatedProgressValue != 1 && animatedProgressValue != currentProgress) { + float progressDiff = currentProgress - animationProgressStart; + if (progressDiff > 0) { + currentProgressTime += dt; + if (currentProgressTime >= 300) { + animatedProgressValue = currentProgress; + animationProgressStart = currentProgress; + currentProgressTime = 0; + } else { + animatedProgressValue = animationProgressStart + progressDiff * decelerateInterpolator.getInterpolation(currentProgressTime / 300.0f); + } + } + invalidate(); + } + if (animatedProgressValue >= 1 && animatedProgressValue == 1 && animatedAlphaValue != 0) { + animatedAlphaValue -= dt / 200.0f; + if (animatedAlphaValue <= 0) { + animatedAlphaValue = 0.0f; + } + invalidate(); + } + } + + public void setProgressColor(int color) { + progressPaint.setColor(color); + } + + public void setProgress(float value, boolean animated) { + if (!animated) { + animatedProgressValue = value; + animationProgressStart = value; + } else { + animationProgressStart = animatedProgressValue; + } + if (value != 1) { + animatedAlphaValue = 1; + } + currentProgress = value; + currentProgressTime = 0; + + lastUpdateTime = System.currentTimeMillis(); + invalidate(); + } + + public void onDraw(Canvas canvas) { + progressPaint.setAlpha((int)(255 * animatedAlphaValue)); + canvas.drawRect(0, 0, getWidth() * animatedProgressValue, getHeight(), progressPaint); + updateAnimation(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java index 8595aa08..97dbea03 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java @@ -289,7 +289,7 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, ContactsController.getInstance().getInviteText()); - getParentActivity().startActivity(intent); + getParentActivity().startActivity(Intent.createChooser(intent, "")); } catch (Exception e) { FileLog.e("tmessages", e); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java index 66b99d28..177c9ae0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java @@ -36,11 +36,10 @@ import org.telegram.messenger.Utilities; import org.telegram.ui.Adapters.BaseFragmentAdapter; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; -import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.AnimationCompat.AnimatorSetProxy; import org.telegram.ui.AnimationCompat.ObjectAnimatorProxy; -import org.telegram.ui.Cells.TextDetailDocumentsCell; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Cells.SharedDocumentCell; import java.io.BufferedReader; import java.io.File; @@ -63,13 +62,13 @@ public class DocumentSelectActivity extends BaseFragment { private TextView emptyView; private File currentDir; - private ArrayList items = new ArrayList(); + private ArrayList items = new ArrayList<>(); private boolean receiverRegistered = false; - private ArrayList history = new ArrayList(); + private ArrayList history = new ArrayList<>(); private long sizeLimit = 1024 * 1024 * 1024; private DocumentSelectActivityDelegate delegate; - private HashMap selectedFiles = new HashMap(); - private ArrayList actionModeViews = new ArrayList(); + private HashMap selectedFiles = new HashMap<>(); + private ArrayList actionModeViews = new ArrayList<>(); private boolean scrolling; private final static int done = 3; @@ -152,27 +151,19 @@ public class DocumentSelectActivity extends BaseFragment { public void onItemClick(int id) { if (id == -1) { finishFragment(); - } else if (id == 1) { - if (delegate != null) { - delegate.startDocumentSelectActivity(); - } - finishFragment(false); } else if (id == -2) { selectedFiles.clear(); actionBar.hideActionMode(); listView.invalidateViews(); } else if (id == done) { if (delegate != null) { - ArrayList files = new ArrayList(); + ArrayList files = new ArrayList<>(); files.addAll(selectedFiles.keySet()); delegate.didSelectFiles(DocumentSelectActivity.this, files); } } } }); - ActionBarMenu menu = actionBar.createMenu(); - final ActionBarMenuItem item = menu.addItem(1, R.drawable.ic_ab_other); - selectedFiles.clear(); actionModeViews.clear(); @@ -254,7 +245,7 @@ public class DocumentSelectActivity extends BaseFragment { selectedMessagesCountTextView.setText(String.format("%d", selectedFiles.size())); if (Build.VERSION.SDK_INT >= 11) { AnimatorSetProxy animatorSet = new AnimatorSetProxy(); - ArrayList animators = new ArrayList(); + ArrayList animators = new ArrayList<>(); for (int a = 0; a < actionModeViews.size(); a++) { View view2 = actionModeViews.get(a); AndroidUtilities.clearDrawableAnimation(view2); @@ -269,8 +260,8 @@ public class DocumentSelectActivity extends BaseFragment { animatorSet.start(); } scrolling = false; - if (view instanceof TextDetailDocumentsCell) { - ((TextDetailDocumentsCell) view).setChecked(true, true); + if (view instanceof SharedDocumentCell) { + ((SharedDocumentCell) view).setChecked(true, true); } actionBar.showActionMode(); } @@ -287,24 +278,32 @@ public class DocumentSelectActivity extends BaseFragment { ListItem item = items.get(i); File file = item.file; if (file == null) { - HistoryEntry he = history.remove(history.size() - 1); - actionBar.setTitle(he.title); - if (he.dir != null) { - listFiles(he.dir); + if (item.icon == R.drawable.ic_storage_gallery) { + if (delegate != null) { + delegate.startDocumentSelectActivity(); + } + finishFragment(false); } else { - listRoots(); + HistoryEntry he = history.remove(history.size() - 1); + actionBar.setTitle(he.title); + if (he.dir != null) { + listFiles(he.dir); + } else { + listRoots(); + } + listView.setSelectionFromTop(he.scrollItem, he.scrollOffset); } - listView.setSelectionFromTop(he.scrollItem, he.scrollOffset); } else if (file.isDirectory()) { HistoryEntry he = new HistoryEntry(); he.scrollItem = listView.getFirstVisiblePosition(); he.scrollOffset = listView.getChildAt(0).getTop(); he.dir = currentDir; he.title = actionBar.getTitle().toString(); + history.add(he); if (!listFiles(file)) { + history.remove(he); return; } - history.add(he); actionBar.setTitle(item.title); listView.setSelection(0); } else { @@ -333,12 +332,12 @@ public class DocumentSelectActivity extends BaseFragment { selectedMessagesCountTextView.setText(String.format("%d", selectedFiles.size())); } scrolling = false; - if (view instanceof TextDetailDocumentsCell) { - ((TextDetailDocumentsCell) view).setChecked(selectedFiles.containsKey(item.file.toString()), true); + if (view instanceof SharedDocumentCell) { + ((SharedDocumentCell) view).setChecked(selectedFiles.containsKey(item.file.toString()), true); } } else { if (delegate != null) { - ArrayList files = new ArrayList(); + ArrayList files = new ArrayList<>(); files.add(file.getAbsolutePath()); delegate.didSelectFiles(DocumentSelectActivity.this, files); } @@ -465,7 +464,16 @@ public class DocumentSelectActivity extends BaseFragment { } ListItem item = new ListItem(); item.title = ".."; - item.subtitle = LocaleController.getString("Folder", R.string.Folder); + if (history.size() > 0) { + HistoryEntry entry = history.get(history.size() - 1); + if (entry.dir == null) { + item.subtitle = LocaleController.getString("Folder", R.string.Folder); + } else { + item.subtitle = entry.dir.toString(); + } + } else { + item.subtitle = LocaleController.getString("Folder", R.string.Folder); + } item.icon = R.drawable.ic_directory; item.file = null; items.add(0, item); @@ -499,8 +507,8 @@ public class DocumentSelectActivity extends BaseFragment { try { BufferedReader reader = new BufferedReader(new FileReader("/proc/mounts")); String line; - HashMap> aliases = new HashMap>(); - ArrayList result = new ArrayList(); + HashMap> aliases = new HashMap<>(); + ArrayList result = new ArrayList<>(); String extDevice = null; while ((line = reader.readLine()) != null) { if ((!line.contains("/mnt") && !line.contains("/storage") && !line.contains("/sdcard")) || line.contains("asec") || line.contains("tmpfs") || line.contains("none")) { @@ -560,6 +568,13 @@ public class DocumentSelectActivity extends BaseFragment { FileLog.e("tmessages", e); } + fs = new ListItem(); + fs.title = LocaleController.getString("Gallery", R.string.Gallery); + fs.subtitle = LocaleController.getString("GalleryInfo", R.string.GalleryInfo); + fs.icon = R.drawable.ic_storage_gallery; + fs.file = null; + items.add(fs); + AndroidUtilities.clearDrawableAnimation(listView); scrolling = true; listAdapter.notifyDataSetChanged(); @@ -608,15 +623,15 @@ public class DocumentSelectActivity extends BaseFragment { @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { - convertView = new TextDetailDocumentsCell(mContext); + convertView = new SharedDocumentCell(mContext); } - TextDetailDocumentsCell textDetailCell = (TextDetailDocumentsCell) convertView; + SharedDocumentCell textDetailCell = (SharedDocumentCell) convertView; ListItem item = items.get(position); if (item.icon != 0) { - ((TextDetailDocumentsCell) convertView).setTextAndValueAndTypeAndThumb(item.title, item.subtitle, null, null, item.icon); + ((SharedDocumentCell) convertView).setTextAndValueAndTypeAndThumb(item.title, item.subtitle, null, null, item.icon); } else { String type = item.ext.toUpperCase().substring(0, Math.min(item.ext.length(), 4)); - ((TextDetailDocumentsCell) convertView).setTextAndValueAndTypeAndThumb(item.title, item.subtitle, type, item.thumb, 0); + ((SharedDocumentCell) convertView).setTextAndValueAndTypeAndThumb(item.title, item.subtitle, type, item.thumb, 0); } if (item.file != null && actionBar.isActionModeShowed()) { textDetailCell.setChecked(selectedFiles.containsKey(item.file.toString()), !scrolling); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java index ebcf3133..983d43cd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java @@ -8,10 +8,13 @@ package org.telegram.ui; +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.database.DataSetObserver; +import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.view.PagerAdapter; @@ -114,7 +117,13 @@ public class IntroActivity extends Activity { } viewPager = (ViewPager)findViewById(R.id.intro_view_pager); TextView startMessagingButton = (TextView) findViewById(R.id.start_messaging_button); - startMessagingButton.setText(LocaleController.getString("StartMessaging", R.string.StartMessaging)); + startMessagingButton.setText(LocaleController.getString("StartMessaging", R.string.StartMessaging).toUpperCase()); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[] {android.R.attr.state_pressed}, ObjectAnimator.ofFloat(startMessagingButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[] {}, ObjectAnimator.ofFloat(startMessagingButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + startMessagingButton.setStateListAnimator(animator); + } topImage1 = (ImageView)findViewById(R.id.icon_image1); topImage2 = (ImageView)findViewById(R.id.icon_image2); bottomPages = (ViewGroup)findViewById(R.id.bottom_pages); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 67f84d0a..ea916e6a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -276,7 +276,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, ContactsController.getInstance().getInviteText()); - startActivity(intent); + startActivity(Intent.createChooser(intent, "")); } catch (Exception e) { FileLog.e("tmessages", e); } @@ -735,6 +735,14 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa } else if (showDialogsList) { if (!AndroidUtilities.isTablet()) { actionBarLayout.removeAllFragments(); + } else { + if (!layersActionBarLayout.fragmentsStack.isEmpty()) { + for (int a = 0; a < layersActionBarLayout.fragmentsStack.size() - 1; a++) { + layersActionBarLayout.removeFragmentFromStack(layersActionBarLayout.fragmentsStack.get(0)); + a--; + } + layersActionBarLayout.closeLastFragment(false); + } } pushOpened = false; isNew = false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index 73adbf3b..2814c8b3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -843,7 +843,7 @@ public class LoginActivity extends BaseFragment { needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); } else if (error.text.startsWith("FLOOD_WAIT")) { needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { + } else if (error.code != -1000) { needShowAlert(error.text); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java index ad2a0ce1..811f0f3e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java @@ -9,10 +9,18 @@ package org.telegram.ui; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; import android.graphics.Bitmap; +import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -20,43 +28,86 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.webkit.MimeTypeMap; import android.widget.AbsListView; import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.GridView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.ProgressBar; import android.widget.TextView; import org.telegram.android.AndroidUtilities; import org.telegram.android.LocaleController; +import org.telegram.android.MessagesController; +import org.telegram.android.SendMessagesHelper; +import org.telegram.android.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLoader; import org.telegram.messenger.TLRPC; import org.telegram.android.MessageObject; -import org.telegram.android.MessagesController; import org.telegram.android.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.Adapters.BaseFragmentAdapter; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.AnimationCompat.AnimatorSetProxy; +import org.telegram.ui.AnimationCompat.ObjectAnimatorProxy; +import org.telegram.ui.Cells.LoadingCell; +import org.telegram.ui.Cells.SharedDocumentCell; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; public class MediaActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { private GridView listView; + private ListView mediaListView; private ListAdapter listAdapter; - private ArrayList messages = new ArrayList<>(); - private HashMap messagesDict = new HashMap<>(); + private SharedDocumentsAdapter documentsAdapter; + private LinearLayout progressView; + private TextView emptyTextView; + private ImageView emptyImageView; + private LinearLayout emptyView; + private TextView dropDown; + private ActionBarMenuItem dropDownContainer; + private ActionBarMenuItem searchItem; + private TextView selectedMessagesCountTextView; + private ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout; + + private HashMap selectedFiles = new HashMap<>(); + private ArrayList actionModeViews = new ArrayList<>(); + private boolean scrolling; + private long dialog_id; - private int totalCount = 0; + private int selectedMode; private int itemWidth = 100; - private boolean loading = false; - private boolean endReached = false; - private boolean cacheEndReached = false; - private int max_id = Integer.MAX_VALUE; - private View progressView; - private TextView emptyView; + + private class SharedMediaData { + private ArrayList messages = new ArrayList<>(); + private HashMap messagesDict = new HashMap<>(); + private int totalCount; + private boolean loading; + private boolean endReached; + private boolean cacheEndReached; + private int max_id; + } + + private SharedMediaData sharedMediaData[] = new SharedMediaData[3]; + + private final static int shared_media_item = 1; + private final static int files_item = 2; + private final static int forward = 3; + private final static int delete = 4; public MediaActivity(Bundle args) { super(args); @@ -70,11 +121,12 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceivedNewMessages); NotificationCenter.getInstance().addObserver(this, NotificationCenter.messageReceivedByServer); dialog_id = getArguments().getLong("dialog_id", 0); - if (((int)dialog_id) == 0) { - max_id = Integer.MIN_VALUE; + for (int a = 0; a < sharedMediaData.length; a++) { + sharedMediaData[a] = new SharedMediaData(); + sharedMediaData[a].max_id = ((int)dialog_id) == 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE; } - loading = true; - MessagesController.getInstance().loadMedia(dialog_id, 0, 50, 0, true, classGuid); + sharedMediaData[0].loading = true; + SharedMediaQuery.loadMedia(dialog_id, 0, 50, 0, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); return true; } @@ -91,8 +143,8 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No public View createView(LayoutInflater inflater, ViewGroup container) { if (fragmentView == null) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setAllowOverlayTitle(true); - actionBar.setTitle(LocaleController.getString("SharedMedia", R.string.SharedMedia)); + actionBar.setTitle(""); + actionBar.setAllowOverlayTitle(false); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { @@ -103,42 +155,380 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No listAdapter = null; } finishFragment(); + } else if (id == -2) { + selectedFiles.clear(); + actionBar.hideActionMode(); + mediaListView.invalidateViews(); + } else if (id == shared_media_item) { + if (selectedMode == 0) { + return; + } + selectedMode = 0; + switchToCurrentSelectedMode(); + } else if (id == files_item) { + if (selectedMode == 1) { + return; + } + selectedMode = 1; + switchToCurrentSelectedMode(); + } else if (id == delete) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("files", selectedFiles.size()))); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + ArrayList ids = new ArrayList<>(selectedFiles.keySet()); + ArrayList random_ids = null; + TLRPC.EncryptedChat currentEncryptedChat = null; + if ((int) dialog_id == 0) { + currentEncryptedChat = MessagesController.getInstance().getEncryptedChat((int) (dialog_id >> 32)); + } + if (currentEncryptedChat != null) { + random_ids = new ArrayList<>(); + for (HashMap.Entry entry : selectedFiles.entrySet()) { + MessageObject msg = entry.getValue(); + if (msg.messageOwner.random_id != 0 && msg.type != 10) { + random_ids.add(msg.messageOwner.random_id); + } + } + } + MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat); + actionBar.hideActionMode(); + selectedFiles.clear(); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showAlertDialog(builder); + } else if (id == forward) { + Bundle args = new Bundle(); + args.putBoolean("onlySelect", true); + args.putBoolean("serverOnly", true); + args.putString("selectAlertString", LocaleController.getString("ForwardMessagesTo", R.string.ForwardMessagesTo)); + args.putString("selectAlertStringGroup", LocaleController.getString("ForwardMessagesToGroup", R.string.ForwardMessagesToGroup)); + MessagesActivity fragment = new MessagesActivity(args); + fragment.setDelegate(new MessagesActivity.MessagesActivityDelegate() { + @Override + public void didSelectDialog(MessagesActivity fragment, long did, boolean param) { + int lower_part = (int)did; + if (lower_part != 0) { + Bundle args = new Bundle(); + args.putBoolean("scrollToTopOnResume", true); + if (lower_part > 0) { + args.putInt("user_id", lower_part); + } else if (lower_part < 0) { + args.putInt("chat_id", -lower_part); + } + + ArrayList ids = new ArrayList<>(selectedFiles.keySet()); + Collections.sort(ids); + for (Integer id : ids) { + if (id > 0) { + SendMessagesHelper.getInstance().sendMessage(selectedFiles.get(id), did); + } + } + selectedFiles.clear(); + actionBar.hideActionMode(); + + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + ChatActivity chatActivity = new ChatActivity(args); + presentFragment(chatActivity, true); + + if (!AndroidUtilities.isTablet()) { + removeSelfFromStack(); + Activity parentActivity = getParentActivity(); + if (parentActivity == null) { + parentActivity = chatActivity.getParentActivity(); + } + if (parentActivity != null) { + parentActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } + } + } else { + fragment.finishFragment(); + } + } + }); + presentFragment(fragment); } } }); - fragmentView = inflater.inflate(R.layout.media_layout, container, false); + selectedFiles.clear(); + actionModeViews.clear(); - emptyView = (TextView)fragmentView.findViewById(R.id.searchEmptyView); - emptyView.setText(LocaleController.getString("NoMedia", R.string.NoMedia)); - emptyView.setOnTouchListener(new View.OnTouchListener() { + final ActionBarMenu menu = actionBar.createMenu(); + searchItem = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchExpand() { + dropDownContainer.setVisibility(View.GONE); + } + + @Override + public void onSearchCollapse() { + dropDownContainer.setVisibility(View.VISIBLE); + } + + @Override + public void onTextChanged(EditText editText) { + + } + }); + searchItem.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); + searchItem.setVisibility(View.GONE); + + dropDownContainer = new ActionBarMenuItem(getParentActivity(), menu, R.drawable.bar_selector); + dropDownContainer.setSubMenuOpenSide(1); + dropDownContainer.addSubItem(shared_media_item, LocaleController.getString("SharedMedia", R.string.SharedMedia), 0); + dropDownContainer.addSubItem(files_item, LocaleController.getString("DocumentsTitle", R.string.DocumentsTitle), 0); + actionBar.addView(dropDownContainer); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) dropDownContainer.getLayoutParams(); + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams.rightMargin = AndroidUtilities.dp(40); + layoutParams.leftMargin = AndroidUtilities.isTablet() ? AndroidUtilities.dp(64) : AndroidUtilities.dp(56); + layoutParams.gravity = Gravity.TOP | Gravity.LEFT; + dropDownContainer.setLayoutParams(layoutParams); + dropDownContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + dropDownContainer.toggleSubMenu(); + } + }); + + dropDown = new TextView(getParentActivity()); + dropDown.setGravity(Gravity.LEFT); + dropDown.setSingleLine(true); + dropDown.setLines(1); + dropDown.setMaxLines(1); + dropDown.setEllipsize(TextUtils.TruncateAt.END); + dropDown.setTextColor(0xffffffff); + dropDown.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + dropDown.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_arrow_drop_down, 0); + dropDown.setCompoundDrawablePadding(AndroidUtilities.dp(4)); + dropDown.setPadding(0, 0, AndroidUtilities.dp(10), 0); + dropDownContainer.addView(dropDown); + layoutParams = (FrameLayout.LayoutParams) dropDown.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams.leftMargin = AndroidUtilities.dp(16); + layoutParams.gravity = Gravity.CENTER_VERTICAL; + dropDown.setLayoutParams(layoutParams); + + final ActionBarMenu actionMode = actionBar.createActionMode(); + actionModeViews.add(actionMode.addItem(-2, R.drawable.ic_ab_back_grey, R.drawable.bar_selector_mode, null, AndroidUtilities.dp(54))); + + selectedMessagesCountTextView = new TextView(actionMode.getContext()); + selectedMessagesCountTextView.setTextSize(18); + selectedMessagesCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + selectedMessagesCountTextView.setTextColor(0xff737373); + selectedMessagesCountTextView.setSingleLine(true); + selectedMessagesCountTextView.setLines(1); + selectedMessagesCountTextView.setEllipsize(TextUtils.TruncateAt.END); + selectedMessagesCountTextView.setPadding(AndroidUtilities.dp(11), 0, 0, AndroidUtilities.dp(2)); + selectedMessagesCountTextView.setGravity(Gravity.CENTER_VERTICAL); + selectedMessagesCountTextView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); - listView = (GridView)fragmentView.findViewById(R.id.media_grid); - progressView = fragmentView.findViewById(R.id.progressLayout); + actionMode.addView(selectedMessagesCountTextView); + LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams)selectedMessagesCountTextView.getLayoutParams(); + layoutParams1.weight = 1; + layoutParams1.width = 0; + layoutParams1.height = LinearLayout.LayoutParams.MATCH_PARENT; + selectedMessagesCountTextView.setLayoutParams(layoutParams1); + if ((int) dialog_id != 0) { + actionModeViews.add(actionMode.addItem(forward, R.drawable.ic_ab_fwd_forward, R.drawable.bar_selector_mode, null, AndroidUtilities.dp(54))); + } + actionModeViews.add(actionMode.addItem(delete, R.drawable.ic_ab_fwd_delete, R.drawable.bar_selector_mode, null, AndroidUtilities.dp(54))); + + + FrameLayout frameLayout; + fragmentView = frameLayout = new FrameLayout(getParentActivity()); + fragmentView.setBackgroundColor(0xfff0f0f0); + + mediaListView = new ListView(getParentActivity()); + mediaListView.setDivider(null); + mediaListView.setDividerHeight(0); + mediaListView.setVerticalScrollBarEnabled(false); + mediaListView.setDrawSelectorOnTop(true); + frameLayout.addView(mediaListView); + layoutParams = (FrameLayout.LayoutParams) mediaListView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.TOP; + mediaListView.setLayoutParams(layoutParams); + mediaListView.setAdapter(documentsAdapter = new SharedDocumentsAdapter(getParentActivity())); + mediaListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, final int i, long l) { + if (view instanceof SharedDocumentCell) { + SharedDocumentCell cell = (SharedDocumentCell) view; + MessageObject message = cell.getDocument(); + if (actionBar.isActionModeShowed()) { + if (selectedFiles.containsKey(message.messageOwner.id)) { + selectedFiles.remove(message.messageOwner.id); + } else { + selectedFiles.put(message.messageOwner.id, message); + } + if (selectedFiles.isEmpty()) { + actionBar.hideActionMode(); + } else { + selectedMessagesCountTextView.setText(String.format("%d", selectedFiles.size())); + } + scrolling = false; + if (view instanceof SharedDocumentCell) { + ((SharedDocumentCell) view).setChecked(selectedFiles.containsKey(message.messageOwner.id), true); + } + } else { + if (cell.isLoaded()) { + File f = null; + String fileName = FileLoader.getAttachFileName(message.messageOwner.media.document); + if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { + f = new File(message.messageOwner.attachPath); + } + if (f == null || f != null && !f.exists()) { + f = FileLoader.getPathToMessage(message.messageOwner); + } + if (f != null && f.exists()) { + String realMimeType = null; + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + MimeTypeMap myMime = MimeTypeMap.getSingleton(); + int idx = fileName.lastIndexOf("."); + if (idx != -1) { + String ext = fileName.substring(idx + 1); + realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); + if (realMimeType == null) { + realMimeType = message.messageOwner.media.document.mime_type; + if (realMimeType == null || realMimeType.length() == 0) { + realMimeType = null; + } + } + if (realMimeType != null) { + intent.setDataAndType(Uri.fromFile(f), realMimeType); + } else { + intent.setDataAndType(Uri.fromFile(f), "text/plain"); + } + } else { + intent.setDataAndType(Uri.fromFile(f), "text/plain"); + } + if (realMimeType != null) { + try { + getParentActivity().startActivity(intent); + } catch (Exception e) { + intent.setDataAndType(Uri.fromFile(f), "text/plain"); + getParentActivity().startActivity(intent); + } + } else { + getParentActivity().startActivity(intent); + } + } catch (Exception e) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.messageOwner.media.document.mime_type)); + showAlertDialog(builder); + } + } + } else if (!cell.isLoading()) { + FileLoader.getInstance().loadFile(cell.getDocument().messageOwner.media.document, true, false); + cell.updateFileExistIcon(); + } else { + FileLoader.getInstance().cancelLoadFile(cell.getDocument().messageOwner.media.document); + cell.updateFileExistIcon(); + } + } + } + } + }); + mediaListView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + scrolling = scrollState != SCROLL_STATE_IDLE; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !sharedMediaData[1].loading && !sharedMediaData[1].endReached) { + sharedMediaData[1].loading = true; + SharedMediaQuery.loadMedia(dialog_id, 0, 50, sharedMediaData[1].max_id, SharedMediaQuery.MEDIA_FILE, !sharedMediaData[1].cacheEndReached, classGuid); + } + } + }); + mediaListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int i, long id) { + if (actionBar.isActionModeShowed() || i < 0 || i >= sharedMediaData[1].messages.size()) { + return false; + } + MessageObject item = sharedMediaData[1].messages.get(i); + selectedFiles.put(item.messageOwner.id, item); + selectedMessagesCountTextView.setText(String.format("%d", selectedFiles.size())); + if (Build.VERSION.SDK_INT >= 11) { + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + ArrayList animators = new ArrayList<>(); + for (int a = 0; a < actionModeViews.size(); a++) { + View view2 = actionModeViews.get(a); + AndroidUtilities.clearDrawableAnimation(view2); + if (a < 1) { + animators.add(ObjectAnimatorProxy.ofFloat(view2, "translationX", -AndroidUtilities.dp(56), 0)); + } else { + animators.add(ObjectAnimatorProxy.ofFloat(view2, "scaleY", 0.1f, 1.0f)); + } + } + animatorSet.playTogether(animators); + animatorSet.setDuration(250); + animatorSet.start(); + } + scrolling = false; + if (view instanceof SharedDocumentCell) { + ((SharedDocumentCell) view).setChecked(true, true); + } + actionBar.showActionMode(); + return true; + } + }); + + listView = new GridView(getParentActivity()); + listView.setPadding(AndroidUtilities.dp(2), 0, AndroidUtilities.dp(2), AndroidUtilities.dp(2)); + listView.setClipToPadding(false); + listView.setDrawSelectorOnTop(true); + listView.setVerticalSpacing(AndroidUtilities.dp(4)); + listView.setHorizontalSpacing(AndroidUtilities.dp(4)); + listView.setSelector(R.drawable.list_selector); + listView.setGravity(Gravity.CENTER); + listView.setNumColumns(GridView.AUTO_FIT); + listView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); + frameLayout.addView(listView); + layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + listView.setLayoutParams(layoutParams); listView.setAdapter(listAdapter = new ListAdapter(getParentActivity())); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int i, long l) { - if (i < 0 || i >= messages.size()) { + if (i < 0 || i >= sharedMediaData[selectedMode].messages.size()) { return; } - PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhoto(messages, i, MediaActivity.this); + if (selectedMode == 0) { + PhotoViewer.getInstance().setParentActivity(getParentActivity()); + PhotoViewer.getInstance().openPhoto(sharedMediaData[selectedMode].messages, i, MediaActivity.this); + } else if (selectedMode == 1) { + + } } }); - if (loading && messages.isEmpty()) { - progressView.setVisibility(View.VISIBLE); - listView.setEmptyView(null); - } else { - progressView.setVisibility(View.GONE); - listView.setEmptyView(emptyView); - } - listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int i) { @@ -147,12 +537,67 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !loading && !endReached) { - loading = true; - MessagesController.getInstance().loadMedia(dialog_id, 0, 50, max_id, !cacheEndReached, classGuid); + if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !sharedMediaData[0].loading && !sharedMediaData[0].endReached) { + sharedMediaData[0].loading = true; + SharedMediaQuery.loadMedia(dialog_id, 0, 50, sharedMediaData[0].max_id, SharedMediaQuery.MEDIA_PHOTOVIDEO, !sharedMediaData[0].cacheEndReached, classGuid); } } }); + + emptyView = new LinearLayout(getParentActivity()); + emptyView.setOrientation(LinearLayout.VERTICAL); + emptyView.setGravity(Gravity.CENTER); + emptyView.setVisibility(View.GONE); + frameLayout.addView(emptyView); + layoutParams = (FrameLayout.LayoutParams) emptyView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + emptyView.setLayoutParams(layoutParams); + emptyView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + + emptyImageView = new ImageView(getParentActivity()); + emptyView.addView(emptyImageView); + layoutParams1 = (LinearLayout.LayoutParams) emptyImageView.getLayoutParams(); + layoutParams1.width = LinearLayout.LayoutParams.WRAP_CONTENT; + layoutParams1.height = LinearLayout.LayoutParams.WRAP_CONTENT; + emptyImageView.setLayoutParams(layoutParams1); + + emptyTextView = new TextView(getParentActivity()); + emptyTextView.setTextColor(0xff8a8a8a); + emptyTextView.setGravity(Gravity.CENTER); + emptyTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + emptyTextView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), AndroidUtilities.dp(128)); + emptyView.addView(emptyTextView); + layoutParams1 = (LinearLayout.LayoutParams) emptyTextView.getLayoutParams(); + layoutParams1.topMargin = AndroidUtilities.dp(24); + layoutParams1.width = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams1.height = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams1.gravity = Gravity.CENTER; + emptyTextView.setLayoutParams(layoutParams1); + + progressView = new LinearLayout(getParentActivity()); + progressView.setGravity(Gravity.CENTER); + progressView.setOrientation(LinearLayout.VERTICAL); + progressView.setVisibility(View.GONE); + frameLayout.addView(progressView); + layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + progressView.setLayoutParams(layoutParams); + + ProgressBar progressBar = new ProgressBar(getParentActivity()); + progressView.addView(progressBar); + layoutParams1 = (LinearLayout.LayoutParams) progressBar.getLayoutParams(); + layoutParams1.width = LinearLayout.LayoutParams.WRAP_CONTENT; + layoutParams1.height = LinearLayout.LayoutParams.WRAP_CONTENT; + progressBar.setLayoutParams(layoutParams1); + + switchToCurrentSelectedMode(); } else { ViewGroup parent = (ViewGroup)fragmentView.getParent(); if (parent != null) { @@ -166,98 +611,124 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.mediaDidLoaded) { - long uid = (Long)args[0]; - int guid = (Integer)args[4]; + long uid = (Long) args[0]; + int guid = (Integer) args[4]; + int type = (Integer) args[5]; if (uid == dialog_id && guid == classGuid) { - loading = false; - totalCount = (Integer)args[1]; - @SuppressWarnings("uchecked") - ArrayList arr = (ArrayList)args[2]; + sharedMediaData[type].loading = false; + sharedMediaData[type].totalCount = (Integer) args[1]; + ArrayList arr = (ArrayList) args[2]; boolean added = false; - boolean enc = ((int)dialog_id) == 0; + boolean enc = ((int) dialog_id) == 0; for (MessageObject message : arr) { - if (!messagesDict.containsKey(message.messageOwner.id)) { + if (!sharedMediaData[type].messagesDict.containsKey(message.messageOwner.id)) { if (!enc) { if (message.messageOwner.id > 0) { - max_id = Math.min(message.messageOwner.id, max_id); + sharedMediaData[type].max_id = Math.min(message.messageOwner.id, sharedMediaData[type].max_id); } } else { - max_id = Math.max(message.messageOwner.id, max_id); + sharedMediaData[type].max_id = Math.max(message.messageOwner.id, sharedMediaData[type].max_id); } - messagesDict.put(message.messageOwner.id, message); - messages.add(message); + sharedMediaData[type].messagesDict.put(message.messageOwner.id, message); + sharedMediaData[type].messages.add(message); added = true; } } if (!added) { - endReached = true; + sharedMediaData[type].endReached = true; } - cacheEndReached = !(Boolean)args[3]; + sharedMediaData[type].cacheEndReached = !(Boolean) args[3]; if (progressView != null) { progressView.setVisibility(View.GONE); } - if (listView != null) { - if (listView.getEmptyView() == null) { - listView.setEmptyView(emptyView); + if (type == 0) { + if (listView != null) { + if (listView.getEmptyView() == null) { + listView.setEmptyView(emptyView); + } + } + } else if (type == 1) { + if (mediaListView != null) { + if (mediaListView.getEmptyView() == null) { + mediaListView.setEmptyView(emptyView); + } } } if (listAdapter != null) { listAdapter.notifyDataSetChanged(); } + if (documentsAdapter != null) { + scrolling = true; + documentsAdapter.notifyDataSetChanged(); + } } } else if (id == NotificationCenter.messagesDeleted) { - @SuppressWarnings("unchecked") - ArrayList markAsDeletedMessages = (ArrayList)args[0]; + ArrayList markAsDeletedMessages = (ArrayList) args[0]; boolean updated = false; for (Integer ids : markAsDeletedMessages) { - MessageObject obj = messagesDict.get(ids); - if (obj != null) { - messages.remove(obj); - messagesDict.remove(ids); - totalCount--; - updated = true; + for (SharedMediaData data : sharedMediaData) { + MessageObject obj = data.messagesDict.get(ids); + if (obj != null) { + data.messages.remove(obj); + data.messagesDict.remove(ids); + data.totalCount--; + updated = true; + } } } if (updated && listAdapter != null) { listAdapter.notifyDataSetChanged(); } + if (documentsAdapter != null) { + scrolling = true; + documentsAdapter.notifyDataSetChanged(); + } } else if (id == NotificationCenter.didReceivedNewMessages) { - long uid = (Long)args[0]; + long uid = (Long) args[0]; if (uid == dialog_id) { boolean markAsRead = false; - @SuppressWarnings("unchecked") - ArrayList arr = (ArrayList)args[1]; + ArrayList arr = (ArrayList) args[1]; for (MessageObject obj : arr) { - if (obj.messageOwner.media == null || !(obj.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) && !(obj.messageOwner.media instanceof TLRPC.TL_messageMediaVideo)) { + if (obj.messageOwner.media == null) { continue; } - if (messagesDict.containsKey(obj.messageOwner.id)) { + int type = SharedMediaQuery.getMediaType(obj.messageOwner); + if (type == -1) { + return; + } + if (sharedMediaData[type].messagesDict.containsKey(obj.messageOwner.id)) { continue; } - boolean enc = ((int)dialog_id) == 0; + boolean enc = ((int) dialog_id) == 0; if (!enc) { if (obj.messageOwner.id > 0) { - max_id = Math.min(obj.messageOwner.id, max_id); + sharedMediaData[type].max_id = Math.min(obj.messageOwner.id, sharedMediaData[type].max_id); } } else { - max_id = Math.max(obj.messageOwner.id, max_id); + sharedMediaData[type].max_id = Math.max(obj.messageOwner.id, sharedMediaData[type].max_id); } - messagesDict.put(obj.messageOwner.id, obj); - messages.add(0, obj); + sharedMediaData[type].messagesDict.put(obj.messageOwner.id, obj); + sharedMediaData[type].messages.add(0, obj); } if (listAdapter != null) { listAdapter.notifyDataSetChanged(); } + if (documentsAdapter != null) { + scrolling = true; + documentsAdapter.notifyDataSetChanged(); + } } } else if (id == NotificationCenter.messageReceivedByServer) { - Integer msgId = (Integer)args[0]; - MessageObject obj = messagesDict.get(msgId); - if (obj != null) { - Integer newMsgId = (Integer)args[1]; - messagesDict.remove(msgId); - messagesDict.put(newMsgId, obj); - obj.messageOwner.id = newMsgId; + Integer msgId = (Integer) args[0]; + for (SharedMediaData data : sharedMediaData) { + MessageObject obj = data.messagesDict.get(msgId); + if (obj != null) { + Integer newMsgId = (Integer) args[1]; + data.messagesDict.remove(msgId); + data.messagesDict.put(newMsgId, obj); + obj.messageOwner.id = newMsgId; + } } } } @@ -268,6 +739,10 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No if (listAdapter != null) { listAdapter.notifyDataSetChanged(); } + if (documentsAdapter != null) { + scrolling = true; + documentsAdapter.notifyDataSetChanged(); + } fixLayout(); } @@ -289,10 +764,10 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No BackupImageView imageView = (BackupImageView)view.findViewById(R.id.media_photo_image); if (imageView != null) { int num = (Integer)imageView.getTag(); - if (num < 0 || num >= messages.size()) { + if (num < 0 || num >= sharedMediaData[0].messages.size()) { continue; } - MessageObject message = messages.get(num); + MessageObject message = sharedMediaData[0].messages.get(num); if (message != null && message.messageOwner.id == messageObject.messageOwner.id) { int coords[] = new int[2]; imageView.getLocationInWindow(coords); @@ -335,6 +810,56 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public int getSelectedCount() { return 0; } + private void switchToCurrentSelectedMode() { + if (selectedMode == 0) { + mediaListView.setEmptyView(null); + mediaListView.setVisibility(View.GONE); + mediaListView.setAdapter(null); + + listView.setAdapter(listAdapter); + + dropDown.setText(LocaleController.getString("SharedMedia", R.string.SharedMedia)); + emptyImageView.setImageResource(R.drawable.tip1); + emptyTextView.setText(LocaleController.getString("NoMedia", R.string.NoMedia)); + searchItem.setVisibility(View.GONE); + if (sharedMediaData[selectedMode].loading && sharedMediaData[selectedMode].messages.isEmpty()) { + progressView.setVisibility(View.VISIBLE); + listView.setEmptyView(null); + emptyView.setVisibility(View.GONE); + } else { + progressView.setVisibility(View.GONE); + listView.setEmptyView(emptyView); + } + listView.setVisibility(View.VISIBLE); + } else if (selectedMode == 1) { + listView.setEmptyView(null); + listView.setVisibility(View.GONE); + listView.setAdapter(null); + + mediaListView.setAdapter(documentsAdapter); + + dropDown.setText(LocaleController.getString("DocumentsTitle", R.string.DocumentsTitle)); + int lower_id = (int) dialog_id; + emptyImageView.setImageResource(R.drawable.tip2); + emptyTextView.setText(LocaleController.getString("NoSharedFiles", R.string.NoSharedFiles)); + //searchItem.setVisibility(View.VISIBLE); + if (!sharedMediaData[1].loading && !sharedMediaData[1].endReached && sharedMediaData[1].messages.isEmpty()) { + sharedMediaData[selectedMode].loading = true; + SharedMediaQuery.loadMedia(dialog_id, 0, 50, 0, SharedMediaQuery.MEDIA_FILE, true, classGuid); + } + mediaListView.setVisibility(View.VISIBLE); + + if (sharedMediaData[selectedMode].loading && sharedMediaData[selectedMode].messages.isEmpty()) { + progressView.setVisibility(View.VISIBLE); + mediaListView.setEmptyView(null); + emptyView.setVisibility(View.GONE); + } else { + progressView.setVisibility(View.GONE); + mediaListView.setEmptyView(emptyView); + } + } + } + private void fixLayout() { if (listView != null) { ViewTreeObserver obs = listView.getViewTreeObserver(); @@ -348,30 +873,121 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No listView.setNumColumns(4); itemWidth = AndroidUtilities.dp(490) / 4 - AndroidUtilities.dp(2) * 3; listView.setColumnWidth(itemWidth); + emptyTextView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), AndroidUtilities.dp(128)); } else { if (rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90) { listView.setNumColumns(6); itemWidth = AndroidUtilities.displaySize.x / 6 - AndroidUtilities.dp(2) * 5; listView.setColumnWidth(itemWidth); + emptyTextView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), 0); } else { listView.setNumColumns(4); itemWidth = AndroidUtilities.displaySize.x / 4 - AndroidUtilities.dp(2) * 3; listView.setColumnWidth(itemWidth); + emptyTextView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), AndroidUtilities.dp(128)); } } listView.setPadding(listView.getPaddingLeft(), AndroidUtilities.dp(4), listView.getPaddingRight(), listView.getPaddingBottom()); listAdapter.notifyDataSetChanged(); + listView.getViewTreeObserver().removeOnPreDrawListener(this); - if (listView != null) { - listView.getViewTreeObserver().removeOnPreDrawListener(this); + if (dropDownContainer != null) { + if (!AndroidUtilities.isTablet()) { + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) dropDownContainer.getLayoutParams(); + layoutParams.topMargin = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + dropDownContainer.setLayoutParams(layoutParams); + } + + if (!AndroidUtilities.isTablet() && ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + dropDown.setTextSize(18); + } else { + dropDown.setTextSize(20); + } } - return false; } }); } } + private class SharedDocumentsAdapter extends BaseFragmentAdapter { + private Context mContext; + + public SharedDocumentsAdapter(Context context) { + mContext = context; + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int i) { + return i != sharedMediaData[1].messages.size(); + } + + @Override + public int getCount() { + return sharedMediaData[1].messages.size() + (sharedMediaData[1].messages.isEmpty() || sharedMediaData[1].endReached ? 0 : 1); + } + + @Override + public Object getItem(int i) { + return null; + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + int type = getItemViewType(i); + if (type == 0) { + if (view == null) { + view = new SharedDocumentCell(mContext); + } + SharedDocumentCell sharedDocumentCell = (SharedDocumentCell) view; + sharedDocumentCell.setDocument(sharedMediaData[1].messages.get(i), i != sharedMediaData[1].messages.size() - 1 || sharedMediaData[1].loading); + if (actionBar.isActionModeShowed()) { + sharedDocumentCell.setChecked(selectedFiles.containsKey(sharedMediaData[1].messages.get(i).messageOwner.id), !scrolling); + } else { + sharedDocumentCell.setChecked(false, !scrolling); + } + } else if (type == 1) { + if (view == null) { + view = new LoadingCell(mContext); + } + } + return view; + } + + @Override + public int getItemViewType(int i) { + if (i == sharedMediaData[1].messages.size()) { + return 1; + } + return 0; + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public boolean isEmpty() { + return sharedMediaData[1].messages.isEmpty(); + } + } + private class ListAdapter extends BaseFragmentAdapter { private Context mContext; @@ -386,12 +1002,12 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public boolean isEnabled(int i) { - return i != messages.size(); + return i != sharedMediaData[0].messages.size(); } @Override public int getCount() { - return messages.size() + (messages.isEmpty() || endReached ? 0 : 1); + return sharedMediaData[0].messages.size() + (sharedMediaData[0].messages.isEmpty() || sharedMediaData[0].endReached ? 0 : 1); } @Override @@ -406,14 +1022,14 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public boolean hasStableIds() { - return false; + return true; } @Override public View getView(int i, View view, ViewGroup viewGroup) { int type = getItemViewType(i); if (type == 0) { - MessageObject message = messages.get(i); + MessageObject message = sharedMediaData[0].messages.get(i); if (view == null) { LayoutInflater li = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = li.inflate(R.layout.media_photo_layout, viewGroup, false); @@ -426,20 +1042,18 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No BackupImageView imageView = (BackupImageView)view.findViewById(R.id.media_photo_image); imageView.setTag(i); + imageView.imageReceiver.setParentMessageObject(message); + imageView.imageReceiver.setNeedsQualityThumb(true); + imageView.imageReceiver.setShouldGenerateQualityThumb(true); if (message.messageOwner.media != null && message.messageOwner.media.photo != null && !message.messageOwner.media.photo.sizes.isEmpty()) { - ArrayList sizes = message.messageOwner.media.photo.sizes; - if (message.imagePreview != null) { - imageView.setImageBitmap(message.imagePreview); - } else { - TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.messageOwner.media.photo.sizes, 80); - imageView.setImage(photoSize.location, null, mContext.getResources().getDrawable(R.drawable.photo_placeholder_in)); - } + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, 80); + imageView.setImage(null, null, null, mContext.getResources().getDrawable(R.drawable.photo_placeholder_in), null, photoSize.location, "b", 0); } else { imageView.setImageResource(R.drawable.photo_placeholder_in); } imageView.imageReceiver.setVisible(!PhotoViewer.getInstance().isShowingImage(message), false); } else if (type == 1) { - MessageObject message = messages.get(i); + MessageObject message = sharedMediaData[0].messages.get(i); if (view == null) { LayoutInflater li = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = li.inflate(R.layout.media_video_layout, viewGroup, false); @@ -453,16 +1067,16 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No BackupImageView imageView = (BackupImageView)view.findViewById(R.id.media_photo_image); imageView.setTag(i); + imageView.imageReceiver.setParentMessageObject(message); + imageView.imageReceiver.setNeedsQualityThumb(true); + imageView.imageReceiver.setShouldGenerateQualityThumb(true); if (message.messageOwner.media.video != null && message.messageOwner.media.video.thumb != null) { int duration = message.messageOwner.media.video.duration; int minutes = duration / 60; int seconds = duration - minutes * 60; textView.setText(String.format("%d:%02d", minutes, seconds)); - if (message.imagePreview != null) { - imageView.setImageBitmap(message.imagePreview); - } else { - imageView.setImage(message.messageOwner.media.video.thumb.location, null, mContext.getResources().getDrawable(R.drawable.photo_placeholder_in)); - } + TLRPC.FileLocation location = message.messageOwner.media.video.thumb.location; + imageView.setImage(null, null, null, mContext.getResources().getDrawable(R.drawable.photo_placeholder_in), null, location, "b", 0); textView.setVisibility(View.VISIBLE); } else { textView.setVisibility(View.GONE); @@ -484,10 +1098,10 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public int getItemViewType(int i) { - if (i == messages.size()) { + if (i == sharedMediaData[0].messages.size()) { return 2; } - MessageObject message = messages.get(i); + MessageObject message = sharedMediaData[0].messages.get(i); if (message.messageOwner.media instanceof TLRPC.TL_messageMediaVideo) { return 1; } @@ -501,7 +1115,7 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No @Override public boolean isEmpty() { - return messages.isEmpty(); + return sharedMediaData[0].messages.isEmpty(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MessagesActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MessagesActivity.java index 4846282c..d79c4cfe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MessagesActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MessagesActivity.java @@ -8,9 +8,12 @@ package org.telegram.ui; +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.res.Configuration; +import android.graphics.Outline; import android.os.Build; import android.os.Bundle; import android.view.Gravity; @@ -18,6 +21,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.AbsListView; @@ -102,6 +106,8 @@ public class MessagesActivity extends BaseFragment implements NotificationCenter NotificationCenter.getInstance().addObserver(this, NotificationCenter.contactsDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.appDidLogout); NotificationCenter.getInstance().addObserver(this, NotificationCenter.openedChatChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.notificationsSettingsUpdated); + if (getArguments() != null) { onlySelect = arguments.getBoolean("onlySelect", false); serverOnly = arguments.getBoolean("serverOnly", false); @@ -126,6 +132,7 @@ public class MessagesActivity extends BaseFragment implements NotificationCenter NotificationCenter.getInstance().removeObserver(this, NotificationCenter.contactsDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.appDidLogout); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.openedChatChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.notificationsSettingsUpdated); delegate = null; } @@ -273,8 +280,21 @@ public class MessagesActivity extends BaseFragment implements NotificationCenter textView = (TextView)fragmentView.findViewById(R.id.search_empty_text); textView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - floatingButton = (ImageView)fragmentView.findViewById(R.id.floating_button); + floatingButton = (ImageView) fragmentView.findViewById(R.id.floating_button); floatingButton.setVisibility(onlySelect ? View.GONE : View.VISIBLE); + floatingButton.setScaleType(ImageView.ScaleType.CENTER); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[] {android.R.attr.state_pressed}, ObjectAnimator.ofFloat(floatingButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[] {}, ObjectAnimator.ofFloat(floatingButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + floatingButton.setStateListAnimator(animator); + floatingButton.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)floatingButton.getLayoutParams(); layoutParams.leftMargin = LocaleController.isRTL ? AndroidUtilities.dp(14) : 0; layoutParams.rightMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(14); @@ -595,6 +615,10 @@ public class MessagesActivity extends BaseFragment implements NotificationCenter } updateVisibleRows(MessagesController.UPDATE_MASK_SELECT_DIALOG); } + } else if (id == NotificationCenter.notificationsSettingsUpdated) { + if (messagesListView != null) { + updateVisibleRows(0); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java index 1a5eba5f..61646ad1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java @@ -281,7 +281,9 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati ArrayList photos = new ArrayList<>(); for (HashMap.Entry entry : selectedPhotos.entrySet()) { MediaController.PhotoEntry photoEntry = entry.getValue(); - if (photoEntry.path != null) { + if (photoEntry.imagePath != null) { + photos.add(photoEntry.imagePath); + } else if (photoEntry.path != null) { photos.add(photoEntry.path); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java index 82622951..910b4f44 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java @@ -35,8 +35,8 @@ import java.io.File; public class PhotoCropActivity extends BaseFragment { - public interface PhotoCropActivityDelegate { - public abstract void didFinishCrop(Bitmap bitmap); + public interface PhotoEditActivityDelegate { + public abstract void didFinishEdit(Bitmap bitmap, Bundle args); } private class PhotoCropView extends FrameLayout { @@ -44,12 +44,14 @@ public class PhotoCropActivity extends BaseFragment { Paint rectPaint = null; Paint circlePaint = null; Paint halfPaint = null; - float rectSize = 600; + float rectSizeX = 600; + float rectSizeY = 600; float rectX = -1, rectY = -1; int draggingState = 0; float oldX = 0, oldY = 0; int bitmapWidth, bitmapHeight, bitmapX, bitmapY; int viewWidth, viewHeight; + boolean freeform; public PhotoCropView(Context context) { super(context); @@ -68,14 +70,14 @@ public class PhotoCropActivity extends BaseFragment { private void init() { rectPaint = new Paint(); - rectPaint.setColor(0xfffafafa); + rectPaint.setColor(0x3ffafafa); rectPaint.setStrokeWidth(AndroidUtilities.dp(2)); rectPaint.setStyle(Paint.Style.STROKE); circlePaint = new Paint(); - circlePaint.setColor(0x7fffffff); + circlePaint.setColor(0xffffffff); halfPaint = new Paint(); - halfPaint.setColor(0x3f000000); - setBackgroundColor(0xff000000); + halfPaint.setColor(0xc8000000); + setBackgroundColor(0xff333333); setOnTouchListener(new OnTouchListener() { @Override @@ -86,13 +88,13 @@ public class PhotoCropActivity extends BaseFragment { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide < y && rectY + cornerSide > y) { draggingState = 1; - } else if (rectX - cornerSide + rectSize < x && rectX + cornerSide + rectSize > x && rectY - cornerSide < y && rectY + cornerSide > y) { + } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide < y && rectY + cornerSide > y) { draggingState = 2; - } else if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide + rectSize < y && rectY + cornerSide + rectSize > y) { + } else if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { draggingState = 3; - } else if (rectX - cornerSide + rectSize < x && rectX + cornerSide + rectSize > x && rectY - cornerSide + rectSize < y && rectY + cornerSide + rectSize > y) { + } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { draggingState = 4; - } else if (rectX < x && rectX + rectSize > x && rectY < y && rectY + rectSize > y) { + } else if (rectX < x && rectX + rectSizeX > x && rectY < y && rectY + rectSizeY > y) { draggingState = 5; } else { draggingState = 0; @@ -113,61 +115,115 @@ public class PhotoCropActivity extends BaseFragment { if (rectX < bitmapX) { rectX = bitmapX; - } else if (rectX + rectSize > bitmapX + bitmapWidth) { - rectX = bitmapX + bitmapWidth - rectSize; + } else if (rectX + rectSizeX > bitmapX + bitmapWidth) { + rectX = bitmapX + bitmapWidth - rectSizeX; } if (rectY < bitmapY) { rectY = bitmapY; - } else if (rectY + rectSize > bitmapY + bitmapHeight) { - rectY = bitmapY + bitmapHeight - rectSize; + } else if (rectY + rectSizeY > bitmapY + bitmapHeight) { + rectY = bitmapY + bitmapHeight - rectSizeY; } - } else if (draggingState == 1) { - if (rectSize - diffX < 160) { - diffX = rectSize - 160; - } - if (rectX + diffX < bitmapX) { - diffX = bitmapX - rectX; - } - if (rectY + diffX < bitmapY) { - diffX = bitmapY - rectY; - } - rectX += diffX; - rectY += diffX; - rectSize -= diffX; - } else if (draggingState == 2) { - if (rectSize + diffX < 160) { - diffX = -(rectSize - 160); - } - if (rectX + rectSize + diffX > bitmapX + bitmapWidth) { - diffX = bitmapX + bitmapWidth - rectX - rectSize; - } - if (rectY - diffX < bitmapY) { - diffX = rectY - bitmapY; - } - rectY -= diffX; - rectSize += diffX; - } else if (draggingState == 3) { - if (rectSize - diffX < 160) { - diffX = rectSize - 160; - } - if (rectX + diffX < bitmapX) { - diffX = bitmapX - rectX; - } - if (rectY + rectSize - diffX > bitmapY + bitmapHeight) { - diffX = rectY + rectSize - bitmapY - bitmapHeight; - } - rectX += diffX; - rectSize -= diffX; - } else if (draggingState == 4) { - if (rectX + rectSize + diffX > bitmapX + bitmapWidth) { - diffX = bitmapX + bitmapWidth - rectX - rectSize; - } - if (rectY + rectSize + diffX > bitmapY + bitmapHeight) { - diffX = bitmapY + bitmapHeight - rectY - rectSize; - } - rectSize += diffX; - if (rectSize < 160) { - rectSize = 160; + } else { + if (draggingState == 1) { + if (rectSizeX - diffX < 160) { + diffX = rectSizeX - 160; + } + if (rectX + diffX < bitmapX) { + diffX = bitmapX - rectX; + } + if (!freeform) { + if (rectY + diffX < bitmapY) { + diffX = bitmapY - rectY; + } + rectX += diffX; + rectY += diffX; + rectSizeX -= diffX; + rectSizeY -= diffX; + } else { + if (rectSizeY - diffY < 160) { + diffY = rectSizeY - 160; + } + if (rectY + diffY < bitmapY) { + diffY = bitmapY - rectY; + } + rectX += diffX; + rectY += diffY; + rectSizeX -= diffX; + rectSizeY -= diffY; + } + } else if (draggingState == 2) { + if (rectSizeX + diffX < 160) { + diffX = -(rectSizeX - 160); + } + if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { + diffX = bitmapX + bitmapWidth - rectX - rectSizeX; + } + if (!freeform) { + if (rectY - diffX < bitmapY) { + diffX = rectY - bitmapY; + } + rectY -= diffX; + rectSizeX += diffX; + rectSizeY += diffX; + } else { + if (rectSizeY - diffY < 160) { + diffY = rectSizeY - 160; + } + if (rectY + diffY < bitmapY) { + diffY = bitmapY - rectY; + } + rectY += diffY; + rectSizeX += diffX; + rectSizeY -= diffY; + } + } else if (draggingState == 3) { + if (rectSizeX - diffX < 160) { + diffX = rectSizeX - 160; + } + if (rectX + diffX < bitmapX) { + diffX = bitmapX - rectX; + } + if (!freeform) { + if (rectY + rectSizeX - diffX > bitmapY + bitmapHeight) { + diffX = rectY + rectSizeX - bitmapY - bitmapHeight; + } + rectX += diffX; + rectSizeX -= diffX; + rectSizeY -= diffX; + } else { + if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { + diffY = bitmapY + bitmapHeight - rectY - rectSizeY; + } + rectX += diffX; + rectSizeX -= diffX; + rectSizeY += diffY; + if (rectSizeY < 160) { + rectSizeY = 160; + } + } + } else if (draggingState == 4) { + if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { + diffX = bitmapX + bitmapWidth - rectX - rectSizeX; + } + if (!freeform) { + if (rectY + rectSizeX + diffX > bitmapY + bitmapHeight) { + diffX = bitmapY + bitmapHeight - rectY - rectSizeX; + } + rectSizeX += diffX; + rectSizeY += diffX; + } else { + if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { + diffY = bitmapY + bitmapHeight - rectY - rectSizeY; + } + rectSizeX += diffX; + rectSizeY += diffY; + } + if (rectSizeX < 160) { + rectSizeX = 160; + } + if (rectSizeY < 160) { + rectSizeY = 160; + } } } @@ -186,7 +242,8 @@ public class PhotoCropActivity extends BaseFragment { } float percX = (rectX - bitmapX) / bitmapWidth; float percY = (rectY - bitmapY) / bitmapHeight; - float percSize = rectSize / bitmapWidth; + float percSizeX = rectSizeX / bitmapWidth; + float percSizeY = rectSizeY / bitmapHeight; float w = imageToCrop.getWidth(); float h = imageToCrop.getHeight(); float scaleX = viewWidth / w; @@ -198,23 +255,33 @@ public class PhotoCropActivity extends BaseFragment { bitmapWidth = viewWidth; bitmapHeight = (int)Math.ceil(h * scaleX); } - bitmapX = (viewWidth - bitmapWidth) / 2; - bitmapY = (viewHeight - bitmapHeight) / 2; + bitmapX = (viewWidth - bitmapWidth) / 2 + AndroidUtilities.dp(14); + bitmapY = (viewHeight - bitmapHeight) / 2 + AndroidUtilities.dp(14); if (rectX == -1 && rectY == -1) { - if (bitmapWidth > bitmapHeight) { + if (freeform) { rectY = bitmapY; - rectX = (viewWidth - bitmapHeight) / 2; - rectSize = bitmapHeight; - } else { rectX = bitmapX; - rectY = (viewHeight - bitmapWidth) / 2; - rectSize = bitmapWidth; + rectSizeX = bitmapWidth; + rectSizeY = bitmapHeight; + } else { + if (bitmapWidth > bitmapHeight) { + rectY = bitmapY; + rectX = (viewWidth - bitmapHeight) / 2 + AndroidUtilities.dp(14); + rectSizeX = bitmapHeight; + rectSizeY = bitmapHeight; + } else { + rectX = bitmapX; + rectY = (viewHeight - bitmapWidth) / 2 + AndroidUtilities.dp(14); + rectSizeX = bitmapWidth; + rectSizeY = bitmapWidth; + } } } else { rectX = percX * bitmapWidth + bitmapX; rectY = percY * bitmapHeight + bitmapY; - rectSize = percSize * bitmapWidth; + rectSizeX = percSizeX * bitmapWidth; + rectSizeY = percSizeY * bitmapHeight; } invalidate(); } @@ -222,31 +289,33 @@ public class PhotoCropActivity extends BaseFragment { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - viewWidth = right - left; - viewHeight = bottom - top; + viewWidth = right - left - AndroidUtilities.dp(28); + viewHeight = bottom - top - AndroidUtilities.dp(28); updateBitmapSize(); } public Bitmap getBitmap() { float percX = (rectX - bitmapX) / bitmapWidth; float percY = (rectY - bitmapY) / bitmapHeight; - float percSize = rectSize / bitmapWidth; + float percSizeX = rectSizeX / bitmapWidth; + float percSizeY = rectSizeY / bitmapWidth; int x = (int)(percX * imageToCrop.getWidth()); int y = (int)(percY * imageToCrop.getHeight()); - int size = (int)(percSize * imageToCrop.getWidth()); - if (x + size > imageToCrop.getWidth()) { - size = imageToCrop.getWidth() - x; + int sizeX = (int)(percSizeX * imageToCrop.getWidth()); + int sizeY = (int)(percSizeY * imageToCrop.getWidth()); + if (x + sizeX > imageToCrop.getWidth()) { + sizeX = imageToCrop.getWidth() - x; } - if (y + size > imageToCrop.getHeight()) { - size = imageToCrop.getHeight() - y; + if (y + sizeY > imageToCrop.getHeight()) { + sizeY = imageToCrop.getHeight() - y; } try { - return Bitmap.createBitmap(imageToCrop, x, y, size, size); + return Bitmap.createBitmap(imageToCrop, x, y, sizeX, sizeY); } catch (Throwable e) { FileLog.e("tmessags", e); System.gc(); try { - return Bitmap.createBitmap(imageToCrop, x, y, size, size); + return Bitmap.createBitmap(imageToCrop, x, y, sizeX, sizeY); } catch (Throwable e2) { FileLog.e("tmessages", e2); } @@ -261,26 +330,39 @@ public class PhotoCropActivity extends BaseFragment { drawable.draw(canvas); } canvas.drawRect(bitmapX, bitmapY, bitmapX + bitmapWidth, rectY, halfPaint); - canvas.drawRect(bitmapX, rectY, rectX, rectY + rectSize, halfPaint); - canvas.drawRect(rectX + rectSize, rectY, bitmapX + bitmapWidth, rectY + rectSize, halfPaint); - canvas.drawRect(bitmapX, rectY + rectSize, bitmapX + bitmapWidth, bitmapY + bitmapHeight, halfPaint); + canvas.drawRect(bitmapX, rectY, rectX, rectY + rectSizeY, halfPaint); + canvas.drawRect(rectX + rectSizeX, rectY, bitmapX + bitmapWidth, rectY + rectSizeY, halfPaint); + canvas.drawRect(bitmapX, rectY + rectSizeY, bitmapX + bitmapWidth, bitmapY + bitmapHeight, halfPaint); - canvas.drawRect(rectX, rectY, rectX + rectSize, rectY + rectSize, rectPaint); + canvas.drawRect(rectX, rectY, rectX + rectSizeX, rectY + rectSizeY, rectPaint); - int side = AndroidUtilities.dp(7); - canvas.drawRect(rectX - side, rectY - side, rectX + side, rectY + side, circlePaint); - canvas.drawRect(rectX + rectSize - side, rectY - side, rectX + rectSize + side, rectY + side, circlePaint); - canvas.drawRect(rectX - side, rectY + rectSize - side, rectX + side, rectY + rectSize + side, circlePaint); - canvas.drawRect(rectX + rectSize - side, rectY + rectSize - side, rectX + rectSize + side, rectY + rectSize + side, circlePaint); + int side = AndroidUtilities.dp(1); + canvas.drawRect(rectX + side, rectY + side, rectX + side + AndroidUtilities.dp(20), rectY + side * 3, circlePaint); + canvas.drawRect(rectX + side, rectY + side, rectX + side * 3, rectY + side + AndroidUtilities.dp(20), circlePaint); + + canvas.drawRect(rectX + rectSizeX - side - AndroidUtilities.dp(20), rectY + side, rectX + rectSizeX - side, rectY + side * 3, circlePaint); + canvas.drawRect(rectX + rectSizeX - side * 3, rectY + side, rectX + rectSizeX - side, rectY + side + AndroidUtilities.dp(20), circlePaint); + + canvas.drawRect(rectX + side, rectY + rectSizeY - side - AndroidUtilities.dp(20), rectX + side * 3, rectY + rectSizeY - side, circlePaint); + canvas.drawRect(rectX + side, rectY + rectSizeY - side * 3, rectX + side + AndroidUtilities.dp(20), rectY + rectSizeY - side, circlePaint); + + canvas.drawRect(rectX + rectSizeX - side - AndroidUtilities.dp(20), rectY + rectSizeY - side * 3, rectX + rectSizeX - side, rectY + rectSizeY - side, circlePaint); + canvas.drawRect(rectX + rectSizeX - side * 3, rectY + rectSizeY - side - AndroidUtilities.dp(20), rectX + rectSizeX - side, rectY + rectSizeY - side, circlePaint); + + for (int a = 1; a < 3; a++) { + canvas.drawRect(rectX + rectSizeX / 3 * a, rectY + side, rectX + side + rectSizeX / 3 * a, rectY + rectSizeY - side, circlePaint); + canvas.drawRect(rectX + side, rectY + rectSizeY / 3 * a, rectX - side + rectSizeX, rectY + rectSizeY / 3 * a + side, circlePaint); + } } } private Bitmap imageToCrop; private BitmapDrawable drawable; - private PhotoCropActivityDelegate delegate = null; + private PhotoEditActivityDelegate delegate = null; private PhotoCropView view; private boolean sameBitmap = false; private boolean doneButtonPressed = false; + private String bitmapKey; private final static int done_button = 1; @@ -288,29 +370,40 @@ public class PhotoCropActivity extends BaseFragment { super(args); } + public PhotoCropActivity(Bundle args, Bitmap bitmap, String key) { + super(args); + imageToCrop = bitmap; + bitmapKey = key; + if (imageToCrop != null && key != null) { + ImageLoader.getInstance().incrementUseCount(key); + } + } + @Override public boolean onFragmentCreate() { swipeBackEnabled = false; - String photoPath = getArguments().getString("photoPath"); - Uri photoUri = getArguments().getParcelable("photoUri"); - if (photoPath == null && photoUri == null) { - return false; - } - if (photoPath != null) { - File f = new File(photoPath); - if (!f.exists()) { + if (imageToCrop == null) { + String photoPath = getArguments().getString("photoPath"); + Uri photoUri = getArguments().getParcelable("photoUri"); + if (photoPath == null && photoUri == null) { + return false; + } + if (photoPath != null) { + File f = new File(photoPath); + if (!f.exists()) { + return false; + } + } + int size = 0; + if (AndroidUtilities.isTablet()) { + size = AndroidUtilities.dp(520); + } else { + size = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); + } + imageToCrop = ImageLoader.loadBitmap(photoPath, photoUri, size, size, true); + if (imageToCrop == null) { return false; } - } - int size = 0; - if (AndroidUtilities.isTablet()) { - size = AndroidUtilities.dp(520); - } else { - size = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); - } - imageToCrop = ImageLoader.loadBitmap(photoPath, photoUri, size, size); - if (imageToCrop == null) { - return false; } drawable = new BitmapDrawable(imageToCrop); super.onFragmentCreate(); @@ -321,7 +414,12 @@ public class PhotoCropActivity extends BaseFragment { public void onFragmentDestroy() { super.onFragmentDestroy(); drawable = null; - if (imageToCrop != null && !sameBitmap) { + if (bitmapKey != null) { + if (ImageLoader.getInstance().decrementUseCount(bitmapKey) && !ImageLoader.getInstance().isInCache(bitmapKey)) { + bitmapKey = null; + } + } + if (bitmapKey == null && imageToCrop != null && !sameBitmap) { imageToCrop.recycle(); imageToCrop = null; } @@ -330,6 +428,8 @@ public class PhotoCropActivity extends BaseFragment { @Override public View createView(LayoutInflater inflater, ViewGroup container) { if (fragmentView == null) { + actionBar.setBackgroundColor(0xff333333); + actionBar.setItemsBackground(R.drawable.bar_selector_picker); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); actionBar.setTitle(LocaleController.getString("CropImage", R.string.CropImage)); @@ -344,7 +444,7 @@ public class PhotoCropActivity extends BaseFragment { if (bitmap == imageToCrop) { sameBitmap = true; } - delegate.didFinishCrop(bitmap); + delegate.didFinishEdit(bitmap, getArguments()); doneButtonPressed = true; } finishFragment(); @@ -356,6 +456,7 @@ public class PhotoCropActivity extends BaseFragment { menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); fragmentView = view = new PhotoCropView(getParentActivity()); + ((PhotoCropView) fragmentView).freeform = getArguments().getBoolean("freeform", false); fragmentView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); } else { ViewGroup parent = (ViewGroup)fragmentView.getParent(); @@ -366,7 +467,7 @@ public class PhotoCropActivity extends BaseFragment { return fragmentView; } - public void setDelegate(PhotoCropActivityDelegate delegate) { + public void setDelegate(PhotoEditActivityDelegate delegate) { this.delegate = delegate; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoEditorActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoEditorActivity.java new file mode 100644 index 00000000..5e0ff8f4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoEditorActivity.java @@ -0,0 +1,1285 @@ +/* + * This is the source code of Telegram for Android v. 2.0.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2014. + */ + +package org.telegram.ui; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.net.Uri; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.opengl.GLUtils; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.SeekBar; + +import org.telegram.android.AndroidUtilities; +import org.telegram.android.ImageLoader; +import org.telegram.android.LocaleController; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.AnimationCompat.AnimatorListenerAdapterProxy; +import org.telegram.ui.AnimationCompat.AnimatorSetProxy; +import org.telegram.ui.AnimationCompat.ObjectAnimatorProxy; +import org.telegram.ui.AnimationCompat.ViewProxy; +import org.telegram.ui.Cells.PhotoEditToolCell; +import org.telegram.ui.Components.RecyclerListView; + +import java.io.File; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import javax.microedition.khronos.opengles.GL10; + +public class PhotoEditorActivity extends BaseFragment { + + private GLSurfaceView glView; + private PhotoCropView cropView; + + private SeekBar valueSeekBar; + private LinearLayout toolsView; + private LinearLayout cropButtonsView; + private ImageView imageView; + private ImageView filtersButton; + private ImageView toolButton; + private AnimatorSetProxy rotationAnimation; + + private ActionBarMenuItem doneButton; + private ActionBarMenuItem sizeButton; + private ActionBarMenuItem rotateButton; + + private boolean sameBitmap = false; + private int currentMode = 0; + private boolean freeformCrop; + private boolean onlyCrop; + + private PhotoCropActivity.PhotoEditActivityDelegate delegate; + + private int selectedTool = 0; + private int rotateDegree = 0; + + private Bitmap bitmapToEdit; + private String bitmapKey; + + private float highlightsValue = 0; //0 100 + private float contrastValue = 0; //-100 100 + private float shadowsValue = 0; //0 100 + private float exposureValue = 0; //-100 100 + private float saturationValue = 0; //-100 100 + private float warmthValue = 0; //-100 100 + private float vignetteValue = 0; //0 100 + private float grainValue = 0; //0 100 + private float width = 0; + private float height = 0; + + private boolean donePressed = false; + + private final static int done_button = 1; + private final static int rotate_button = 2; + private final static int size_button = 3; + + private class PhotoCropView extends FrameLayout { + + private Paint rectPaint; + private Paint circlePaint; + private Paint halfPaint; + private Paint shadowPaint; + private float rectSizeX = 600; + private float rectSizeY = 600; + private int draggingState = 0; + private float oldX = 0, oldY = 0; + private int bitmapWidth = 1, bitmapHeight = 1, bitmapX, bitmapY; + private float rectX = -1, rectY = -1; + + public PhotoCropView(Context context) { + super(context); + + rectPaint = new Paint(); + rectPaint.setColor(0xb2ffffff); + rectPaint.setStrokeWidth(AndroidUtilities.dp(2)); + rectPaint.setStyle(Paint.Style.STROKE); + circlePaint = new Paint(); + circlePaint.setColor(0xffffffff); + halfPaint = new Paint(); + halfPaint.setColor(0x7f000000); + shadowPaint = new Paint(); + shadowPaint.setColor(0x1a000000); + setWillNotDraw(false); + + setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + float x = motionEvent.getX(); + float y = motionEvent.getY(); + int cornerSide = AndroidUtilities.dp(20); + if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { + if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide < y && rectY + cornerSide > y) { + draggingState = 1; + } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide < y && rectY + cornerSide > y) { + draggingState = 2; + } else if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { + draggingState = 3; + } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { + draggingState = 4; + } else if (rectX < x && rectX + rectSizeX > x && rectY < y && rectY + rectSizeY > y) { + draggingState = 5; + } else { + draggingState = 0; + } + if (draggingState != 0) { + PhotoCropView.this.requestDisallowInterceptTouchEvent(true); + } + oldX = x; + oldY = y; + } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + draggingState = 0; + } else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && draggingState != 0) { + float diffX = x - oldX; + float diffY = y - oldY; + if (draggingState == 5) { + rectX += diffX; + rectY += diffY; + + if (rectX < bitmapX) { + rectX = bitmapX; + } else if (rectX + rectSizeX > bitmapX + bitmapWidth) { + rectX = bitmapX + bitmapWidth - rectSizeX; + } + if (rectY < bitmapY) { + rectY = bitmapY; + } else if (rectY + rectSizeY > bitmapY + bitmapHeight) { + rectY = bitmapY + bitmapHeight - rectSizeY; + } + } else { + if (draggingState == 1) { + if (rectSizeX - diffX < 160) { + diffX = rectSizeX - 160; + } + if (rectX + diffX < bitmapX) { + diffX = bitmapX - rectX; + } + if (!freeformCrop) { + if (rectY + diffX < bitmapY) { + diffX = bitmapY - rectY; + } + rectX += diffX; + rectY += diffX; + rectSizeX -= diffX; + rectSizeY -= diffX; + } else { + if (rectSizeY - diffY < 160) { + diffY = rectSizeY - 160; + } + if (rectY + diffY < bitmapY) { + diffY = bitmapY - rectY; + } + rectX += diffX; + rectY += diffY; + rectSizeX -= diffX; + rectSizeY -= diffY; + } + } else if (draggingState == 2) { + if (rectSizeX + diffX < 160) { + diffX = -(rectSizeX - 160); + } + if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { + diffX = bitmapX + bitmapWidth - rectX - rectSizeX; + } + if (!freeformCrop) { + if (rectY - diffX < bitmapY) { + diffX = rectY - bitmapY; + } + rectY -= diffX; + rectSizeX += diffX; + rectSizeY += diffX; + } else { + if (rectSizeY - diffY < 160) { + diffY = rectSizeY - 160; + } + if (rectY + diffY < bitmapY) { + diffY = bitmapY - rectY; + } + rectY += diffY; + rectSizeX += diffX; + rectSizeY -= diffY; + } + } else if (draggingState == 3) { + if (rectSizeX - diffX < 160) { + diffX = rectSizeX - 160; + } + if (rectX + diffX < bitmapX) { + diffX = bitmapX - rectX; + } + if (!freeformCrop) { + if (rectY + rectSizeX - diffX > bitmapY + bitmapHeight) { + diffX = rectY + rectSizeX - bitmapY - bitmapHeight; + } + rectX += diffX; + rectSizeX -= diffX; + rectSizeY -= diffX; + } else { + if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { + diffY = bitmapY + bitmapHeight - rectY - rectSizeY; + } + rectX += diffX; + rectSizeX -= diffX; + rectSizeY += diffY; + if (rectSizeY < 160) { + rectSizeY = 160; + } + } + } else if (draggingState == 4) { + if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { + diffX = bitmapX + bitmapWidth - rectX - rectSizeX; + } + if (!freeformCrop) { + if (rectY + rectSizeX + diffX > bitmapY + bitmapHeight) { + diffX = bitmapY + bitmapHeight - rectY - rectSizeX; + } + rectSizeX += diffX; + rectSizeY += diffX; + } else { + if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { + diffY = bitmapY + bitmapHeight - rectY - rectSizeY; + } + rectSizeX += diffX; + rectSizeY += diffY; + } + if (rectSizeX < 160) { + rectSizeX = 160; + } + if (rectSizeY < 160) { + rectSizeY = 160; + } + } + } + + oldX = x; + oldY = y; + invalidate(); + } + return true; + } + }); + } + + public Bitmap getBitmap() { + float percX = (rectX - bitmapX) / bitmapWidth; + float percY = (rectY - bitmapY) / bitmapHeight; + float percSizeX = rectSizeX / bitmapWidth; + float percSizeY = rectSizeY / bitmapWidth; + int x = (int)(percX * bitmapToEdit.getWidth()); + int y = (int)(percY * bitmapToEdit.getHeight()); + int sizeX = (int)(percSizeX * bitmapToEdit.getWidth()); + int sizeY = (int)(percSizeY * bitmapToEdit.getWidth()); + if (x + sizeX > bitmapToEdit.getWidth()) { + sizeX = bitmapToEdit.getWidth() - x; + } + if (y + sizeY > bitmapToEdit.getHeight()) { + sizeY = bitmapToEdit.getHeight() - y; + } + try { + return Bitmap.createBitmap(bitmapToEdit, x, y, sizeX, sizeY); + } catch (Throwable e) { + FileLog.e("tmessags", e); + System.gc(); + try { + return Bitmap.createBitmap(bitmapToEdit, x, y, sizeX, sizeY); + } catch (Throwable e2) { + FileLog.e("tmessages", e2); + } + } + return null; + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawRect(bitmapX, bitmapY, bitmapX + bitmapWidth, rectY, halfPaint); + canvas.drawRect(bitmapX, rectY, rectX, rectY + rectSizeY, halfPaint); + canvas.drawRect(rectX + rectSizeX, rectY, bitmapX + bitmapWidth, rectY + rectSizeY, halfPaint); + canvas.drawRect(bitmapX, rectY + rectSizeY, bitmapX + bitmapWidth, bitmapY + bitmapHeight, halfPaint); + + int side = AndroidUtilities.dp(1); + canvas.drawRect(rectX - side * 2, rectY - side * 2, rectX - side * 2 + AndroidUtilities.dp(20), rectY, circlePaint); + canvas.drawRect(rectX - side * 2, rectY - side * 2, rectX, rectY - side * 2 + AndroidUtilities.dp(20), circlePaint); + + canvas.drawRect(rectX + rectSizeX + side * 2 - AndroidUtilities.dp(20), rectY - side * 2, rectX + rectSizeX + side * 2, rectY, circlePaint); + canvas.drawRect(rectX + rectSizeX, rectY - side * 2, rectX + rectSizeX + side * 2, rectY - side * 2 + AndroidUtilities.dp(20), circlePaint); + + canvas.drawRect(rectX - side * 2, rectY + rectSizeY + side * 2 - AndroidUtilities.dp(20), rectX, rectY + rectSizeY + side * 2, circlePaint); + canvas.drawRect(rectX - side * 2, rectY + rectSizeY, rectX - side * 2 + AndroidUtilities.dp(20), rectY + rectSizeY + side * 2, circlePaint); + + canvas.drawRect(rectX + rectSizeX + side * 2 - AndroidUtilities.dp(20), rectY + rectSizeY, rectX + rectSizeX + side * 2, rectY + rectSizeY + side * 2, circlePaint); + canvas.drawRect(rectX + rectSizeX, rectY + rectSizeY + side * 2 - AndroidUtilities.dp(20), rectX + rectSizeX + side * 2, rectY + rectSizeY + side * 2, circlePaint); + + for (int a = 1; a < 3; a++) { + canvas.drawRect(rectX + rectSizeX / 3 * a - side, rectY, rectX + side * 2 + rectSizeX / 3 * a, rectY + rectSizeY, shadowPaint); + canvas.drawRect(rectX, rectY + rectSizeY / 3 * a - side, rectX + rectSizeX, rectY + rectSizeY / 3 * a + side * 2, shadowPaint); + } + + for (int a = 1; a < 3; a++) { + canvas.drawRect(rectX + rectSizeX / 3 * a, rectY, rectX + side + rectSizeX / 3 * a, rectY + rectSizeY, circlePaint); + canvas.drawRect(rectX, rectY + rectSizeY / 3 * a, rectX + rectSizeX, rectY + rectSizeY / 3 * a + side, circlePaint); + } + + canvas.drawRect(rectX, rectY, rectX + rectSizeX, rectY + rectSizeY, rectPaint); + } + } + + class MyGLSurfaceView extends GLSurfaceView { + + public MyGLSurfaceView(Context context){ + super(context); + setEGLContextClientVersion(2); + setRenderer(new MyGLRenderer()); + setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + } + } + + public class MyGLRenderer implements GLSurfaceView.Renderer { + + private int trivialShaderProgram; + + private int positionHandle; + private int inputTexCoordHandle; + private int photoImageHandle; + private int shadowsHandle; + private int highlightsHandle; + private int exposureHandle; + private int contrastHandle; + private int saturationHandle; + private int warmthHandle; + private int vignetteHandle; + private int grainHandle; + private int grainWidthHandle; + private int grainHeightHandle; + + private int[] textures = new int[1]; + + private FloatBuffer vertexBuffer; + private FloatBuffer textureBuffer; + private FloatBuffer vertexSaveBuffer; + + private static final String trivialVertexShaderCode = + "attribute vec4 position;" + + "attribute vec4 inputTexCoord;" + + "varying vec2 texCoord;" + + "void main() {" + + "gl_Position = position;" + + "texCoord = inputTexCoord.xy;" + + "}"; + + private static final String trivialFragmentShaderCode = + "varying highp vec2 texCoord;" + + "uniform sampler2D photoImage;" + + "uniform lowp float shadows;" + + "uniform highp float width;" + + "uniform highp float height;" + + "const mediump vec3 hsLuminanceWeighting = vec3(0.3, 0.3, 0.3);" + + "uniform lowp float highlights;" + + "uniform highp float exposure;" + + "uniform lowp float contrast;" + + "const mediump vec3 satLuminanceWeighting = vec3(0.2126, 0.7152, 0.0722);" + + "uniform lowp float saturation;" + + "uniform lowp float warmth;" + + "uniform lowp float grain;" + + "const lowp float permTexUnit = 1.0 / 256.0;" + + "const lowp float permTexUnitHalf = 0.5 / 256.0;" + + "const lowp float grainsize = 2.3;" + + "uniform lowp float vignette;" + + "highp float getLuma(highp vec3 rgbP) { return (0.299 * rgbP.r) + (0.587 * rgbP.g) + (0.114 * rgbP.b); }" + + "highp vec3 rgbToYuv(highp vec3 inP) { highp vec3 outP; outP.r = getLuma(inP); outP.g = (1.0 / 1.772) * (inP.b - outP.r); outP.b = (1.0 / 1.402) * (inP.r - outP.r); return outP; }" + + "lowp vec3 yuvToRgb(highp vec3 inP) { highp float y = inP.r; highp float u = inP.g; highp float v = inP.b; lowp vec3 outP; outP.r = 1.402 * v + y; outP.g = (y - (0.299 * 1.402 / 0.587) * v - (0.114 * 1.772 / 0.587) * u); outP.b = 1.772 * u + y; return outP; } " + + "lowp float easeInOutSigmoid(lowp float value, lowp float strength) { lowp float t = 1.0 / (1.0 - strength); if (value > 0.5) { return 1.0 - pow(2.0 - 2.0 * value, t) * 0.5; } else { return pow(2.0 * value, t) * 0.5; } }" + + "highp vec4 rnm(in highp vec2 tc) { highp float noise = sin(dot(tc,vec2(12.9898,78.233))) * 43758.5453; highp float noiseR = fract(noise)*2.0-1.0; highp float noiseG = fract(noise*1.2154)*2.0-1.0; highp float noiseB = fract(noise*1.3453)*2.0-1.0; " + + "highp float noiseA = fract(noise*1.3647)*2.0-1.0; return vec4(noiseR,noiseG,noiseB,noiseA); } highp float fade(in highp float t) { return t*t*t*(t*(t*6.0-15.0)+10.0); } highp float pnoise3D(in highp vec3 p) { highp vec3 pi = permTexUnit*floor(p)+permTexUnitHalf; " + + "highp vec3 pf = fract(p); highp float perm00 = rnm(pi.xy).a ; highp vec3 grad000 = rnm(vec2(perm00, pi.z)).rgb * 4.0 - 1.0; highp float n000 = dot(grad000, pf); highp vec3 grad001 = rnm(vec2(perm00, pi.z + permTexUnit)).rgb * 4.0 - 1.0; " + + "highp float n001 = dot(grad001, pf - vec3(0.0, 0.0, 1.0)); highp float perm01 = rnm(pi.xy + vec2(0.0, permTexUnit)).a ; highp vec3 grad010 = rnm(vec2(perm01, pi.z)).rgb * 4.0 - 1.0; highp float n010 = dot(grad010, pf - vec3(0.0, 1.0, 0.0));" + + "highp vec3 grad011 = rnm(vec2(perm01, pi.z + permTexUnit)).rgb * 4.0 - 1.0; highp float n011 = dot(grad011, pf - vec3(0.0, 1.0, 1.0)); highp float perm10 = rnm(pi.xy + vec2(permTexUnit, 0.0)).a ;" + + "highp vec3 grad100 = rnm(vec2(perm10, pi.z)).rgb * 4.0 - 1.0; highp float n100 = dot(grad100, pf - vec3(1.0, 0.0, 0.0)); highp vec3 grad101 = rnm(vec2(perm10, pi.z + permTexUnit)).rgb * 4.0 - 1.0;" + + "highp float n101 = dot(grad101, pf - vec3(1.0, 0.0, 1.0)); highp float perm11 = rnm(pi.xy + vec2(permTexUnit, permTexUnit)).a ; highp vec3 grad110 = rnm(vec2(perm11, pi.z)).rgb * 4.0 - 1.0; highp float n110 = dot(grad110, pf - vec3(1.0, 1.0, 0.0));" + + "highp vec3 grad111 = rnm(vec2(perm11, pi.z + permTexUnit)).rgb * 4.0 - 1.0; highp float n111 = dot(grad111, pf - vec3(1.0, 1.0, 1.0)); highp vec4 n_x = mix(vec4(n000, n001, n010, n011), vec4(n100, n101, n110, n111), fade(pf.x));" + + "highp vec2 n_xy = mix(n_x.xy, n_x.zw, fade(pf.y)); highp float n_xyz = mix(n_xy.x, n_xy.y, fade(pf.z)); return n_xyz; } lowp vec2 coordRot(in lowp vec2 tc, in lowp float angle) { lowp float rotX = ((tc.x * 2.0 - 1.0) * cos(angle)) - ((tc.y * 2.0 - 1.0) * sin(angle));" + + "lowp float rotY = ((tc.y * 2.0 - 1.0) * cos(angle)) + ((tc.x * 2.0 - 1.0) * sin(angle)); rotX = rotX * 0.5 + 0.5; rotY = rotY * 0.5 + 0.5; return vec2(rotX,rotY); }void main() {lowp vec4 source = texture2D(photoImage, texCoord);lowp vec4 result = source;" + + "const lowp float toolEpsilon = 0.005;mediump float hsLuminance = dot(result.rgb, hsLuminanceWeighting); mediump float shadow = clamp((pow(hsLuminance, 1.0 / (shadows + 1.0)) + (-0.76) * pow(hsLuminance, 2.0 / (shadows + 1.0))) - hsLuminance, 0.0, 1.0);" + + "mediump float highlight = clamp((1.0 - (pow(1.0 - hsLuminance, 1.0 / (2.0 - highlights)) + (-0.8) * pow(1.0 - hsLuminance, 2.0 / (2.0 - highlights)))) - hsLuminance, -1.0, 0.0);" + + "lowp vec3 shresult = vec3(0.0, 0.0, 0.0) + ((hsLuminance + shadow + highlight) - 0.0) * ((result.rgb - vec3(0.0, 0.0, 0.0)) / (hsLuminance - 0.0)); result = vec4(shresult.rgb, result.a);" + + "if (abs(exposure) > toolEpsilon) { mediump float mag = exposure * 1.045; mediump float exppower = 1.0 + abs(mag); if (mag < 0.0) { exppower = 1.0 / exppower; } result.r = 1.0 - pow((1.0 - result.r), exppower);" + + "result.g = 1.0 - pow((1.0 - result.g), exppower); result.b = 1.0 - pow((1.0 - result.b), exppower); }result = vec4(((result.rgb - vec3(0.5)) * contrast + vec3(0.5)), result.a);" + + "lowp float satLuminance = dot(result.rgb, satLuminanceWeighting); lowp vec3 greyScaleColor = vec3(satLuminance); result = vec4(mix(greyScaleColor, result.rgb, saturation), result.a);" + + "if (abs(warmth) > toolEpsilon) { highp vec3 yuvVec; if (warmth > 0.0 ) { yuvVec = vec3(0.1765, -0.1255, 0.0902); } else { yuvVec = -vec3(0.0588, 0.1569, -0.1255); } highp vec3 yuvColor = rgbToYuv(result.rgb); highp float luma = yuvColor.r;" + + "highp float curveScale = sin(luma * 3.14159); yuvColor += 0.375 * warmth * curveScale * yuvVec; result.rgb = yuvToRgb(yuvColor); }if (abs(grain) > toolEpsilon) { highp vec3 rotOffset = vec3(1.425, 3.892, 5.835);" + + "highp vec2 rotCoordsR = coordRot(texCoord, rotOffset.x); highp vec3 noise = vec3(pnoise3D(vec3(rotCoordsR * vec2(width / grainsize, height / grainsize),0.0))); lowp vec3 lumcoeff = vec3(0.299,0.587,0.114);" + + "lowp float luminance = dot(result.rgb, lumcoeff); lowp float lum = smoothstep(0.2, 0.0, luminance); lum += luminance; noise = mix(noise,vec3(0.0),pow(lum,4.0)); result.rgb = result.rgb + noise * grain; }" + + "if (abs(vignette) > toolEpsilon) { const lowp float midpoint = 0.7; const lowp float fuzziness = 0.62; lowp float radDist = length(texCoord - 0.5) / sqrt(0.5);" + + "lowp float mag = easeInOutSigmoid(radDist * midpoint, fuzziness) * vignette * 0.645; result.rgb = mix(pow(result.rgb, vec3(1.0 / (1.0 - mag))), vec3(0.0), mag * mag); }gl_FragColor = result;}"; + + private int loadShader(int type, String shaderCode) { + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, shaderCode); + GLES20.glCompileShader(shader); + int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + if (compileStatus[0] == 0) { + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + @Override + public void onSurfaceCreated(GL10 gl, javax.microedition.khronos.egl.EGLConfig config) { + + float squareCoordinates[] = { + -1.0f, 1.0f, + 1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f}; + + ByteBuffer bb = ByteBuffer.allocateDirect(squareCoordinates.length * 4); + bb.order(ByteOrder.nativeOrder()); + vertexBuffer = bb.asFloatBuffer(); + vertexBuffer.put(squareCoordinates); + vertexBuffer.position(0); + + float squareCoordinates2[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f}; + + bb = ByteBuffer.allocateDirect(squareCoordinates2.length * 4); + bb.order(ByteOrder.nativeOrder()); + vertexSaveBuffer = bb.asFloatBuffer(); + vertexSaveBuffer.put(squareCoordinates2); + vertexSaveBuffer.position(0); + + float textureCoordinates[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + + bb = ByteBuffer.allocateDirect(textureCoordinates.length * 4); + bb.order(ByteOrder.nativeOrder()); + textureBuffer = bb.asFloatBuffer(); + textureBuffer.put(textureCoordinates); + textureBuffer.position(0); + + GLES20.glGenTextures(1, textures, 0); + gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); + gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmapToEdit, 0); + + int trivialVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, trivialVertexShaderCode); + int trivialFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, trivialFragmentShaderCode); + + if (trivialVertexShader != 0 && trivialFragmentShader != 0) { + trivialShaderProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(trivialShaderProgram, trivialVertexShader); + GLES20.glAttachShader(trivialShaderProgram, trivialFragmentShader); + GLES20.glBindAttribLocation(trivialShaderProgram, 0, "position"); + GLES20.glBindAttribLocation(trivialShaderProgram, 1, "inputTexCoord"); + + GLES20.glLinkProgram(trivialShaderProgram); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(trivialShaderProgram, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] == 0) { + GLES20.glDeleteProgram(trivialShaderProgram); + trivialShaderProgram = 0; + } + } + + if (trivialShaderProgram != 0) { + positionHandle = GLES20.glGetAttribLocation(trivialShaderProgram, "position"); + inputTexCoordHandle = GLES20.glGetAttribLocation(trivialShaderProgram, "inputTexCoord"); + photoImageHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "photoImage"); + shadowsHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "shadows"); + highlightsHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "highlights"); + exposureHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "exposure"); + contrastHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "contrast"); + saturationHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "saturation"); + warmthHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "warmth"); + vignetteHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "vignette"); + grainHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "grain"); + grainWidthHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "width"); + grainHeightHandle = GLES20.glGetUniformLocation(trivialShaderProgram, "height"); + GLES20.glUseProgram(trivialShaderProgram); + } + } + + public void onDrawFrame(GL10 unused) { + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]); + GLES20.glUniform1i(photoImageHandle, 0); + GLES20.glUniform1f(shadowsHandle, getShadowsValue()); + GLES20.glUniform1f(highlightsHandle, getHighlightsValue()); + GLES20.glUniform1f(exposureHandle, getExposureValue()); + GLES20.glUniform1f(contrastHandle, getContrastValue()); + GLES20.glUniform1f(saturationHandle, getSaturationValue()); + GLES20.glUniform1f(warmthHandle, getWarmthValue()); + GLES20.glUniform1f(vignetteHandle, getVignetteValue()); + GLES20.glUniform1f(grainHandle, getGrainValue()); + GLES20.glUniform1f(grainWidthHandle, width); + GLES20.glUniform1f(grainHeightHandle, height); + GLES20.glEnableVertexAttribArray(inputTexCoordHandle); + GLES20.glVertexAttribPointer(inputTexCoordHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); + GLES20.glEnableVertexAttribArray(positionHandle); + if (donePressed) { + GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 8, vertexSaveBuffer); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + final Bitmap bitmap = saveTexture((int)width, (int)height); + donePressed = false; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + delegate.didFinishEdit(bitmap, getArguments()); + finishFragment(); + } + }); + } + GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + } + + public void onSurfaceChanged(GL10 unused, int width, int height) { + GLES20.glViewport(0, 0, width, height); + } + + public Bitmap saveTexture(int width, int height) { + //int[] frame = new int[1]; + //GLES20.glGenFramebuffers(1, frame, 0); + //GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frame[0]); + //GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texture, 0); + ByteBuffer buffer = ByteBuffer.allocate(width * height * 4); + GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.copyPixelsFromBuffer(buffer); + //GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + //GLES20.glDeleteFramebuffers(1, frame, 0); + return bitmap; + } + } + + public PhotoEditorActivity(Bundle args, Bitmap bitmap, String key) { + super(args); + bitmapToEdit = bitmap; + bitmapKey = key; + if (bitmapToEdit != null && key != null) { + ImageLoader.getInstance().incrementUseCount(key); + } + } + + private float getShadowsValue() { + return (shadowsValue / 100.0f) * 0.65f; + } + + private float getHighlightsValue() { + return 1 - (highlightsValue / 100.0f); + } + + private float getExposureValue() { + return (exposureValue / 100.0f); + } + + private float getContrastValue() { + return (contrastValue / 100.0f) * 0.3f + 1; + } + + private float getWarmthValue() { + return warmthValue / 100.0f; + } + + private float getVignetteValue() { + return vignetteValue / 100.0f; + } + + private float getGrainValue() { + return grainValue / 100.0f * 0.04f; + } + + private float getSaturationValue() { + float value = (saturationValue / 100.0f); + if (value < 0) { + value *= 0.55f; + } else { + value *= 1.05f; + } + return value + 1; + } + + @Override + public boolean onFragmentCreate() { + swipeBackEnabled = false; + freeformCrop = getArguments().getBoolean("freeformCrop", false); + onlyCrop = getArguments().getBoolean("onlyCrop", false); + if (bitmapToEdit == null) { + String photoPath = getArguments().getString("photoPath"); + Uri photoUri = getArguments().getParcelable("photoUri"); + if (photoPath == null && photoUri == null) { + return false; + } + if (photoPath != null) { + File f = new File(photoPath); + if (!f.exists()) { + return false; + } + } + int size = 0; + if (AndroidUtilities.isTablet()) { + size = AndroidUtilities.dp(520); + } else { + size = Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); + } + bitmapToEdit = ImageLoader.loadBitmap(photoPath, photoUri, size, size, true); + if (bitmapToEdit == null) { + return false; + } + } + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + if (bitmapKey != null) { + if (ImageLoader.getInstance().decrementUseCount(bitmapKey) && !ImageLoader.getInstance().isInCache(bitmapKey)) { + bitmapKey = null; + } + } + if (bitmapKey == null && bitmapToEdit != null && !sameBitmap) { + bitmapToEdit.recycle(); + bitmapToEdit = null; + } + } + + @Override + public void onPause() { + super.onPause(); + if (glView != null) { + glView.onPause(); + } + } + + @Override + public void onResume() { + super.onResume(); + if (glView != null) { + glView.onResume(); + } + } + + @Override + public View createView(LayoutInflater inflater, ViewGroup container) { + if (fragmentView == null) { + actionBar.setBackgroundColor(0xff262626); + actionBar.setItemsBackground(R.drawable.bar_selector_picker); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("EditImage", R.string.EditImage)); + actionBar.setCastShadows(false); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } else if (id == done_button) { + donePressed = true; + glView.requestRender(); + } else if (id == rotate_button) { + int newRotation = rotateDegree; + newRotation += 90; + fixLayoutInternal(newRotation, true); + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + rotateButton = menu.addItemWithWidth(rotate_button, R.drawable.photo_rotate, AndroidUtilities.dp(56)); + sizeButton = menu.addItemWithWidth(size_button, R.drawable.photo_sizes, AndroidUtilities.dp(56)); + doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + + rotateButton.setVisibility(View.GONE); + sizeButton.setVisibility(View.GONE); + + FrameLayout frameLayout = null; + fragmentView = frameLayout = new FrameLayout(getParentActivity()); + fragmentView.setBackgroundColor(0xff262626); + + imageView = new ImageView(getParentActivity()); + imageView.setScaleType(ImageView.ScaleType.MATRIX); + imageView.setImageBitmap(bitmapToEdit); + frameLayout.addView(imageView); + + cropView = new PhotoCropView(getParentActivity()); + cropView.setVisibility(View.GONE); + frameLayout.addView(cropView); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) cropView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + cropView.setLayoutParams(layoutParams); + + cropButtonsView = new LinearLayout(getParentActivity()); + cropButtonsView.setVisibility(View.GONE); + frameLayout.addView(cropButtonsView); + layoutParams = (FrameLayout.LayoutParams) cropButtonsView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams.height = AndroidUtilities.dp(48); + layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; + cropButtonsView.setLayoutParams(layoutParams); + + ImageView button = new ImageView(getParentActivity()); + button.setScaleType(ImageView.ScaleType.CENTER); + button.setImageResource(R.drawable.ic_close_white); + cropButtonsView.addView(button); + LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams) button.getLayoutParams(); + layoutParams1.width = AndroidUtilities.dp(48); + layoutParams1.height = AndroidUtilities.dp(48); + button.setLayoutParams(layoutParams1); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onlyCrop) { + finishFragment(); + } else { + switchToMode(0, true); + } + } + }); + + button = new ImageView(getParentActivity()); + button.setScaleType(ImageView.ScaleType.CENTER); + button.setImageResource(R.drawable.ic_done); + cropButtonsView.addView(button); + layoutParams1 = (LinearLayout.LayoutParams) button.getLayoutParams(); + layoutParams1.width = AndroidUtilities.dp(48); + layoutParams1.height = AndroidUtilities.dp(48); + layoutParams1.leftMargin = AndroidUtilities.dp(146); + button.setLayoutParams(layoutParams1); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onlyCrop) { + if (delegate != null && currentMode == 1) { + Bitmap bitmap = cropView.getBitmap(); + if (bitmap == bitmapToEdit) { + sameBitmap = true; + } + delegate.didFinishEdit(bitmap, getArguments()); + currentMode = 0; + finishFragment(); + } + } else { + switchToMode(0, false); + } + } + }); + + if (!onlyCrop) { + toolsView = new LinearLayout(getParentActivity()); + frameLayout.addView(toolsView); + layoutParams = (FrameLayout.LayoutParams) toolsView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; + layoutParams.height = AndroidUtilities.dp(48); + layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; + toolsView.setLayoutParams(layoutParams); + + button = new ImageView(getParentActivity()); + button.setScaleType(ImageView.ScaleType.CENTER); + button.setImageResource(R.drawable.photo_crop); + toolsView.addView(button); + layoutParams1 = (LinearLayout.LayoutParams) button.getLayoutParams(); + layoutParams1.width = AndroidUtilities.dp(48); + layoutParams1.height = AndroidUtilities.dp(48); + button.setLayoutParams(layoutParams1); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + switchToMode(1, true); + } + }); + + filtersButton = new ImageView(getParentActivity()); + filtersButton.setScaleType(ImageView.ScaleType.CENTER); + filtersButton.setImageResource(R.drawable.photo_filters); + toolsView.addView(filtersButton); + layoutParams1 = (LinearLayout.LayoutParams) filtersButton.getLayoutParams(); + layoutParams1.width = AndroidUtilities.dp(48); + layoutParams1.height = AndroidUtilities.dp(48); + layoutParams1.leftMargin = AndroidUtilities.dp(54); + filtersButton.setLayoutParams(layoutParams1); + + toolButton = new ImageView(getParentActivity()); + toolButton.setScaleType(ImageView.ScaleType.CENTER); + toolButton.setImageResource(R.drawable.photo_tune); + toolsView.addView(toolButton); + layoutParams1 = (LinearLayout.LayoutParams) toolButton.getLayoutParams(); + layoutParams1.width = AndroidUtilities.dp(48); + layoutParams1.height = AndroidUtilities.dp(48); + layoutParams1.leftMargin = AndroidUtilities.dp(54); + toolButton.setLayoutParams(layoutParams1); + + glView = new MyGLSurfaceView(getParentActivity()); + glView.setVisibility(View.GONE); + frameLayout.addView(glView); + layoutParams = (FrameLayout.LayoutParams) glView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; + glView.setLayoutParams(layoutParams); + + RecyclerListView toolsView = new RecyclerListView(getParentActivity()); + LinearLayoutManager layoutManager = new LinearLayoutManager(getParentActivity()); + layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); + toolsView.setLayoutManager(layoutManager); + toolsView.setClipToPadding(false); + if (Build.VERSION.SDK_INT >= 9) { + toolsView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); + } + toolsView.setAdapter(new ToolsAdapter(getParentActivity())); + toolsView.setVisibility(View.GONE); + frameLayout.addView(toolsView); + layoutParams = (FrameLayout.LayoutParams) toolsView.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = AndroidUtilities.dp(60); + layoutParams.gravity = Gravity.LEFT | Gravity.BOTTOM; + layoutParams.bottomMargin = AndroidUtilities.dp(40); + toolsView.setLayoutParams(layoutParams); + toolsView.addOnItemTouchListener(new RecyclerListView.RecyclerListViewItemClickListener(getParentActivity(), new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int i) { + selectedTool = i; + if (i == 0) { + valueSeekBar.setMax(100); + valueSeekBar.setProgress((int) highlightsValue); + } else if (i == 1) { + valueSeekBar.setMax(200); + valueSeekBar.setProgress((int) contrastValue + 100); + } else if (i == 2) { + valueSeekBar.setMax(200); + valueSeekBar.setProgress((int) exposureValue + 100); + } else if (i == 3) { + valueSeekBar.setMax(200); + valueSeekBar.setProgress((int) warmthValue + 100); + } else if (i == 4) { + valueSeekBar.setMax(200); + valueSeekBar.setProgress((int) saturationValue + 100); + } else if (i == 5) { + valueSeekBar.setMax(100); + valueSeekBar.setProgress((int) vignetteValue); + } else if (i == 6) { + valueSeekBar.setMax(100); + valueSeekBar.setProgress((int) shadowsValue); + } else if (i == 7) { + valueSeekBar.setMax(100); + valueSeekBar.setProgress((int) grainValue); + } + } + })); + + valueSeekBar = new SeekBar(getParentActivity()); + valueSeekBar.setVisibility(View.GONE); + valueSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!fromUser) { + return; + } + if (selectedTool == 0) { + highlightsValue = progress; + } else if (selectedTool == 1) { + contrastValue = progress - 100; + } else if (selectedTool == 2) { + exposureValue = progress - 100; + } else if (selectedTool == 3) { + warmthValue = progress - 100; + } else if (selectedTool == 4) { + saturationValue = progress - 100; + } else if (selectedTool == 5) { + vignetteValue = progress; + } else if (selectedTool == 6) { + shadowsValue = progress; + } else if (selectedTool == 7) { + grainValue = progress; + } + glView.requestRender(); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + try { + Field field = ProgressBar.class.getDeclaredField("mMinHeight"); + field.setAccessible(true); + field.setInt(valueSeekBar, AndroidUtilities.dp(40)); + field = ProgressBar.class.getDeclaredField("mMaxHeight"); + field.setAccessible(true); + field.setInt(valueSeekBar, AndroidUtilities.dp(40)); + } catch (Exception e) { + FileLog.e("tmessages", e); + } + frameLayout.addView(valueSeekBar); + layoutParams = (FrameLayout.LayoutParams) valueSeekBar.getLayoutParams(); + layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; + layoutParams.height = AndroidUtilities.dp(40); + layoutParams.gravity = Gravity.LEFT | Gravity.BOTTOM; + layoutParams.leftMargin = AndroidUtilities.dp(10); + layoutParams.rightMargin = AndroidUtilities.dp(10); + valueSeekBar.setLayoutParams(layoutParams); + } else { + switchToMode(1, false); + } + + fixLayout(); + } else { + ViewGroup parent = (ViewGroup)fragmentView.getParent(); + if (parent != null) { + parent.removeView(fragmentView); + } + } + return fragmentView; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + fixLayout(); + } + + private void switchToMode(final int mode, final boolean animated) { + if (animated) { + if (currentMode == 0) { + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + animatorSet.playTogether( + ObjectAnimatorProxy.ofFloat(doneButton, "alpha", 1.0f, 0.0f), + ObjectAnimatorProxy.ofFloat(toolsView, "translationY", 0, AndroidUtilities.dp(48))); + animatorSet.setDuration(150); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + processFromMode(currentMode, mode, animated); + } + }); + animatorSet.start(); + } else if (currentMode == 1) { + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + animatorSet.playTogether( + ObjectAnimatorProxy.ofFloat(cropView, "alpha", 1.0f, 0.0f), + ObjectAnimatorProxy.ofFloat(cropButtonsView, "translationY", 0, AndroidUtilities.dp(48)), + ObjectAnimatorProxy.ofFloat(rotateButton, "alpha", 1.0f, 0.0f), + ObjectAnimatorProxy.ofFloat(sizeButton, "alpha", 1.0f, 0.0f)); + animatorSet.setDuration(150); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + processFromMode(currentMode, mode, animated); + } + }); + animatorSet.start(); + } + } else { + processFromMode(currentMode, mode, animated); + } + } + + private void processFromMode(int from, int to, boolean animated) { + if (from == 0) { + doneButton.setVisibility(View.GONE); + if (toolsView != null) { + toolsView.setVisibility(View.GONE); + } + processToMode(to, animated); + } else if (from == 1) { + cropView.setVisibility(View.GONE); + rotateButton.setVisibility(View.GONE); + if (freeformCrop) { + sizeButton.setVisibility(View.GONE); + } + cropButtonsView.setVisibility(View.GONE); + processToMode(to, animated); + } + } + + private void processToMode(int to, boolean animated) { + currentMode = to; + if (currentMode == 0) { + doneButton.setVisibility(View.VISIBLE); + toolsView.setVisibility(View.VISIBLE); + actionBar.setTitle(LocaleController.getString("EditImage", R.string.EditImage)); + if (animated) { + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + animatorSet.playTogether( + ObjectAnimatorProxy.ofFloat(doneButton, "alpha", 0.0f, 1.0f), + ObjectAnimatorProxy.ofFloat(toolsView, "translationY", AndroidUtilities.dp(48), 0)); + animatorSet.setDuration(150); + animatorSet.setInterpolator(new AccelerateInterpolator()); + animatorSet.start(); + } + } else if (currentMode == 1) { + cropView.setVisibility(View.VISIBLE); + rotateButton.setVisibility(View.VISIBLE); + if (freeformCrop) { + sizeButton.setVisibility(View.VISIBLE); + } + cropButtonsView.setVisibility(View.VISIBLE); + actionBar.setTitle(LocaleController.getString("CropImage", R.string.CropImage)); + if (animated) { + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + animatorSet.playTogether( + ObjectAnimatorProxy.ofFloat(cropView, "alpha", 0.0f, 1.0f), + ObjectAnimatorProxy.ofFloat(cropButtonsView, "translationY", AndroidUtilities.dp(48), 0), + ObjectAnimatorProxy.ofFloat(rotateButton, "alpha", 0.0f, 1.0f), + ObjectAnimatorProxy.ofFloat(sizeButton, "alpha", 0.0f, 1.0f)); + animatorSet.setDuration(150); + animatorSet.setInterpolator(new AccelerateInterpolator()); + animatorSet.start(); + } + } + } + + private void fixLayoutInternal(int rotation, final boolean animated) { + if (bitmapToEdit == null || fragmentView == null) { + return; + } + + int viewWidth = fragmentView.getWidth() - AndroidUtilities.dp(28); + int viewHeight = fragmentView.getHeight() - AndroidUtilities.dp(28 + 48); + + rotateDegree = rotation; + + if (cropView != null) { + float bitmapWidth = rotation % 180 == 0 ? bitmapToEdit.getWidth() : bitmapToEdit.getHeight(); + float bitmapHeight = rotation % 180 == 0 ? bitmapToEdit.getHeight() : bitmapToEdit.getWidth(); + float scaleX = viewWidth / bitmapWidth; + float scaleY = viewHeight / bitmapHeight; + if (scaleX > scaleY) { + bitmapHeight = viewHeight; + bitmapWidth = (int)Math.ceil(bitmapWidth * scaleY); + } else { + bitmapWidth = viewWidth; + bitmapHeight = (int)Math.ceil(bitmapHeight * scaleX); + } + + float percX = (cropView.rectX - cropView.bitmapX) / cropView.bitmapWidth; + float percY = (cropView.rectY - cropView.bitmapY) / cropView.bitmapHeight; + float percSizeX = cropView.rectSizeX / cropView.bitmapWidth; + float percSizeY = cropView.rectSizeY / cropView.bitmapHeight; + cropView.bitmapWidth = (int) bitmapWidth; + cropView.bitmapHeight = (int) bitmapHeight; + + cropView.bitmapX = (int) Math.ceil((viewWidth - bitmapWidth) / 2 + AndroidUtilities.dp(14)); + cropView.bitmapY = (int) Math.ceil((viewHeight - bitmapHeight) / 2 + AndroidUtilities.dp(14)); + + if (cropView.rectX == -1 && cropView.rectY == -1) { + if (freeformCrop) { + cropView.rectY = cropView.bitmapY; + cropView.rectX = cropView.bitmapX; + cropView.rectSizeX = bitmapWidth; + cropView.rectSizeY = bitmapHeight; + } else { + if (bitmapWidth > bitmapHeight) { + cropView.rectY = cropView.bitmapY; + cropView.rectX = (viewWidth - bitmapHeight) / 2 + AndroidUtilities.dp(14); + cropView.rectSizeX = bitmapHeight; + cropView.rectSizeY = bitmapHeight; + } else { + cropView.rectX = cropView.bitmapX; + cropView.rectY = (viewHeight - bitmapWidth) / 2 + AndroidUtilities.dp(14); + cropView.rectSizeX = bitmapWidth; + cropView.rectSizeY = bitmapWidth; + } + } + } else { + if (rotation % 180 == 0) { + cropView.rectX = percX * bitmapWidth + cropView.bitmapX; + cropView.rectY = percY * bitmapHeight + cropView.bitmapY; + } else { + cropView.rectX = percY * bitmapWidth + cropView.bitmapX; + cropView.rectY = percX * bitmapHeight + cropView.bitmapY; + } + cropView.rectSizeX = percSizeX * bitmapWidth; + cropView.rectSizeY = percSizeY * bitmapHeight; + } + cropView.invalidate(); + } + + float bitmapWidth = bitmapToEdit.getWidth(); + float bitmapHeight = bitmapToEdit.getHeight(); + float scaleX = viewWidth / bitmapWidth; + float scaleY = viewHeight / bitmapHeight; + float scale; + if (scaleX > scaleY) { + bitmapHeight = viewHeight; + bitmapWidth = (int)Math.ceil(bitmapWidth * scaleY); + scale = cropView.bitmapHeight / bitmapWidth; + } else { + bitmapWidth = viewWidth; + bitmapHeight = (int)Math.ceil(bitmapHeight * scaleX); + scale = cropView.bitmapWidth / bitmapHeight; + } + + FrameLayout.LayoutParams layoutParams; + if (imageView != null) { + layoutParams = (FrameLayout.LayoutParams) imageView.getLayoutParams(); + layoutParams.leftMargin = (int) ((viewWidth - bitmapWidth) / 2 + AndroidUtilities.dp(14)); + layoutParams.topMargin = (int) ((viewHeight - bitmapHeight) / 2 + AndroidUtilities.dp(14)); + layoutParams.width = (int) bitmapWidth; + layoutParams.height = (int) bitmapHeight; + imageView.setLayoutParams(layoutParams); + + if (animated) { + ViewProxy.setAlpha(cropView, 0.0f); + rotationAnimation = new AnimatorSetProxy(); + rotationAnimation.playTogether( + ObjectAnimatorProxy.ofFloat(imageView, "scaleX", rotateDegree % 180 != 0 ? scale : 1), + ObjectAnimatorProxy.ofFloat(imageView, "scaleY", rotateDegree % 180 != 0 ? scale : 1), + ObjectAnimatorProxy.ofFloat(imageView, "rotation", rotateDegree)); + rotationAnimation.setDuration(150); + rotationAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); + rotationAnimation.addListener(new AnimatorListenerAdapterProxy() { + @Override + public void onAnimationEnd(Object animation) { + if (rotationAnimation.equals(animation)) { + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + animatorSet.playTogether(ObjectAnimatorProxy.ofFloat(cropView, "alpha", 1.0f)); + animatorSet.setDuration(150); + animatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + animatorSet.start(); + rotationAnimation = null; + } + } + }); + rotationAnimation.start(); + } else { + imageView.setScaleX(rotateDegree % 180 != 0 ? scale : 1); + imageView.setScaleY(rotateDegree % 180 != 0 ? scale : 1); + imageView.setRotation(rotateDegree); + } + } + + if (glView != null) { + width = bitmapWidth; + height = bitmapHeight; + layoutParams = (FrameLayout.LayoutParams) glView.getLayoutParams(); + layoutParams.leftMargin = (int) ((viewWidth - bitmapWidth) / 2 + AndroidUtilities.dp(14)); + layoutParams.topMargin = (int) ((viewHeight - bitmapHeight) / 2 + AndroidUtilities.dp(14)); + layoutParams.width = (int) bitmapWidth; + layoutParams.height = (int) bitmapHeight; + glView.setLayoutParams(layoutParams); + glView.requestRender(); + } + } + + private void fixLayout() { + if (fragmentView == null) { + return; + } + fragmentView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (fragmentView != null) { + fixLayoutInternal(rotateDegree, false); + fragmentView.getViewTreeObserver().removeOnPreDrawListener(this); + } + return false; + } + }); + } + + public void setDelegate(PhotoCropActivity.PhotoEditActivityDelegate delegate) { + this.delegate = delegate; + } + + public class ToolsAdapter extends RecyclerView.Adapter { + + private Context mContext; + + private class Holder extends RecyclerView.ViewHolder { + + public Holder(View itemView) { + super(itemView); + } + } + + public ToolsAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemCount() { + return 8; + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { + PhotoEditToolCell view = new PhotoEditToolCell(mContext); + return new Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { + Holder holder = (Holder) viewHolder; + if (i == 0) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_highlights, "Highlights"); + } else if (i == 1) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_contrast, "Contrast"); + } else if (i == 2) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_exposure, "Exposure"); + } else if (i == 3) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_warmth, "Warmth"); + } else if (i == 4) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_saturation, "Saturation"); + } else if (i == 5) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_vignette, "Vignette"); + } else if (i == 6) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_shadows, "Shadows"); + } else if (i == 7) { + ((PhotoEditToolCell) holder.itemView).setIconAndText(R.drawable.photo_editor_grain, "Grain"); + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java index 1788e99b..0c573a96 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java @@ -12,6 +12,7 @@ import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.os.Build; +import android.os.Bundle; import android.util.Base64; import android.view.Gravity; import android.view.LayoutInflater; @@ -32,6 +33,7 @@ import android.widget.TextView; import org.json.JSONArray; import org.json.JSONObject; import org.telegram.android.AndroidUtilities; +import org.telegram.android.ImageLoader; import org.telegram.android.LocaleController; import org.telegram.android.MediaController; import org.telegram.android.MessagesStorage; @@ -45,6 +47,7 @@ import org.telegram.android.volley.toolbox.JsonObjectRequest; import org.telegram.android.volley.toolbox.Volley; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.TLRPC; @@ -65,7 +68,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; -public class PhotoPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { +public class PhotoPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider, PhotoCropActivity.PhotoEditActivityDelegate { public static interface PhotoPickerActivityDelegate { public abstract void selectedPhotosChanged(); @@ -85,7 +88,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen private boolean searching; private String nextSearchBingString; - private boolean giffySearchEndReached = true; + private boolean giphySearchEndReached = true; private String lastSearchString; private boolean loadingRecent; @@ -184,7 +187,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen searchResultKeys.clear(); lastSearchString = null; nextSearchBingString = null; - giffySearchEndReached = true; + giphySearchEndReached = true; searching = false; requestQueue.cancelAll("search"); if (type == 0) { @@ -204,11 +207,11 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen searchResult.clear(); searchResultKeys.clear(); nextSearchBingString = null; - giffySearchEndReached = true; + giphySearchEndReached = true; if (type == 0) { searchBingImages(editText.getText().toString(), 0, 53); } else if (type == 1) { - searchGiffyImages(editText.getText().toString(), 0, 53); + searchGiphyImages(editText.getText().toString(), 0, 53); } lastSearchString = editText.getText().toString(); if (lastSearchString.length() == 0) { @@ -320,8 +323,8 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !searching) { if (type == 0 && nextSearchBingString != null) { searchBingImages(lastSearchString, searchResult.size(), 54); - } else if (type == 1 && !giffySearchEndReached) { - searchGiffyImages(searchItem.getSearchField().getText().toString(), searchResult.size(), 54); + } else if (type == 1 && !giphySearchEndReached) { + searchGiphyImages(searchItem.getSearchField().getText().toString(), searchResult.size(), 54); } } } @@ -615,6 +618,20 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } } + @Override + public void didFinishEdit(Bitmap bitmap, Bundle args) { + TLRPC.PhotoSize size = ImageLoader.scaleAndSaveImage(bitmap, AndroidUtilities.getPhotoSize(), AndroidUtilities.getPhotoSize(), 80, false, 101, 101); + if (size != null) { + int id = args.getInt("id"); + MediaController.PhotoEntry entry = selectedAlbum.photosByIds.get(id); + entry.imagePath = FileLoader.getPathToAttach(size, true).toString(); + selectedPhotos.put(entry.imageId, entry); + listAdapter.notifyDataSetChanged(); + photoPickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); + delegate.selectedPhotosChanged(); + } + } + private void updateSearchInterface() { if (listAdapter != null) { listAdapter.notifyDataSetChanged(); @@ -630,7 +647,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } } - private void searchGiffyImages(String query, int offset, final int count) { + private void searchGiphyImages(String query, int offset, final int count) { if (searching) { searching = false; requestQueue.cancelAll("search"); @@ -647,7 +664,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen try { JSONObject pagination = response.getJSONObject("pagination"); int total_count = pagination.getInt("total_count"); - giffySearchEndReached = searchResult.size() + result.length() >= total_count; + giphySearchEndReached = searchResult.size() + result.length() >= total_count; } catch (Exception e) { FileLog.e("tmessages", e); } @@ -678,7 +695,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } } if (!added) { - giffySearchEndReached = true; + giphySearchEndReached = true; } } catch (Exception e) { FileLog.e("tmessages", e); @@ -691,7 +708,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen @Override public void onErrorResponse(VolleyError error) { FileLog.e("tmessages", "Error: " + error.getMessage()); - giffySearchEndReached = true; + giphySearchEndReached = true; searching = false; updateSearchInterface(); } @@ -884,7 +901,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } else if (type == 0) { return searchResult.size() + (nextSearchBingString == null ? 0 : 1); } else if (type == 1) { - return searchResult.size() + (giffySearchEndReached ? 0 : 1); + return searchResult.size() + (giphySearchEndReached ? 0 : 1); } } return selectedAlbum.photos.size(); @@ -920,9 +937,11 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get((Integer) ((View) v.getParent()).getTag()); if (selectedPhotos.containsKey(photoEntry.imageId)) { selectedPhotos.remove(photoEntry.imageId); + photoEntry.imagePath = null; } else { selectedPhotos.put(photoEntry.imageId, photoEntry); } + ((PhotoPickerPhotoCell) v.getParent()).editedImage.setVisibility(photoEntry.imagePath != null ? View.VISIBLE : View.GONE); ((PhotoPickerPhotoCell) v.getParent()).checkBox.setChecked(selectedPhotos.containsKey(photoEntry.imageId), true); } else { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); @@ -937,6 +956,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen } else { selectedWebPhotos.put(photoEntry.id, photoEntry); } + ((PhotoPickerPhotoCell) v.getParent()).editedImage.setVisibility(View.GONE); ((PhotoPickerPhotoCell) v.getParent()).checkBox.setChecked(selectedWebPhotos.containsKey(photoEntry.id), true); } photoPickerBottomLayout.updateSelectedCount(selectedPhotos.size() + selectedWebPhotos.size(), true); @@ -958,6 +978,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen imageView.setImageResource(R.drawable.nophotos); } cell.checkBox.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); + cell.editedImage.setVisibility(photoEntry.imagePath != null ? View.VISIBLE : View.GONE); showing = PhotoViewer.getInstance().isShowingImage(photoEntry.path); } else { MediaController.SearchImage photoEntry = null; @@ -972,6 +993,7 @@ public class PhotoPickerActivity extends BaseFragment implements NotificationCen imageView.setImageResource(R.drawable.nophotos); } cell.checkBox.setChecked(selectedWebPhotos.containsKey(photoEntry.id), false); + cell.editedImage.setVisibility(View.GONE); showing = PhotoViewer.getInstance().isShowingImage(photoEntry.thumbUrl); } imageView.imageReceiver.setVisible(!showing, false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index d421bccd..4def7298 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -34,11 +34,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; import android.view.animation.DecelerateInterpolator; -import android.view.animation.ScaleAnimation; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Scroller; @@ -48,6 +44,7 @@ import org.telegram.android.AndroidUtilities; import org.telegram.android.ContactsController; import org.telegram.android.ImageLoader; import org.telegram.android.MessagesStorage; +import org.telegram.android.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ConnectionsManager; import org.telegram.messenger.FileLoader; @@ -108,7 +105,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private PhotoPickerBottomLayout pickerView; private ImageView shareButton; private RadialProgressView radialProgressViews[] = new RadialProgressView[3]; - private GifDrawable gifDrawable = null; + private GifDrawable gifDrawable; + private ActionBarMenuItem editItem; + private AnimatorSetProxy currentActionBarAnimation; private boolean canShowBottom = true; private int animationInProgress = 0; @@ -117,7 +116,6 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private PlaceProviderObject showAfterAnimation; private PlaceProviderObject hideAfterAnimation; private boolean disableShowCheck = false; - private Animation.AnimationListener animationListener; private ImageReceiver leftImage = new ImageReceiver(); private ImageReceiver centerImage = new ImageReceiver(); @@ -189,6 +187,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private final static int gallery_menu_save = 1; private final static int gallery_menu_showall = 2; private final static int gallery_menu_send = 3; + private final static int gallery_menu_edit = 4; private final static int PAGE_SPACING = AndroidUtilities.dp(30); @@ -286,14 +285,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } public void onDraw(Canvas canvas) { - int sizeScaled = (int)(size * scale); + int sizeScaled = (int) (size * scale); int x = (canvas.getWidth() - sizeScaled) / 2; int y = (canvas.getHeight() - sizeScaled) / 2; if (previousBackgroundState >= 0 && previousBackgroundState < 4) { Drawable drawable = progressDrawables[previousBackgroundState]; if (drawable != null) { - drawable.setAlpha((int)(255 * animatedAlphaValue * alpha)); + drawable.setAlpha((int) (255 * animatedAlphaValue * alpha)); drawable.setBounds(x, y, x + sizeScaled, y + sizeScaled); drawable.draw(canvas); } @@ -303,9 +302,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat Drawable drawable = progressDrawables[backgroundState]; if (drawable != null) { if (previousBackgroundState != -2) { - drawable.setAlpha((int)(255 * (1.0f - animatedAlphaValue) * alpha)); + drawable.setAlpha((int) (255 * (1.0f - animatedAlphaValue) * alpha)); } else { - drawable.setAlpha((int)(255 * alpha)); + drawable.setAlpha((int) (255 * alpha)); } drawable.setBounds(x, y, x + sizeScaled, y + sizeScaled); drawable.draw(canvas); @@ -315,9 +314,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (backgroundState == 0 || backgroundState == 1 || previousBackgroundState == 0 || previousBackgroundState == 1) { int diff = AndroidUtilities.dp(1); if (previousBackgroundState != -2) { - progressPaint.setAlpha((int)(255 * animatedAlphaValue * alpha)); + progressPaint.setAlpha((int) (255 * animatedAlphaValue * alpha)); } else { - progressPaint.setAlpha((int)(255 * alpha)); + progressPaint.setAlpha((int) (255 * alpha)); } progressRect.set(x + diff, y + diff, x + sizeScaled - diff, y + sizeScaled - diff); canvas.drawArc(progressRect, -90 + radOffset, Math.max(4, 360 * animatedProgressValue), false, progressPaint); @@ -340,13 +339,21 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public static interface PhotoViewerProvider { public PlaceProviderObject getPlaceForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index); + public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index); + public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index); + public void willHidePhotoViewer(); + public boolean isPhotoChecked(int index); + public void setPhotoChecked(int index); + public void cancelButtonPressed(); + public void sendButtonPressed(int index); + public int getSelectedCount(); } @@ -373,14 +380,6 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat setWillNotDraw(false); } - @Override - protected void onAnimationEnd() { - super.onAnimationEnd(); - if (getInstance().animationListener != null) { - getInstance().animationListener.onAnimationEnd(null); - } - } - @Override protected void onDraw(Canvas canvas) { getInstance().onDraw(canvas); @@ -388,6 +387,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } private static volatile PhotoViewer Instance = null; + public static PhotoViewer getInstance() { PhotoViewer localInstance = Instance; if (localInstance == null) { @@ -405,7 +405,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.FileDidFailedLoad) { - String location = (String)args[0]; + String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { radialProgressViews[a].setProgress(1.0f, true); @@ -414,7 +414,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (id == NotificationCenter.FileDidLoaded) { - String location = (String)args[0]; + String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { radialProgressViews[a].setProgress(1.0f, true); @@ -426,7 +426,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (id == NotificationCenter.FileLoadProgressChanged) { - String location = (String)args[0]; + String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { Float progress = (Float) args[1]; @@ -434,13 +434,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (id == NotificationCenter.userPhotosLoaded) { - int guid = (Integer)args[4]; - int uid = (Integer)args[0]; + int guid = (Integer) args[4]; + int uid = (Integer) args[0]; if (avatarsUserId == uid && classGuid == guid) { - boolean fromCache = (Boolean)args[3]; + boolean fromCache = (Boolean) args[3]; int setToImage = -1; - ArrayList photos = (ArrayList)args[5]; + ArrayList photos = (ArrayList) args[5]; if (photos.isEmpty()) { return; } @@ -486,27 +486,27 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (id == NotificationCenter.mediaCountDidLoaded) { - long uid = (Long)args[0]; + long uid = (Long) args[0]; if (uid == currentDialogId) { - if ((int)currentDialogId != 0 && (Boolean)args[2]) { - MessagesController.getInstance().getMediaCount(currentDialogId, classGuid, false); + if ((int) currentDialogId != 0 && (Boolean) args[2]) { + SharedMediaQuery.getMediaCount(currentDialogId, SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, false); } - totalImagesCount = (Integer)args[1]; + totalImagesCount = (Integer) args[1]; if (needSearchImageInArr && isFirstLoading) { isFirstLoading = false; loadingMoreImages = true; - MessagesController.getInstance().loadMedia(currentDialogId, 0, 100, 0, true, classGuid); + SharedMediaQuery.loadMedia(currentDialogId, 0, 100, 0, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); } else if (!imagesArr.isEmpty()) { actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, (totalImagesCount - imagesArr.size()) + currentIndex + 1, totalImagesCount)); } } } else if (id == NotificationCenter.mediaDidLoaded) { - long uid = (Long)args[0]; - int guid = (Integer)args[4]; + long uid = (Long) args[0]; + int guid = (Integer) args[4]; if (uid == currentDialogId && guid == classGuid) { loadingMoreImages = false; - ArrayList arr = (ArrayList)args[2]; - boolean fromCache = (Boolean)args[3]; + ArrayList arr = (ArrayList) args[2]; + boolean fromCache = (Boolean) args[3]; cacheEndReached = !fromCache; if (needSearchImageInArr) { if (arr.isEmpty()) { @@ -548,7 +548,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { if (!cacheEndReached || !arr.isEmpty() && added != 0) { loadingMoreImages = true; - MessagesController.getInstance().loadMedia(currentDialogId, 0, 100, imagesArrTemp.get(0).messageOwner.id, true, classGuid); + SharedMediaQuery.loadMedia(currentDialogId, 0, 100, imagesArrTemp.get(0).messageOwner.id, SharedMediaQuery.MEDIA_PHOTOVIDEO, true, classGuid); } } } else { @@ -601,7 +601,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat containerView = new FrameLayoutDrawer(activity); containerView.setFocusable(false); windowView.addView(containerView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)containerView.getLayoutParams(); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) containerView.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT; layoutParams.gravity = Gravity.TOP | Gravity.LEFT; @@ -652,10 +652,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (opennedFromMedia) { closePhoto(true); } else if (currentDialogId != 0) { + disableShowCheck = true; closePhoto(false); Bundle args2 = new Bundle(); args2.putLong("dialog_id", currentDialogId); - ((LaunchActivity)parentActivity).presentFragment(new MediaActivity(args2), false, true); + ((LaunchActivity) parentActivity).presentFragment(new MediaActivity(args2), false, true); } } else if (id == gallery_menu_send) { /*Intent intent = new Intent(this, MessagesActivity.class); @@ -690,6 +691,21 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } }*/ + } else if (id == gallery_menu_edit) { + Bundle args = new Bundle(); + Bitmap bitmap = centerImage.getBitmap(); + String key = centerImage.getKey(); + if (bitmap == null) { + args.putString("photoPath", currentPathObject); + } + MediaController.PhotoEntry object = (MediaController.PhotoEntry) imagesArrLocals.get(currentIndex); + args.putInt("id", object.imageId); + args.putBoolean("freeformCrop", true); + args.putBoolean("onlyCrop", true); + PhotoEditorActivity fragment = new PhotoEditorActivity(args, bitmap, key); + fragment.setDelegate((PhotoCropActivity.PhotoEditActivityDelegate) placeProvider); + ((LaunchActivity) parentActivity).presentFragment(fragment, false, true); + closePhoto(false); } } @@ -716,9 +732,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery), 0); menuItem.addSubItem(gallery_menu_showall, LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia), 0); + editItem = menu.addItemWithWidth(gallery_menu_edit, R.drawable.photo_edit, AndroidUtilities.dp(56)); + bottomLayout = new FrameLayout(containerView.getContext()); containerView.addView(bottomLayout); - layoutParams = (FrameLayout.LayoutParams)bottomLayout.getLayoutParams(); + layoutParams = (FrameLayout.LayoutParams) bottomLayout.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; layoutParams.height = AndroidUtilities.dp(48); layoutParams.gravity = Gravity.BOTTOM | Gravity.LEFT; @@ -764,7 +782,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat intent.setType("image/jpeg"); } intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); - parentActivity.startActivity(intent); + + parentActivity.startActivity(Intent.createChooser(intent, "")); } else { AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -802,10 +821,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat ArrayList random_ids = null; TLRPC.EncryptedChat encryptedChat = null; - if ((int)obj.getDialogId() == 0 && obj.messageOwner.random_id != 0) { + if ((int) obj.getDialogId() == 0 && obj.messageOwner.random_id != 0) { random_ids = new ArrayList<>(); random_ids.add(obj.messageOwner.random_id); - encryptedChat = MessagesController.getInstance().getEncryptedChat((int)(obj.getDialogId() >> 32)); + encryptedChat = MessagesController.getInstance().getEncryptedChat((int) (obj.getDialogId() >> 32)); } MessagesController.getInstance().deleteMessages(arr, random_ids, encryptedChat); @@ -868,7 +887,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat nameTextView.setTextColor(0xffffffff); nameTextView.setGravity(Gravity.CENTER); bottomLayout.addView(nameTextView); - layoutParams = (FrameLayout.LayoutParams)nameTextView.getLayoutParams(); + layoutParams = (FrameLayout.LayoutParams) nameTextView.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; layoutParams.gravity = Gravity.TOP; @@ -885,7 +904,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat dateTextView.setTextColor(0xffb8bdbe); dateTextView.setGravity(Gravity.CENTER); bottomLayout.addView(dateTextView); - layoutParams = (FrameLayout.LayoutParams)dateTextView.getLayoutParams(); + layoutParams = (FrameLayout.LayoutParams) dateTextView.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; layoutParams.gravity = Gravity.TOP; @@ -895,6 +914,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat dateTextView.setLayoutParams(layoutParams); pickerView = new PhotoPickerBottomLayout(parentActivity); + pickerView.setBackgroundColor(0x7f000000); containerView.addView(pickerView); pickerView.cancelButton.setOnClickListener(new View.OnClickListener() { @Override @@ -915,7 +935,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } }); - layoutParams = (FrameLayout.LayoutParams)pickerView.getLayoutParams(); + layoutParams = (FrameLayout.LayoutParams) pickerView.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.MATCH_PARENT; layoutParams.height = AndroidUtilities.dp(48); layoutParams.gravity = Gravity.BOTTOM; @@ -935,7 +955,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat checkImageView.setColor(0xff3ccaef); containerView.addView(checkImageView); checkImageView.setVisibility(View.GONE); - layoutParams = (FrameLayout.LayoutParams)checkImageView.getLayoutParams(); + layoutParams = (FrameLayout.LayoutParams) checkImageView.getLayoutParams(); layoutParams.width = AndroidUtilities.dp(45); layoutParams.height = AndroidUtilities.dp(45); layoutParams.gravity = Gravity.RIGHT; @@ -963,13 +983,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private void toggleCheckImageView(boolean show) { AnimatorSetProxy animatorSet = new AnimatorSetProxy(); animatorSet.playTogether( - ObjectAnimatorProxy.ofFloat(checkImageView, "alpha", show ? 1.0f : 0.0f) + ObjectAnimatorProxy.ofFloat(checkImageView, "alpha", show ? 1.0f : 0.0f), + ObjectAnimatorProxy.ofFloat(pickerView, "alpha", show ? 1.0f : 0.0f) ); animatorSet.setDuration(200); animatorSet.start(); } - private void toggleActionBar(boolean show, boolean animated) { + private void toggleActionBar(boolean show, final boolean animated) { if (show) { actionBar.setVisibility(View.VISIBLE); if (canShowBottom) { @@ -981,25 +1002,28 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat bottomLayout.setEnabled(show); if (animated) { - AnimatorSetProxy animatorSet = new AnimatorSetProxy(); - animatorSet.playTogether( + currentActionBarAnimation = new AnimatorSetProxy(); + currentActionBarAnimation.playTogether( ObjectAnimatorProxy.ofFloat(actionBar, "alpha", show ? 1.0f : 0.0f), ObjectAnimatorProxy.ofFloat(bottomLayout, "alpha", show ? 1.0f : 0.0f) ); if (!show) { - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + currentActionBarAnimation.addListener(new AnimatorListenerAdapterProxy() { @Override public void onAnimationEnd(Object animation) { - actionBar.setVisibility(View.GONE); - if (canShowBottom) { - bottomLayout.setVisibility(View.GONE); + if (currentActionBarAnimation.equals(animation)) { + actionBar.setVisibility(View.GONE); + if (canShowBottom) { + bottomLayout.setVisibility(View.GONE); + } + currentActionBarAnimation = null; } } }); } - animatorSet.setDuration(200); - animatorSet.start(); + currentActionBarAnimation.setDuration(200); + currentActionBarAnimation.start(); } else { ViewProxy.setAlpha(actionBar, show ? 1.0f : 0.0f); ViewProxy.setAlpha(bottomLayout, show ? 1.0f : 0.0f); @@ -1075,7 +1099,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (message.messageOwner.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) { return message.messageOwner.action.newUserPhoto.photo_big; } else { - TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.messageOwner.action.photo.sizes, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { size[0] = sizeFull.size; if (size[0] == 0) { @@ -1087,7 +1111,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto && message.messageOwner.media.photo != null) { - TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.messageOwner.media.photo.sizes, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { size[0] = sizeFull.size; if (size[0] == 0) { @@ -1138,7 +1162,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat location.secret = sizeFull.secret; return location; } else { - TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.messageOwner.action.photo.sizes, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { TLRPC.TL_inputFileLocation location = new TLRPC.TL_inputFileLocation(); location.local_id = sizeFull.location.local_id; @@ -1149,7 +1173,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { - TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.messageOwner.media.photo.sizes, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { TLRPC.TL_inputFileLocation location = new TLRPC.TL_inputFileLocation(); location.local_id = sizeFull.location.local_id; @@ -1206,9 +1230,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat menuItem.setVisibility(View.VISIBLE); bottomLayout.setVisibility(View.VISIBLE); ViewProxy.setAlpha(checkImageView, 1.0f); + ViewProxy.setAlpha(pickerView, 1.0f); checkImageView.clearAnimation(); + pickerView.clearAnimation(); checkImageView.setVisibility(View.GONE); pickerView.setVisibility(View.GONE); + editItem.setVisibility(View.GONE); for (int a = 0; a < 3; a++) { if (radialProgressViews[a] != null) { radialProgressViews[a].setBackgroundState(-1, false); @@ -1284,17 +1311,26 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat bottomLayout.setVisibility(View.GONE); shareButton.setVisibility(View.VISIBLE); canShowBottom = false; + //editItem.setVisibility(imagesArrLocals.get(index) instanceof MediaController.PhotoEntry ? View.VISIBLE : View.GONE); updateSelectedCount(); } if (currentDialogId != 0 && totalImagesCount == 0) { - MessagesController.getInstance().getMediaCount(currentDialogId, classGuid, true); + SharedMediaQuery.getMediaCount(currentDialogId, SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, true); } else if (avatarsUserId != 0) { MessagesController.getInstance().loadUserPhotos(avatarsUserId, 0, 80, 0, true, classGuid); } } - public void setImageIndex(int index, boolean init) { + private void setImages() { + if (animationInProgress == 0) { + setIndexToImage(centerImage, currentIndex); + setIndexToImage(rightImage, currentIndex + 1); + setIndexToImage(leftImage, currentIndex - 1); + } + } + + private void setImageIndex(int index, boolean init) { if (currentIndex == index) { return; } @@ -1332,7 +1368,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (totalImagesCount != 0 && !needSearchImageInArr) { if (imagesArr.size() < totalImagesCount && !loadingMoreImages && currentIndex < 5) { MessageObject lastMessage = imagesArr.get(0); - MessagesController.getInstance().loadMedia(currentDialogId, 0, 100, lastMessage.messageOwner.id, !cacheEndReached, classGuid); + SharedMediaQuery.loadMedia(currentDialogId, 0, 100, lastMessage.messageOwner.id, SharedMediaQuery.MEDIA_PHOTOVIDEO, !cacheEndReached, classGuid); loadingMoreImages = true; } actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, (totalImagesCount - imagesArr.size()) + currentIndex + 1, totalImagesCount)); @@ -1427,9 +1463,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } if (prevIndex == -1) { - setIndexToImage(centerImage, currentIndex); - setIndexToImage(rightImage, currentIndex + 1); - setIndexToImage(leftImage, currentIndex - 1); + setImages(); for (int a = 0; a < 3; a++) { checkProgress(a, false); @@ -1553,6 +1587,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private void setIndexToImage(ImageReceiver imageReceiver, int index) { if (!imagesArrLocals.isEmpty()) { + imageReceiver.setParentMessageObject(null); if (index >= 0 && index < imagesArrLocals.size()) { Object object = imagesArrLocals.get(index); int size = (int) (AndroidUtilities.getPhotoSize() / AndroidUtilities.density); @@ -1584,33 +1619,38 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (!imagesArr.isEmpty()) { messageObject = imagesArr.get(index); } + imageReceiver.setParentMessageObject(messageObject); + if (messageObject != null) { + imageReceiver.setShouldGenerateQualityThumb(true); + } if (messageObject != null && messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVideo) { - if (messageObject.imagePreview != null) { - imageReceiver.setImageBitmap(messageObject.imagePreview); - } else if (messageObject.messageOwner.media.video.thumb != null) { + imageReceiver.setNeedsQualityThumb(true); + if (messageObject.messageOwner.media.video.thumb != null) { Bitmap placeHolder = null; if (currentThumb != null && imageReceiver == centerImage) { placeHolder = currentThumb; } - imageReceiver.setImage(fileLocation, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, 0, true); + TLRPC.PhotoSize thumbLocation = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 100); + imageReceiver.setImage(null, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, thumbLocation.location, "b", 0, true); } else { imageReceiver.setImageBitmap(parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder)); } } else { + imageReceiver.setNeedsQualityThumb(false); Bitmap placeHolder = null; - if (messageObject != null) { - placeHolder = messageObject.imagePreview; - } if (currentThumb != null && imageReceiver == centerImage) { placeHolder = currentThumb; } if (size[0] == 0) { size[0] = -1; } - imageReceiver.setImage(fileLocation, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, size[0], avatarsUserId != 0); + TLRPC.PhotoSize thumbLocation = messageObject != null ? FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 100) : null; + imageReceiver.setImage(fileLocation, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, thumbLocation != null ? thumbLocation.location : null, "b", size[0], avatarsUserId != 0); } } else { + imageReceiver.setNeedsQualityThumb(false); + imageReceiver.setParentMessageObject(null); if (size[0] == 0) { imageReceiver.setImageBitmap((Bitmap) null); } else { @@ -1776,6 +1816,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override public void run() { animationInProgress = 0; + setImages(); transitionAnimationStartTime = 0; containerView.invalidate(); animatingImageView.setVisibility(View.GONE); @@ -1843,7 +1884,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat final PlaceProviderObject object = placeProvider.getPlaceForPhoto(currentMessageObject, currentFileLocation, currentIndex); - if(animated) { + if (animated) { AndroidUtilities.lockOrientation(parentActivity); animationInProgress = 1; @@ -1948,48 +1989,36 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat transitionAnimationStartTime = System.currentTimeMillis(); animatorSet.start(); } else { - AnimationSet animationSet = new AnimationSet(true); - AlphaAnimation animation = new AlphaAnimation(1.0f, 0.0f); - animation.setDuration(150); - animation.setFillAfter(false); - animationSet.addAnimation(animation); - ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 0.9f, 1.0f, 0.9f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); - scaleAnimation.setDuration(150); - scaleAnimation.setFillAfter(false); - animationSet.addAnimation(scaleAnimation); - animationSet.setDuration(150); + AnimatorSetProxy animatorSet = new AnimatorSetProxy(); + animatorSet.playTogether( + ObjectAnimatorProxy.ofFloat(containerView, "scaleX", 0.9f), + ObjectAnimatorProxy.ofFloat(containerView, "scaleY", 0.9f), + ObjectAnimatorProxy.ofInt(backgroundDrawable, "alpha", 0), + ObjectAnimatorProxy.ofFloat(containerView, "alpha", 0.0f) + ); animationInProgress = 2; animationEndRunnable = new Runnable() { @Override public void run() { - if (animationListener != null) { - animationInProgress = 0; - onPhotoClosed(object); - animationListener = null; - } + animationInProgress = 0; + onPhotoClosed(object); + ViewProxy.setScaleX(containerView, 1.0f); + ViewProxy.setScaleY(containerView, 1.0f); + containerView.clearAnimation(); } }; - animationSet.setAnimationListener(animationListener = new Animation.AnimationListener() { + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapterProxy() { @Override - public void onAnimationStart(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { + public void onAnimationEnd(Object animation) { if (animationEndRunnable != null) { animationEndRunnable.run(); animationEndRunnable = null; } } - - @Override - public void onAnimationRepeat(Animation animation) { - - } }); transitionAnimationStartTime = System.currentTimeMillis(); - containerView.startAnimation(animationSet); + animatorSet.start(); } } @@ -2025,9 +2054,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat radialProgressViews[a].setBackgroundState(-1, false); } } - centerImage.setImageBitmap((Bitmap)null); - leftImage.setImageBitmap((Bitmap)null); - rightImage.setImageBitmap((Bitmap)null); + centerImage.setImageBitmap((Bitmap) null); + leftImage.setImageBitmap((Bitmap) null); + rightImage.setImageBitmap((Bitmap) null); containerView.post(new Runnable() { @Override public void run() { @@ -2081,7 +2110,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return false; } - if(ev.getPointerCount() == 1 && gestureDetector.onTouchEvent(ev)) { + if (ev.getPointerCount() == 1 && gestureDetector.onTouchEvent(ev)) { if (doubleTap) { doubleTap = false; moving = false; @@ -2121,9 +2150,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { - discardTap = true; if (canZoom && ev.getPointerCount() == 2 && !draggingDown && zooming && !changingPage) { - scale = (float)Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)) / pinchStartDistance * pinchStartScale; + discardTap = true; + scale = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)) / pinchStartDistance * pinchStartScale; translationX = (pinchCenterX - containerView.getWidth() / 2) - ((pinchCenterX - containerView.getWidth() / 2) - pinchStartX) * (scale / pinchStartScale); translationY = (pinchCenterY - containerView.getHeight() / 2) - ((pinchCenterY - containerView.getHeight() / 2) - pinchStartY) * (scale / pinchStartScale); updateMinMax(scale); @@ -2134,6 +2163,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } float dx = Math.abs(ev.getX() - moveStartX); float dy = Math.abs(ev.getY() - dragY); + if (dx > AndroidUtilities.dp(3) || dy > AndroidUtilities.dp(3)) { + discardTap = true; + } if (canDragDown && !draggingDown && scale == 1 && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { draggingDown = true; moving = false; @@ -2141,6 +2173,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (isActionBarVisible && canShowBottom) { toggleActionBar(false, true); } else if (checkImageView.getVisibility() == View.VISIBLE) { + toggleActionBar(false, true); toggleCheckImageView(false); } return true; @@ -2197,7 +2230,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (scale < 1.0f) { updateMinMax(1.0f); animateTo(1.0f, 0, 0, true); - } else if(scale > 3.0f) { + } else if (scale > 3.0f) { float atx = (pinchCenterX - containerView.getWidth() / 2) - ((pinchCenterX - containerView.getWidth() / 2) - pinchStartX) * (3.0f / pinchStartScale); float aty = (pinchCenterY - containerView.getHeight() / 2) - ((pinchCenterY - containerView.getHeight() / 2) - pinchStartY) * (3.0f / pinchStartScale); updateMinMax(3.0f); @@ -2221,6 +2254,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat closePhoto(true); } else { if (checkImageView.getVisibility() == View.VISIBLE) { + toggleActionBar(true, true); toggleCheckImageView(true); } animateTo(1, 0, 0); @@ -2238,11 +2272,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat velocity = velocityTracker.getXVelocity(); } - if((translationX < minX - containerView.getWidth() / 3 || velocity < -AndroidUtilities.dp(650)) && rightImage.hasImage()){ + if ((translationX < minX - containerView.getWidth() / 3 || velocity < -AndroidUtilities.dp(650)) && rightImage.hasImage()) { goToNext(); return true; } - if((translationX > maxX + containerView.getWidth() / 3 || velocity > AndroidUtilities.dp(650)) && leftImage.hasImage()){ + if ((translationX > maxX + containerView.getWidth() / 3 || velocity > AndroidUtilities.dp(650)) && leftImage.hasImage()) { goToPrev(); return true; } @@ -2331,7 +2365,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat float ai = -1; if (System.currentTimeMillis() - animationStartTime < animationDuration) { - ai = interpolator.getInterpolation((float)(System.currentTimeMillis() - animationStartTime) / animationDuration); + ai = interpolator.getInterpolation((float) (System.currentTimeMillis() - animationStartTime) / animationDuration); if (ai >= 1.0f) { ai = -1; } @@ -2475,7 +2509,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (gifDrawable != null) { canvas.save(); - gifDrawable.setAlpha((int)(alpha * 255)); + gifDrawable.setAlpha((int) (alpha * 255)); gifDrawable.setBounds(-width / 2, -height / 2, width / 2, height / 2); gifDrawable.draw(canvas); canvas.restore(); @@ -2526,7 +2560,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @SuppressLint("DrawAllocation") private void onLayout(boolean changed, int left, int top, int right, int bottom) { - if(changed) { + if (changed) { scale = 1; translationX = 0; translationY = 0; @@ -2537,8 +2571,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat @Override public boolean onPreDraw() { checkImageView.getViewTreeObserver().removeOnPreDrawListener(this); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)checkImageView.getLayoutParams(); - WindowManager manager = (WindowManager)ApplicationLoader.applicationContext.getSystemService(Activity.WINDOW_SERVICE); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) checkImageView.getLayoutParams(); + WindowManager manager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Activity.WINDOW_SERVICE); int rotation = manager.getDefaultDisplay().getRotation(); if (rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90) { layoutParams.topMargin = AndroidUtilities.dp(58); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java index 38cb6264..5c9dcae8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java @@ -13,6 +13,7 @@ import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.PowerManager; import android.text.TextUtils; @@ -45,7 +46,6 @@ import org.telegram.android.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.TLRPC; import org.telegram.android.MessageObject; -import org.telegram.android.PhotoObject; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.Components.AvatarDrawable; @@ -524,7 +524,8 @@ public class PopupNotificationActivity extends Activity implements NotificationC imageView.imageReceiver.setAspectFit(true); if (messageObject.type == 1) { - PhotoObject currentPhotoObject = PhotoObject.getClosestImageWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 100); boolean photoSet = false; if (currentPhotoObject != null) { boolean photoExist = true; @@ -535,11 +536,11 @@ public class PopupNotificationActivity extends Activity implements NotificationC } } if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO)) { - imageView.setImage(currentPhotoObject.photoOwner.location, "100_100", messageObject.imagePreview, currentPhotoObject.photoOwner.size); + imageView.setImage(currentPhotoObject.location, "100_100", thumb.location, currentPhotoObject.size); photoSet = true; } else { - if (messageObject.imagePreview != null) { - imageView.setImageBitmap(messageObject.imagePreview); + if (thumb != null) { + imageView.setImage(thumb.location, null, (Drawable) null); photoSet = true; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index ce0e88cc..fe528496 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -8,12 +8,15 @@ package org.telegram.ui; +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Outline; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -24,6 +27,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.widget.AbsListView; import android.widget.AdapterView; @@ -38,6 +42,7 @@ import org.telegram.android.LocaleController; import org.telegram.android.MessagesStorage; import org.telegram.android.SecretChatHelper; import org.telegram.android.SendMessagesHelper; +import org.telegram.android.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ConnectionsManager; import org.telegram.messenger.TLRPC; @@ -170,14 +175,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } } - NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatInfoDidLoaded); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); sortedUsers = new ArrayList<>(); updateOnlineCount(); if (chat_id > 0) { - MessagesController.getInstance().getMediaCount(-chat_id, classGuid, true); + SharedMediaQuery.getMediaCount(-chat_id, SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, true); } avatarUpdater = new AvatarUpdater(); @@ -196,6 +199,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. NotificationCenter.getInstance().addObserver(this, NotificationCenter.mediaCountDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateInterfaces); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); updateRowsIds(); return true; @@ -206,6 +210,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. super.onFragmentDestroy(); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.mediaCountDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateInterfaces); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); if (user_id != 0) { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.contactsDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.encryptedChatCreated); @@ -214,7 +219,6 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. MessagesController.getInstance().cancelLoadFullUser(user_id); } else if (chat_id != 0) { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatInfoDidLoaded); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); avatarUpdater.clear(); } } @@ -547,21 +551,36 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. }); } if (dialog_id != 0) { - MessagesController.getInstance().getMediaCount(dialog_id, classGuid, true); + SharedMediaQuery.getMediaCount(dialog_id, SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, true); } else { - MessagesController.getInstance().getMediaCount(user_id, classGuid, true); + SharedMediaQuery.getMediaCount(user_id, SharedMediaQuery.MEDIA_PHOTOVIDEO, classGuid, true); } frameLayout.addView(actionBar); if (user_id != 0 || chat_id >= 0 && !currentChat.left) { writeButton = new ImageView(getParentActivity()); + writeButton.setBackgroundResource(R.drawable.floating_user_states); + writeButton.setScaleType(ImageView.ScaleType.CENTER); if (user_id != 0) { - writeButton.setImageResource(R.drawable.floating_user_states); + writeButton.setImageResource(R.drawable.floating_message); + writeButton.setPadding(0, AndroidUtilities.dp(3), 0, 0); } else if (chat_id != 0) { - writeButton.setImageResource(R.drawable.floating_group_states); + writeButton.setImageResource(R.drawable.floating_camera); } frameLayout.addView(writeButton); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[] {android.R.attr.state_pressed}, ObjectAnimator.ofFloat(writeButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[] {}, ObjectAnimator.ofFloat(writeButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + writeButton.setStateListAnimator(animator); + writeButton.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } layoutParams = (FrameLayout.LayoutParams) writeButton.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; layoutParams.height = FrameLayout.LayoutParams.WRAP_CONTENT; @@ -580,6 +599,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (user == null || user instanceof TLRPC.TL_userEmpty) { return; } + NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); Bundle args = new Bundle(); args.putInt("user_id", user_id); @@ -814,6 +834,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { + NotificationCenter.getInstance().removeObserver(ProfileActivity.this, NotificationCenter.closeChats); NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); TLRPC.EncryptedChat encryptedChat = (TLRPC.EncryptedChat) args[0]; Bundle args2 = new Bundle(); @@ -1082,7 +1103,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. AvatarDrawable avatarDrawable = new AvatarDrawable(user); avatarImage.setImage(photo, "50_50", avatarDrawable); - nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + if (user instanceof TLRPC.TL_userDeleted) { + nameTextView.setText(LocaleController.getString("HiddenName", R.string.HiddenName)); + } else { + nameTextView.setText(ContactsController.formatName(user.first_name, user.last_name)); + } onlineTextView.setText(LocaleController.formatUserStatus(user)); avatarImage.imageReceiver.setVisible(!PhotoViewer.getInstance().isShowingImage(photoBig), false); @@ -1166,6 +1191,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (dialog_id != 0) { Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); int lower_part = (int)dialog_id; if (lower_part != 0) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java index e569eedc..3675c763 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java @@ -30,14 +30,13 @@ import android.widget.ListView; import org.telegram.android.AndroidUtilities; import org.telegram.android.MessagesController; import org.telegram.android.MessagesStorage; +import org.telegram.android.NotificationsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ConnectionsManager; import org.telegram.messenger.FileLog; import org.telegram.android.LocaleController; import org.telegram.android.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.messenger.RPCRequest; -import org.telegram.messenger.TLObject; import org.telegram.messenger.TLRPC; import org.telegram.ui.Adapters.BaseFragmentAdapter; import org.telegram.ui.Cells.TextColorCell; @@ -162,18 +161,23 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi LocaleController.getString("Disabled", R.string.Disabled) }, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int which) { + public void onClick(DialogInterface d, int which) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putInt("notify2_" + dialog_id, which); MessagesStorage.getInstance().setDialogFlags(dialog_id, which == 2 ? 1 : 0); editor.commit(); + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + if (which == 2) { + dialog.notify_settings.mute_until = Integer.MAX_VALUE; + } + } if (listView != null) { listView.invalidateViews(); } - if (i == settingsNotificationsRow) { - updateServerNotificationsSettings(); - } + NotificationsController.updateServerNotificationsSettings(dialog_id); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -298,45 +302,6 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi return fragmentView; } - public void updateServerNotificationsSettings() { - if ((int)dialog_id == 0) { - return; - } - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - TLRPC.TL_account_updateNotifySettings req = new TLRPC.TL_account_updateNotifySettings(); - req.settings = new TLRPC.TL_inputPeerNotifySettings(); - req.settings.sound = "default"; - req.settings.events_mask = 0; - req.settings.mute_until = preferences.getInt("notify2_" + dialog_id, 0) != 2 ? 0 : Integer.MAX_VALUE; - req.settings.show_previews = preferences.getBoolean("preview_" + dialog_id, true); - - req.peer = new TLRPC.TL_inputNotifyPeer(); - - if ((int)dialog_id < 0) { - ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerChat(); - ((TLRPC.TL_inputNotifyPeer)req.peer).peer.chat_id = -(int)dialog_id; - } else { - TLRPC.User user = MessagesController.getInstance().getUser((int)dialog_id); - if (user == null) { - return; - } - if (user instanceof TLRPC.TL_userForeign || user instanceof TLRPC.TL_userRequest) { - ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerForeign(); - ((TLRPC.TL_inputNotifyPeer)req.peer).peer.access_hash = user.access_hash; - } else { - ((TLRPC.TL_inputNotifyPeer)req.peer).peer = new TLRPC.TL_inputPeerContact(); - } - ((TLRPC.TL_inputNotifyPeer)req.peer).peer.user_id = (int)dialog_id; - } - - ConnectionsManager.getInstance().performRpc(req, new RPCRequest.RPCRequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - - } - }); - } - @Override public void onActivityResultFragment(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { @@ -451,6 +416,19 @@ public class ProfileNotificationsActivity extends BaseFragment implements Notifi textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("Enabled", R.string.Enabled), true); } else if (value == 2) { textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("Disabled", R.string.Disabled), true); + } else if (value == 3) { + int delta = preferences.getInt("notifyuntil_" + dialog_id, 0) - ConnectionsManager.getInstance().getCurrentTime(); + String val; + if (delta <= 0) { + val = LocaleController.getString("Enabled", R.string.Enabled); + } else if (delta < 60 * 60) { + val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Minutes", delta / 60)); + } else if (delta < 60 * 60 * 24) { + val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Hours", (int) Math.ceil(delta / 60.0f / 60))); + } else { + val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Days", (int) Math.ceil(delta / 60.0f / 60 / 24))); + } + textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), val, true); } } else if (i == settingsSoundRow) { String value = preferences.getString("sound_" + dialog_id, LocaleController.getString("Default", R.string.Default)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java index bf4d8778..ef77d39e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java @@ -256,12 +256,12 @@ public class SecretPhotoViewer implements NotificationCenter.NotificationCenterD NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagesDeleted); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didCreatedNewDeleteTask); - TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(messageObject.messageOwner.media.photo.sizes, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); int size = sizeFull.size; if (size == 0) { size = -1; } - BitmapDrawable drawable = ImageLoader.getInstance().getImageFromMemory(sizeFull.location, null, null, null); + BitmapDrawable drawable = ImageLoader.getInstance().getImageFromMemory(sizeFull.location, null, null); if (drawable == null) { File file = FileLoader.getPathToAttach(sizeFull); Bitmap bitmap = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java index 0296a89d..31efa308 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java @@ -8,6 +8,8 @@ package org.telegram.ui; +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; @@ -18,7 +20,9 @@ import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Outline; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.text.Html; import android.text.Spannable; @@ -31,6 +35,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.widget.AbsListView; import android.widget.AdapterView; @@ -596,7 +601,21 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter frameLayout.addView(actionBar); writeButton = new ImageView(getParentActivity()); - writeButton.setImageResource(R.drawable.floating_group_states); + writeButton.setBackgroundResource(R.drawable.floating_user_states); + writeButton.setImageResource(R.drawable.floating_camera); + writeButton.setScaleType(ImageView.ScaleType.CENTER); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[] {android.R.attr.state_pressed}, ObjectAnimator.ofFloat(writeButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[] {}, ObjectAnimator.ofFloat(writeButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + writeButton.setStateListAnimator(animator); + writeButton.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } frameLayout.addView(writeButton); layoutParams = (FrameLayout.LayoutParams) writeButton.getLayoutParams(); layoutParams.width = FrameLayout.LayoutParams.WRAP_CONTENT; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java index 5f0c6a45..5b428a43 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java @@ -225,7 +225,7 @@ public class WallpapersActivity extends BaseFragment implements NotificationCent Utilities.addMediaToGallery(currentPicturePath); try { Point screenSize = AndroidUtilities.getRealScreenSize(); - Bitmap bitmap = ImageLoader.loadBitmap(currentPicturePath, null, screenSize.x, screenSize.y); + Bitmap bitmap = ImageLoader.loadBitmap(currentPicturePath, null, screenSize.x, screenSize.y, true); File toFile = new File(ApplicationLoader.applicationContext.getFilesDir(), "wallpaper-temp.jpg"); FileOutputStream stream = new FileOutputStream(toFile); bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); @@ -242,7 +242,7 @@ public class WallpapersActivity extends BaseFragment implements NotificationCent } try { Point screenSize = AndroidUtilities.getRealScreenSize(); - Bitmap bitmap = ImageLoader.loadBitmap(null, data.getData(), screenSize.x, screenSize.y); + Bitmap bitmap = ImageLoader.loadBitmap(null, data.getData(), screenSize.x, screenSize.y, true); File toFile = new File(ApplicationLoader.applicationContext.getFilesDir(), "wallpaper-temp.jpg"); FileOutputStream stream = new FileOutputStream(toFile); bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating.png b/TMessagesProj/src/main/res/drawable-hdpi/floating.png index 419d0b82..99fdffa7 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating.png and b/TMessagesProj/src/main/res/drawable-hdpi/floating.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png index f954a8a0..de9e52d6 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png and b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_m.png new file mode 100755 index 00000000..834d0195 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png index 916e21b9..82f31bdf 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png and b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating4_profile.png b/TMessagesProj/src/main/res/drawable-hdpi/floating4_profile.png deleted file mode 100755 index 69e720a6..00000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating4_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating4_profile_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/floating4_profile_pressed.png deleted file mode 100755 index 54704186..00000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating4_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png new file mode 100755 index 00000000..0980e9e0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_m.png new file mode 100755 index 00000000..7b5f02ca Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png new file mode 100755 index 00000000..c6743f12 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_pencil.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_pencil.png new file mode 100755 index 00000000..69c48cd4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating_pencil.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png index 418c3ac5..1edf010e 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png and b/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_arrow_drop_down.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_arrow_drop_down.png new file mode 100755 index 00000000..c9dab492 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_arrow_drop_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png index e3dccd29..f0cc429d 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png index 5b7c4e19..bbf0414c 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png index fb446d76..6c2c8e92 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png new file mode 100755 index 00000000..7cd764da Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_blue.png new file mode 100755 index 00000000..9176e7b5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_green.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_green.png new file mode 100755 index 00000000..9eef7a7d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_green.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png new file mode 100755 index 00000000..35678c12 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png new file mode 100755 index 00000000..364c6d99 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_red.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_red.png new file mode 100755 index 00000000..4215c482 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_red.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_yellow.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_yellow.png new file mode 100755 index 00000000..5634b7e3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_yellow.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/mute_blue.png new file mode 100755 index 00000000..27af0fa1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/mute_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-hdpi/mute_grey.png new file mode 100755 index 00000000..57a0acb9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/mute_grey.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_crop.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_crop.png new file mode 100644 index 00000000..facaf39e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_crop.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_edit.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_edit.png new file mode 100644 index 00000000..8768155c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_edit.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_filters.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_filters.png new file mode 100644 index 00000000..2bd619df Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_filters.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_filters_active.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_filters_active.png new file mode 100644 index 00000000..e9ad643a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_filters_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_rotate.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_rotate.png new file mode 100644 index 00000000..31e097a2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_rotate.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_sizes.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_sizes.png new file mode 100644 index 00000000..ac8c7818 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_sizes.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_tune.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_tune.png new file mode 100644 index 00000000..dc365663 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_tune.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photo_tune_active.png b/TMessagesProj/src/main/res/drawable-hdpi/photo_tune_active.png new file mode 100644 index 00000000..1a30ec1d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/photo_tune_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/tip1.png b/TMessagesProj/src/main/res/drawable-hdpi/tip1.png new file mode 100755 index 00000000..d979e349 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/tip1.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/tip2.png b/TMessagesProj/src/main/res/drawable-hdpi/tip2.png new file mode 100755 index 00000000..e6870160 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/tip2.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating.png b/TMessagesProj/src/main/res/drawable-mdpi/floating.png index 11b10cf3..4f11fc26 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating.png and b/TMessagesProj/src/main/res/drawable-mdpi/floating.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png index d2e829c9..35eb5bbb 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png and b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_m.png new file mode 100755 index 00000000..652630fc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png index 7689faa1..fbcb4da6 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png and b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating4_profile.png b/TMessagesProj/src/main/res/drawable-mdpi/floating4_profile.png deleted file mode 100755 index a04f3739..00000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating4_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating4_profile_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/floating4_profile_pressed.png deleted file mode 100755 index d1082b32..00000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating4_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png new file mode 100755 index 00000000..0e74dc26 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_m.png new file mode 100755 index 00000000..6d1b3818 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png new file mode 100755 index 00000000..8d5f07d4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_pencil.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_pencil.png new file mode 100755 index 00000000..f4cd0264 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating_pencil.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png index bdcb34f2..0ff85c17 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png and b/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_arrow_drop_down.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_arrow_drop_down.png new file mode 100755 index 00000000..dc801dc1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_arrow_drop_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png index 1028bfaf..3000004c 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png index 630eacbb..97bd0b61 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png index 6189a7ee..dcef21d2 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png new file mode 100755 index 00000000..3dc4bd98 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_blue.png new file mode 100755 index 00000000..b750b186 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_green.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_green.png new file mode 100755 index 00000000..42178e34 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_green.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png new file mode 100755 index 00000000..6ea401f9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png new file mode 100755 index 00000000..e6c16824 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_red.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_red.png new file mode 100755 index 00000000..47835152 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_red.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_yellow.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_yellow.png new file mode 100755 index 00000000..b01e97d6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_yellow.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/mute_blue.png new file mode 100755 index 00000000..365d4cc7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/mute_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-mdpi/mute_grey.png new file mode 100755 index 00000000..fb667464 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/mute_grey.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_crop.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_crop.png new file mode 100644 index 00000000..103f1c26 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_crop.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_edit.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_edit.png new file mode 100644 index 00000000..437baede Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_edit.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_filters.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_filters.png new file mode 100644 index 00000000..890d61d5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_filters.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_filters_active.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_filters_active.png new file mode 100644 index 00000000..62cfe9ab Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_filters_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_rotate.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_rotate.png new file mode 100644 index 00000000..cdfc8530 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_rotate.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_sizes.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_sizes.png new file mode 100644 index 00000000..34d1d2ba Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_sizes.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_tune.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_tune.png new file mode 100644 index 00000000..9db5621e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_tune.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photo_tune_active.png b/TMessagesProj/src/main/res/drawable-mdpi/photo_tune_active.png new file mode 100644 index 00000000..61eab0c5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/photo_tune_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/tip1.png b/TMessagesProj/src/main/res/drawable-mdpi/tip1.png new file mode 100755 index 00000000..40ae2af8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/tip1.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/tip2.png b/TMessagesProj/src/main/res/drawable-mdpi/tip2.png new file mode 100755 index 00000000..09ccb58b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/tip2.png differ diff --git a/TMessagesProj/src/main/res/drawable-v21/floating_states.xml b/TMessagesProj/src/main/res/drawable-v21/floating_states.xml new file mode 100644 index 00000000..f92baa8a --- /dev/null +++ b/TMessagesProj/src/main/res/drawable-v21/floating_states.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable-v21/floating_user_states.xml b/TMessagesProj/src/main/res/drawable-v21/floating_user_states.xml new file mode 100644 index 00000000..41f9bd33 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable-v21/floating_user_states.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable-v21/regbtn_states.xml b/TMessagesProj/src/main/res/drawable-v21/regbtn_states.xml new file mode 100644 index 00000000..4c8d5884 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable-v21/regbtn_states.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating.png index a7e9c09f..5523f817 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating.png and b/TMessagesProj/src/main/res/drawable-xhdpi/floating.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png index 1ee194c0..c838c541 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png and b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_m.png new file mode 100755 index 00000000..5a1e0bae Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png index 3268ca33..5ae6d6fc 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png and b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating4_profile.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating4_profile.png deleted file mode 100755 index 87a90ab7..00000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating4_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating4_profile_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating4_profile_pressed.png deleted file mode 100755 index e4af9d2f..00000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating4_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png new file mode 100755 index 00000000..aa5089de Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_m.png new file mode 100755 index 00000000..0e5daf6a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png new file mode 100755 index 00000000..91428317 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_pencil.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_pencil.png new file mode 100755 index 00000000..adf178ac Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_pencil.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png index 81cccd44..7a28b212 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png new file mode 100755 index 00000000..0dcf82c9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_arrow_drop_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png index 190df823..f91f6665 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png index 2c34f5dc..41ea21d8 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png index d9d8b481..7d8f350a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png new file mode 100755 index 00000000..89685206 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_blue.png new file mode 100755 index 00000000..f1b17b1b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_green.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_green.png new file mode 100755 index 00000000..049dc598 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_green.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png new file mode 100755 index 00000000..f701d7d9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png new file mode 100755 index 00000000..78b4017c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_red.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_red.png new file mode 100755 index 00000000..69a0fd4d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_red.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_yellow.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_yellow.png new file mode 100755 index 00000000..fc6126d9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_yellow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/mute_blue.png new file mode 100755 index 00000000..e5e62eae Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/mute_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-xhdpi/mute_grey.png new file mode 100755 index 00000000..73b1e397 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/mute_grey.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_crop.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_crop.png new file mode 100644 index 00000000..d69c3c12 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_crop.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_edit.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_edit.png new file mode 100644 index 00000000..33176600 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_edit.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_filters.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_filters.png new file mode 100644 index 00000000..61102102 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_filters.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_filters_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_filters_active.png new file mode 100644 index 00000000..6a309075 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_filters_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_rotate.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_rotate.png new file mode 100644 index 00000000..968e91dc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_rotate.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_sizes.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_sizes.png new file mode 100644 index 00000000..01f3176f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_sizes.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_tune.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_tune.png new file mode 100644 index 00000000..38edad85 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_tune.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photo_tune_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/photo_tune_active.png new file mode 100644 index 00000000..e65b8036 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/photo_tune_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/tip1.png b/TMessagesProj/src/main/res/drawable-xhdpi/tip1.png new file mode 100755 index 00000000..a6a75ed7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/tip1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/tip2.png b/TMessagesProj/src/main/res/drawable-xhdpi/tip2.png new file mode 100755 index 00000000..8fb813f9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/tip2.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png index e8d384d6..65abfe39 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png index 7c811457..3715273b 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_m.png new file mode 100755 index 00000000..f9b8bc1f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png index 76f2e6b3..291e574f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating4_profile.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating4_profile.png deleted file mode 100755 index 97378cc0..00000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating4_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating4_profile_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating4_profile_pressed.png deleted file mode 100755 index db33c079..00000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating4_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png new file mode 100755 index 00000000..ca6d5ef9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_m.png new file mode 100755 index 00000000..6a353103 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png new file mode 100755 index 00000000..40451f29 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pencil.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pencil.png new file mode 100755 index 00000000..a93db573 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pencil.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png index 37e2a6f1..26d91f19 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_arrow_drop_down.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_arrow_drop_down.png new file mode 100755 index 00000000..8127958a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_arrow_drop_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png index 6fbc4045..591886a3 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png index c924710a..f1971d6b 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png index 0c75f96f..35227013 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png new file mode 100755 index 00000000..5ec0a940 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_blue.png new file mode 100755 index 00000000..9d977e67 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_green.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_green.png new file mode 100755 index 00000000..f428e86b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_green.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png new file mode 100755 index 00000000..1381ece3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png new file mode 100755 index 00000000..c4d4125d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_red.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_red.png new file mode 100755 index 00000000..863e477c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_red.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_yellow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_yellow.png new file mode 100755 index 00000000..580af5c5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_yellow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mute_blue.png new file mode 100755 index 00000000..4860d2f2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/mute_blue.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mute_grey.png new file mode 100755 index 00000000..e6f3ded3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/mute_grey.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_crop.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_crop.png new file mode 100644 index 00000000..460fdb38 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_crop.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_edit.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_edit.png new file mode 100644 index 00000000..6df74a5b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_edit.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_contrast.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_contrast.png new file mode 100644 index 00000000..78aa2b41 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_contrast.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_exposure.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_exposure.png new file mode 100644 index 00000000..5feb667a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_exposure.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_grain.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_grain.png new file mode 100644 index 00000000..297bc2dc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_grain.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_highlights.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_highlights.png new file mode 100644 index 00000000..ce156a02 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_highlights.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_saturation.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_saturation.png new file mode 100644 index 00000000..c1a14fa7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_saturation.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_shadows.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_shadows.png new file mode 100644 index 00000000..f3ead532 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_shadows.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_sharpen.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_sharpen.png new file mode 100644 index 00000000..4d6687ba Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_sharpen.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_vignette.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_vignette.png new file mode 100644 index 00000000..e72d3d71 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_vignette.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_warmth.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_warmth.png new file mode 100644 index 00000000..9b9a258e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_editor_warmth.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_filters.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_filters.png new file mode 100644 index 00000000..29abc7ed Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_filters.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_filters_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_filters_active.png new file mode 100644 index 00000000..a5bb25e5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_filters_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_rotate.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_rotate.png new file mode 100644 index 00000000..0c1a9c77 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_rotate.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_sizes.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_sizes.png new file mode 100644 index 00000000..40a564e6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_sizes.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_tune.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_tune.png new file mode 100644 index 00000000..254d18c7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_tune.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photo_tune_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_tune_active.png new file mode 100644 index 00000000..3c4e0587 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/photo_tune_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/tip1.png b/TMessagesProj/src/main/res/drawable-xxhdpi/tip1.png new file mode 100755 index 00000000..5d9960e3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/tip1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/tip2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/tip2.png new file mode 100755 index 00000000..bc35c8e0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/tip2.png differ diff --git a/TMessagesProj/src/main/res/drawable/floating_group_states.xml b/TMessagesProj/src/main/res/drawable/floating_group_states.xml deleted file mode 100644 index 785cc037..00000000 --- a/TMessagesProj/src/main/res/drawable/floating_group_states.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/mute_fixed.xml b/TMessagesProj/src/main/res/drawable/mute_fixed.xml new file mode 100644 index 00000000..77b676a9 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/mute_fixed.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/layout/intro_layout.xml b/TMessagesProj/src/main/res/layout/intro_layout.xml index 74fd2ec4..08faa318 100644 --- a/TMessagesProj/src/main/res/layout/intro_layout.xml +++ b/TMessagesProj/src/main/res/layout/intro_layout.xml @@ -40,7 +40,7 @@ android:layout_marginTop="336dp" android:background="@drawable/regbtn_states" android:layout_gravity="center_horizontal" - android:textSize="18dp" + android:textSize="16dp" android:textColor="#ffffff" android:gravity="center" android:paddingLeft="20dp" diff --git a/TMessagesProj/src/main/res/layout/media_layout.xml b/TMessagesProj/src/main/res/layout/media_layout.xml deleted file mode 100644 index 04b9f3a8..00000000 --- a/TMessagesProj/src/main/res/layout/media_layout.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - diff --git a/TMessagesProj/src/main/res/layout/messages_list.xml b/TMessagesProj/src/main/res/layout/messages_list.xml index b051134b..7e685fb8 100644 --- a/TMessagesProj/src/main/res/layout/messages_list.xml +++ b/TMessagesProj/src/main/res/layout/messages_list.xml @@ -83,7 +83,8 @@ %1$s يستخدم إصدار قديم من تيليجرام، لذلك، الصور السرية ستظهر في وضع الموافقة.\n\nعندما يقوم %2$s بتحديث تيليجرام، الصور التي بها عداد دقيقة أو أقل ستعمل بطريقة \"الاستمرار بالضغط للإستعراض\"، وسيتم إخبارك عندما يلتقط المستقبل صورة من شاشته. الرسائل بحث + كتم الإشعارات + ضعه على الصامت لمدة %1$s + إزالة كتم الصوت + خلال %1$s رسالة جماعية جديدة أدخل اسم القائمة @@ -73,6 +77,7 @@ جذر النظام بطاقة الذاكرة مجلد + أرسل الصورة بدون ضغطها مخفي جاري الكتابة… @@ -83,7 +88,7 @@ صورة موقع مقطع مرئي - مستند + ملف ...لا توجد رسائل بعد الرسالة المعاد توجيهها من @@ -105,6 +110,7 @@ حذف هذه الدردشة قم بالسحب للإلغاء حفظ في الجهاز + مشاركة تطبيق ملف التعريب المرفق غير مدعوم عداد التدمير الذاتي @@ -121,7 +127,7 @@ %1$s قام بإرسال مقطع مرئي لك %1$s قام بإرسال جهة اتصال لك %1$s قام بإرسال موقع لك - %1$s قام بإرسال مستند لك + %1$s قام بإرسال ملف لك %1$s قام بإرسال مقطع صوتي لك %1$s قام بإرسال ملصق %1$s @ %2$s: %3$s @@ -130,7 +136,7 @@ %1$s قام بإرسال مقطع مرئي للمجموعة %2$s %1$s قام بإرسال جهة اتصال للمجموعة %2$s %1$s قام بإرسال موقع للمجموعة %2$s - %1$s قام بإرسال مستند للمجموعة %2$s + %1$s قام بإرسال ملف للمجموعة %2$s %1$s قام بإرسال مقطع صوتي للمجموعة %2$s %1$s قام بإرسال ملصق للمجموعة %2$s %1$s قام بدعوتك للمجموعة %2$s @@ -151,7 +157,7 @@ اختر جهة اتصال لا توجد جهات اتصال بعد https://telegram.org/dl مرحبا! هيا نستخدم تيليجرام: - اليوم الساعة + في أمس الساعة متصل آخر ظهور @@ -291,7 +297,9 @@ الرقم %1$s لديه حساب تيليجرام مسبقًا. يرجى حذف هذا الحساب قبل محاولة تغيير رقمك. آخر - لا توجد وسائط بعد + شارك المقاطع المرئية والصور في هذه المحادثة لتستطيع الوصول إليها من أية جهاز من أجهزتك + ملفات + شارك الملفات والمستندات في هذه المحادثة لتستطيع الوصول إليها من أية جهاز من أجهزتك الخريطة قمر صناعي @@ -315,6 +323,7 @@ البحث على الإنترنت البحث عن صور متحركة قص الصورة + تعديل الصورة Password Change password @@ -418,7 +427,7 @@ مقطع مرئي موقع جهة اتصال - مستند + ملف ملصق مقطع صوتي أنت @@ -502,6 +511,12 @@ %1$d رسائل %1$d رسالة %1$d رسالة + لا يوجد ملفات + %1$d ملف + %1$d ملفان + %1$d ملفات + %1$d ملف + %1$d ملف ليس من جهة اتصال من %1$d جهة اتصال من %1$d جهتان اتصال diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index c5d54e32..8fe9a5dc 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -53,6 +53,10 @@ %1$s benutzt eine ältere Version von Telegram, sodass Fotos in Geheimen Chats im Kompatibilitätsmodus angezeigt werden.\n\nSobald %2$s Telegram aktualisiert, werden Fotos mit Timern von 1 Minute und kürzer per \"Tippen und Halten\" angezeigt. Du wirst benachrichtigt, sobald dein Chatpartner ein Bildschirmfoto macht. NACHRICHTEN Suche + Stummschalten + Stumm für %1$s + Stumm aus + In %1$s Neue Broadcast Liste Listenname @@ -73,6 +77,7 @@ Systemverzeichnis SD-Karte Ordner + Bilder ohne Komprimierung senden unsichtbar schreibt… @@ -105,6 +110,7 @@ Diesen Chat löschen WISCHEN UM ABZUBRECHEN In Downloads speichern + Teilen Sprachdatei benutzen Nicht unterstützte Datei Selbstzerstörungs-Timer setzen @@ -151,7 +157,7 @@ Kontakt auswählen Noch keine Kontakte Hey, komm auch zu Telegram: https://telegram.org/dl - heute um + um gestern um online zul. online @@ -270,9 +276,9 @@ Systemvorgabe Telegramvorgabe Automatischer Mediendownload - über Mobilfunk - über W-LAN - bei Roaming + Über Mobilfunk + Über W-LAN + Bei Roaming kein automatischer Download In der Galerie speichern Name bearbeiten @@ -291,7 +297,9 @@ Die Telefonnummer %1$s ist bereits ein Telegram Konto. Bitte lösche es, bevor du mit der Übertragung auf das neue Konto startest. Sonstige - Noch keine geteilten Medien vorhanden + Die hier geteilten Bilder und Videos kannst du von jedem deiner Geräte aufrufen. + Dateien + Die hier geteilten Bilder und Videos kannst du von jedem deiner Geräte aufrufen. Karte Satellit @@ -315,6 +323,7 @@ Suche Bilder Suche GIFs Bild zuschneiden + Bild bearbeiten Password Change password @@ -502,6 +511,12 @@ %1$d Nachrichten %1$d Nachrichten %1$d Nachrichten + keine Dateien + %1$d Datei + %1$d Dateien + %1$d Dateien + %1$d Dateien + %1$d Dateien von keinem Kontakt von %1$d Kontakt von %1$d Kontakten diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index 8d9449a1..c59b5b3e 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -34,7 +34,7 @@ ayer Sin resultados Aún sin chats... - Envía mensajes pulsando el botón para\nredactar, en la parte superior derecha,\no pulsa el botón menú para más opciones. + Envía mensajes pulsando el botón para\nredactar, en la parte inferior derecha,\no pulsa el botón menú para más opciones. Esperando red... Conectando... Actualizando... @@ -53,6 +53,10 @@ %1$s usa una versión antigua de Telegram, así que las fotos secretas serán mostradas en un modo de compatibilidad.\n\nCuando %2$s actualice Telegram, las fotos con autodestrucción de 1 minuto o menos funcionarán con el modo \'Mantén pulsado para ver\', y te notificaremos siempre que la otra parte haga una captura de pantalla. MENSAJES Buscar + Silenciar notificaciones + Silenciar %1$s + No silenciar + En %1$s Nueva difusión Nombre de la lista @@ -73,6 +77,7 @@ Raíz del Sistema Tarjeta SD Carpeta + Para enviar imágenes sin compresión invisible escribiendo... @@ -105,6 +110,7 @@ Eliminar este chat DESLIZA PARA CANCELAR Guardar en descargas + Compartir Aplicar traducción Adjunto no soportado Establecer autodestrucción @@ -151,7 +157,7 @@ Elegir contacto Aún sin contactos ¡Oye! Cambiémonos a Telegram: https://telegram.org/dl - hoy a las + a las ayer a las en línea últ. vez @@ -169,7 +175,7 @@ Nombre del grupo %1$d/%2$d miembros - Fotos y vídeos + Multimedia compartida Ajustes Añadir miembro Eliminar y dejar el grupo @@ -291,7 +297,9 @@ El número %1$s ya está vinculado a una cuenta de Telegram. Por favor, elimina esa cuenta antes de migrar al nuevo número. Otras - Aún no hay fotos ni vídeos + Comparte fotos y vídeos en este chat y accede a ellos desde cualquier dispositivo. + Archivos + Comparte archivos en este chat y accede a ellos desde cualquier dispositivo. Mapa Satélite @@ -306,7 +314,7 @@ %1$d de %2$d Galería Todas las fotos - Sin fotos aún + Aún sin fotos Por favor, primero descarga la multimedia No hay fotos recientes No hay GIF recientes @@ -315,6 +323,7 @@ Buscar foto Buscar GIF Cortar imagen + Editar imagen Password Change password @@ -502,6 +511,12 @@ %1$d mensajes %1$d mensajes %1$d mensajes + sin archivos + %1$d archivo + %1$d archivos + %1$d archivos + %1$d archivos + %1$d archivos unused de %1$d contacto de %1$d contactos diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index 0a96183d..2a2f3da8 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -53,6 +53,10 @@ %1$s sta usando una versione vecchia di Telegram, quindi le foto segrete verranno visualizzate in modalità di compatibilità.\n\nUna volta che %2$s avrà aggiornato Telegram, le foto con il timer minore di 1 minuto funzioneranno in modalità \'Tieni premuto per vedere\' , e verrai notificato ogni volta che l\'altro esegue uno screenshot. MESSAGGI Cerca + Silenzia notifiche + Silenzia per %1$s + Suona + Tra %1$s Nuova lista broadcast Immetti il nome della lista @@ -73,6 +77,7 @@ Root di sistema Scheda SD Cartella + Per inviare immagini senza compressione invisibile sta scrivendo… @@ -105,6 +110,7 @@ Elimina questa chat ANNULLA Salva in download + Condividi Applica file di localizzazione Allegato non supportato Timer di autodistruzione @@ -151,7 +157,7 @@ Seleziona contatto Ancora nessun contatto Ehi, è il momento di passare a Telegram: https://telegram.org/dl - oggi alle + alle ieri alle in linea ultimo accesso @@ -174,7 +180,7 @@ Aggiungi membro Elimina e lascia il gruppo Notifiche - Rimuovi dal gruppo + Espelli dal gruppo Condividi Aggiungi @@ -291,7 +297,9 @@ Il numero %1$s è già connesso a un account Telegram. Per favore elimina quell\'account prima di migrare ad un nuovo numero. Altro - Nessun media condiviso + Condividi foto e video in questa chat e accedi ad essi da ogni tuo dispositivo. + File + Condividi file e documenti in questa chat e accedi ad essi da ogni tuo dispositivo. Mappa Satellite @@ -315,6 +323,7 @@ Cerca su web Cerca GIF Ritaglia immagine + Modifica immagine Password Change password @@ -440,7 +449,7 @@ Aggiungere %1$s al gruppo?\n\nNumero di messaggi recenti da inoltrare: Vuoi inoltrare i messaggi a %1$s? Inviare i messaggi a %1$s? - Sei sicuro di volerti disconnettere?\n\nRicorda che puoi usare Telegram su tutti i tuoi device contemporaneamente.\n\nRicorda, disconnettersi elimina tutte le Chat Segrete. + Sei sicuro di volerti disconnettere?\n\nRicorda che puoi usare Telegram su tutti i tuoi device insieme.\n\nRicorda, disconnettersi elimina tutte le Chat Segrete. Chiudere tutte le altre sessioni? Eliminare il gruppo e uscire da esso? Eliminare questa chat? @@ -502,6 +511,12 @@ %1$d messaggi %1$d messaggi %1$d messaggi + nessun file + %1$d file + %1$d file + %1$d file + %1$d file + %1$d file da nessun contatto da %1$d contatto da %1$d contatti diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index 8d20fb86..08923ac6 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -53,6 +53,10 @@ %1$s님의 텔레그램 버전이 낮아 비밀 사진을 호환성 모드로 표시합니다.\n\n%2$s님이 텔레그램을 업데이트하고 나면, 자동삭제 시간이 1분 이하인 사진은 \"탭하고 누르고 있어야 보임\" 상태가 되며, 상대방이 화면을 캡처할 때 마다 알림을 받습니다. 메시지 검색 + 알림 음소거 + %1$s 동안 음소거 + 음소거 해제 + %1$s 후 새 단체 메시지 리스트 리스트 이름을 입력하세요 @@ -73,6 +77,7 @@ 시스템 루트 SD 카드 폴더 + 압축 없이 사진 보내기 숨김 입력 중... @@ -83,7 +88,7 @@ 앨범 위치 동영상 - 문서 + 파일 메시지가 없습니다... 전달된 메시지 보낸 사람 @@ -105,6 +110,7 @@ 이 채팅방 삭제 밀어서 취소 다운로드 폴더에 저장 + 공유 언어 파일 적용 지원하지 않는 형식입니다 자동삭제 타이머 설정 @@ -121,7 +127,7 @@ %1$s님이 동영상을 보냈습니다 %1$s님이 연락처를 공유했습니다 %1$s님이 위치를 보냈습니다 - %1$s님이 문서를 보냈습니다 + %1$s님이 파일을 보냈습니다 %1$s님이 음성메시지를 보냈습니다 %1$s님이 스티커를 보냈습니다 %1$s @ %2$s: %3$s @@ -130,7 +136,7 @@ %1$s님이 %2$s 그룹에 동영상을 보냈습니다 %1$s님이 %2$s 그룹에 연락처를 공유했습니다 %1$s님이 %2$s 그룹에 위치를 보냈습니다 - %1$s님이 %2$s 그룹에 문서를 보냈습니다 + %1$s님이 %2$s 그룹에 파일을 보냈습니다 %1$s님이 %2$s 그룹에 음성메시지를 보냈습니다 %1$s님이 %2$s 그룹에 스티커를 보냈습니다 %1$s님이 %2$s 그룹에 초대했습니다 @@ -291,7 +297,9 @@ 그 번호는 이미 텔레그램 계정에 연결되어 있습니다. 새 번호로 이동하기 전에 %1$s 계정에서 탈퇴해 주세요. 기타 - 공유한 미디어가 없습니다 + 이 채팅방에서 사진이나 동영상을 공유하면 다른 기기에서도 보실 수 있습니다. + 파일 + 이 채팅방에서 파일이나 문서를 공유하면 다른 기기에서도 보실 수 있습니다. 지도 위성 @@ -315,6 +323,7 @@ 웹 검색 GIF 검색 사진 자르기 + 이미지 편집 Password Change password @@ -344,12 +353,12 @@ 개인정보 마지막 접속 전체 공개 - 내 주소록 + 내 대화상대 비공개 전체 공개 (-%1$d) - 내 주소록 (+%1$d) - 내 주소록 (-%1$d) - 내 주소록 (-%1$d, +%2$d) + 내 대화상대 (+%1$d) + 내 대화상대 (-%1$d) + 내 대화상대 (-%1$d, +%2$d) 비공개 (+%1$d) 보안 회원 탈퇴 @@ -418,7 +427,7 @@ 동영상 위치 연락처 - 문서 + 파일 스티커 음성메시지 @@ -502,6 +511,12 @@ 메시지 %1$d건 메시지 %1$d건 메시지 %1$d건 + 파일 없음 + 파일 %1$d개 + 파일 %1$d개 + 파일 %1$d개 + 파일 %1$d개 + 파일 %1$d개 보낸곳 없음 - 채팅방 %1$d개에서 채팅방 %1$d개에서 diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index 56fe3cab..945b90a4 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -34,7 +34,7 @@ gisteren Geen resultaten Nog geen chats - Begin een gesprek door op de\nopstellen-knop rechtsboven te drukken\nof druk op de menu knop voor meer opties. + Begin een gesprek door op de\nopstelknop rechtsonder te drukken\nof druk op de menuknop voor meer opties. Wachten op netwerk Verbinden Bijwerken @@ -53,6 +53,10 @@ %1$s gebruikt een oudere versie van Telegram, dus worden geheime foto\'s weergegeven in de compatibiliteitsmodus.\n\nZodra %2$s Telegram updatet werken foto\'s met timers voor 1 minuut of minder in de \'Druk en houd ingedrukt\'-modus en krijg je een bericht wanneer de andere partij een schermafdruk maakt. BERICHTEN Zoeken + Meldingen stil + %1$s stil + Stil uitschakelen + Over %1$s Nieuwe verzendlijst Naam van lijst @@ -73,6 +77,7 @@ Systeemmap SD-kaart Map + Om bestanden ongecomprimeerd te versturen onzichtbaar aan het typen @@ -105,6 +110,7 @@ Chat verwijderen SLEEP OM TE ANNULEREN Opslaan in Downloads + Delen Vertaling toepassen Bestandstype niet ondersteund Zelfvernietigingstimer instellen @@ -151,7 +157,7 @@ Contact kiezen Nog geen contacten Hey, zullen we overstappen op Telegram: https://telegram.org/dl - vandaag om + om gisteren om online gezien @@ -291,7 +297,9 @@ Aan telefoonnummer %1$s is al een Telegram-account gekoppeld. Verwijder het account om te kunnen migreren naar het nieuwe nummer. Overig - Nog geen media gedeeld + Deel foto\'s en video\'s in deze chat om ze op al je apparaten te kunnen benaderen. + Bestanden + Deel bestanden en documenten in deze chat om ze op al je apparaten te kunnen benaderen. Kaart Satelliet @@ -315,6 +323,7 @@ Online zoeken GIF\'s zoeken Foto bijsnijden + Foto bewerken Password Change password @@ -502,6 +511,12 @@ %1$d berichten %1$d berichten %1$d berichten + geen bestanden + %1$d bestand + %1$d bestanden + %1$d bestanden + %1$d bestanden + %1$d bestanden van geen enkel contact van %1$d contacten van %1$d contacten diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index 7819c1bb..bced87c6 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -34,7 +34,7 @@ ontem Nenhum resultado Ainda não há chats... - Comece a conversar pressionando o\nbotão \'Nova Mensagem\' no canto superior direito\nou vá para a seção \'Contatos\'. + Comece a conversar pressionando o\nbotão \'Nova Mensagem\' no canto inferior direito\nou vá para a seção \'Contatos\'. Aguardando rede... Conectando... Atualizando... @@ -53,12 +53,16 @@ %1$s está usando uma versão mais antiga do Telegram, por isso fotos secretas serão mostradas em modo de compatibilidade.\n\nAssim que %2$s atualizar o Telegram, fotos com timers de 1 minuto ou menos passarão a funcionar no modo ‘Toque e segure para ver’, e você será notificado caso a outra pessoa salve a tela. MENSAGENS Busca + Silenciar notificações + Silenciar por %1$s + Restaurar Som + Em %1$s - Nova Lista de Broadcast + Nova Lista de Transmissão Digite o nome da lista - Você criou uma lista de broadcast + Você criou uma lista de transmissão Adicionar destinatário - Remover da lista de broadcast + Remover da lista de transmissão Selecione um Arquivo Disponível %1$s de %2$s @@ -73,6 +77,7 @@ Administrador do Sistema Cartão SD Pasta + Para enviar imagens sem compressão invisível escrevendo... @@ -83,7 +88,7 @@ Galeria Localização Vídeo - Documento + Arquivo Ainda não há mensagens aqui... Mensagem encaminhada De @@ -105,6 +110,7 @@ Apagar este chat DESLIZE PARA CANCELAR Salvar em downloads + Compartilhar Aplicar arquivo de localização Anexo não suportado Definir timer de autodestruição @@ -121,16 +127,16 @@ %1$s te enviou um vídeo %1$s compartilhou um contato com você %1$s enviou uma localização - %1$s te enviou um documento + %1$s lhe enviou um arquivo %1$s te enviou um áudio - %1$s te enviou um sticker + %1$s lhe enviou um sticker %1$s @ %2$s: %3$s %1$s enviou uma mensagem para o grupo %2$s %1$s enviou uma foto para o grupo %2$s %1$s enviou um vídeo para o grupo %2$s %1$s compartilhou um contato para o grupo %2$s %1$s enviou uma localização para o grupo %2$s - %1$s enviou um documento para o grupo %2$s + %1$s enviou um arquivo para o grupo %2$s %1$s enviou um áudio para o grupo %2$s %1$s enviou um sticker ao grupo %2$s %1$s convidou você para o grupo %2$s @@ -151,7 +157,7 @@ Selecionar Contato Ainda não há contatos Ei, vamos mudar para o Telegram: https://telegram.org/dl - hoje às + às ontem às online visto @@ -213,7 +219,7 @@ Restaurar todas as configurações de notificação Tamanho do texto nas mensagens Fazer uma pergunta - Permitir animações + Permitir Animações Desbloquear Toque e segure no usuário para desbloquear Nenhum usuário bloqueado @@ -236,7 +242,7 @@ Sem som Padrão Suporte - Papel de parede + Papel de Parede Mensagens Enviar usando \'Enter\' Terminar todas as outras sessões @@ -283,15 +289,17 @@ Máxima Nunca Repetir Notificações - Você pode trocar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\nImportante:]]> todos os contatos do Telegram terão seu novo número]]> adicionado às suas lista de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. - Todos os contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. - TROCAR NÚMERO + Você pode alterar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\nImportante:]]> todos os contatos do Telegram terão seu novo número]]> adicionado às suas lista de contatos, desde que eles tenham seu número antigo e você não os tenha bloqueado no Telegram. + Todos os seus contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. + ALTERAR NÚMERO Novo número Vamos enviar uma SMS com um código de confirmação para o seu novo número. O número %1$s já possui uma conta do Telegram. Por favor, delete esta conta antes de migrar para o novo número. Outro - Ainda não há mídia compartilhada + Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. + Arquivos + Compartilhar arquivos e documentos no chat e acessá-los de qualquer um de seus dispositivos. Mapa Satélite @@ -314,7 +322,8 @@ PROCURAR GIFS Procurar na web Procurar GIFs - Cortar imagem + Recortar imagem + Editar imagem Password Change password @@ -359,14 +368,14 @@ Alterar quem pode ver o seu Último Acesso. Quem pode ver o seu Último Acesso? Adicionar exceções - Importante: você não será capaz de ver quando foi o Último Acesso das pessoas com quem você não compartilha quando foi seu Último Acesso. Você visualizará o Último acesso aproximado (recentemente, dentro de uma semana, dentro de um mês). + Importante: você não poderá ver quando foi o Último Acesso das pessoas com quem você não compartilha o seu Último Acesso. Você visualizará o Último acesso aproximado (recentemente, dentro de uma semana, dentro de um mês). Sempre Mostrar Para Nunca Mostrar Para Estas configurações irão substituir os valores anteriores. Sempre Mostrar - Sempre mostrar para usuários... + Sempre compartilhar para os usuários... Nunca Mostrar - Nunca mostrar para usuários... + Nunca mostrar para os usuários... Adicionar Usuários Desculpe, muitas solicitações. Impossível alterar os ajustes de privacidade agora, por favor aguarde. Sair de todos os dispositivos, exceto este. @@ -401,15 +410,15 @@ un1 saiu do grupo un1 adicionou un2 un1 removeu foto do grupo - un1 mudou a foto do grupo - un1 mudou o nome do grupo para un2 + un1 alterou a foto do grupo + un1 alterou o nome do grupo para un2 un1 criou o grupo Você removeu un2 Você saiu do grupo Você adicionou un2 Você removeu a foto do grupo - Você mudou a foto do grupo - Você mudou o nome do grupo para un2 + Você alterou a foto do grupo + Você alterou o nome do grupo para un2 Você criou o grupo un1 removeu você un1 adicionou você @@ -418,7 +427,7 @@ Vídeo Localização Contato - Documento + Arquivo Sticker Áudio Você @@ -502,6 +511,12 @@ %1$d mensagens %1$d mensagens %1$d mensagens + nenhum arquivo + %1$d arquivo + %1$d arquivos + %1$d arquivos + %1$d arquivos + %1$d arquivos de nenhum contato de %1$d contato de %1$d contatos diff --git a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml index 9d37c537..31f5ae72 100644 --- a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml @@ -34,7 +34,7 @@ ontem Nenhum resultado Ainda não há chats... - Comece a conversar pressionando o\nbotão \'Nova Mensagem\' no canto superior direito\nou vá para a seção \'Contatos\'. + Comece a conversar pressionando o\nbotão \'Nova Mensagem\' no canto inferior direito\nou vá para a seção \'Contatos\'. Aguardando rede... Conectando... Atualizando... @@ -53,12 +53,16 @@ %1$s está usando uma versão mais antiga do Telegram, por isso fotos secretas serão mostradas em modo de compatibilidade.\n\nAssim que %2$s atualizar o Telegram, fotos com timers de 1 minuto ou menos passarão a funcionar no modo ‘Toque e segure para ver’, e você será notificado caso a outra pessoa salve a tela. MENSAGENS Busca + Silenciar notificações + Silenciar por %1$s + Restaurar Som + Em %1$s - Nova Lista de Broadcast + Nova Lista de Transmissão Digite o nome da lista - Você criou uma lista de broadcast + Você criou uma lista de transmissão Adicionar destinatário - Remover da lista de broadcast + Remover da lista de transmissão Selecione um Arquivo Disponível %1$s de %2$s @@ -73,6 +77,7 @@ Administrador do Sistema Cartão SD Pasta + Para enviar imagens sem compressão invisível escrevendo... @@ -83,7 +88,7 @@ Galeria Localização Vídeo - Documento + Arquivo Ainda não há mensagens aqui... Mensagem encaminhada De @@ -105,6 +110,7 @@ Apagar este chat DESLIZE PARA CANCELAR Salvar em downloads + Compartilhar Aplicar arquivo de localização Anexo não suportado Definir timer de autodestruição @@ -121,16 +127,16 @@ %1$s te enviou um vídeo %1$s compartilhou um contato com você %1$s enviou uma localização - %1$s te enviou um documento + %1$s lhe enviou um arquivo %1$s te enviou um áudio - %1$s te enviou um sticker + %1$s lhe enviou um sticker %1$s @ %2$s: %3$s %1$s enviou uma mensagem para o grupo %2$s %1$s enviou uma foto para o grupo %2$s %1$s enviou um vídeo para o grupo %2$s %1$s compartilhou um contato para o grupo %2$s %1$s enviou uma localização para o grupo %2$s - %1$s enviou um documento para o grupo %2$s + %1$s enviou um arquivo para o grupo %2$s %1$s enviou um áudio para o grupo %2$s %1$s enviou um sticker ao grupo %2$s %1$s convidou você para o grupo %2$s @@ -151,7 +157,7 @@ Selecionar Contato Ainda não há contatos Ei, vamos mudar para o Telegram: https://telegram.org/dl - hoje às + às ontem às online visto @@ -213,7 +219,7 @@ Restaurar todas as configurações de notificação Tamanho do texto nas mensagens Fazer uma pergunta - Permitir animações + Permitir Animações Desbloquear Toque e segure no usuário para desbloquear Nenhum usuário bloqueado @@ -236,7 +242,7 @@ Sem som Padrão Suporte - Papel de parede + Papel de Parede Mensagens Enviar usando \'Enter\' Terminar todas as outras sessões @@ -283,15 +289,17 @@ Máxima Nunca Repetir Notificações - Você pode trocar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\nImportante:]]> todos os contatos do Telegram terão seu novo número]]> adicionado às suas lista de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. - Todos os contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. - TROCAR NÚMERO + Você pode alterar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\nImportante:]]> todos os contatos do Telegram terão seu novo número]]> adicionado às suas lista de contatos, desde que eles tenham seu número antigo e você não os tenha bloqueado no Telegram. + Todos os seus contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. + ALTERAR NÚMERO Novo número Vamos enviar uma SMS com um código de confirmação para o seu novo número. O número %1$s já possui uma conta do Telegram. Por favor, delete esta conta antes de migrar para o novo número. Outro - Ainda não há mídia compartilhada + Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. + Arquivos + Compartilhar arquivos e documentos no chat e acessá-los de qualquer um de seus dispositivos. Mapa Satélite @@ -314,7 +322,8 @@ PROCURAR GIFS Procurar na web Procurar GIFs - Cortar imagem + Recortar imagem + Editar imagem Password Change password @@ -359,14 +368,14 @@ Alterar quem pode ver o seu Último Acesso. Quem pode ver o seu Último Acesso? Adicionar exceções - Importante: você não será capaz de ver quando foi o Último Acesso das pessoas com quem você não compartilha quando foi seu Último Acesso. Você visualizará o Último acesso aproximado (recentemente, dentro de uma semana, dentro de um mês). + Importante: você não poderá ver quando foi o Último Acesso das pessoas com quem você não compartilha o seu Último Acesso. Você visualizará o Último acesso aproximado (recentemente, dentro de uma semana, dentro de um mês). Sempre Mostrar Para Nunca Mostrar Para Estas configurações irão substituir os valores anteriores. Sempre Mostrar - Sempre mostrar para usuários... + Sempre compartilhar para os usuários... Nunca Mostrar - Nunca mostrar para usuários... + Nunca mostrar para os usuários... Adicionar Usuários Desculpe, muitas solicitações. Impossível alterar os ajustes de privacidade agora, por favor aguarde. Sair de todos os dispositivos, exceto este. @@ -401,15 +410,15 @@ un1 saiu do grupo un1 adicionou un2 un1 removeu foto do grupo - un1 mudou a foto do grupo - un1 mudou o nome do grupo para un2 + un1 alterou a foto do grupo + un1 alterou o nome do grupo para un2 un1 criou o grupo Você removeu un2 Você saiu do grupo Você adicionou un2 Você removeu a foto do grupo - Você mudou a foto do grupo - Você mudou o nome do grupo para un2 + Você alterou a foto do grupo + Você alterou o nome do grupo para un2 Você criou o grupo un1 removeu você un1 adicionou você @@ -418,7 +427,7 @@ Vídeo Localização Contato - Documento + Arquivo Sticker Áudio Você @@ -502,6 +511,12 @@ %1$d mensagens %1$d mensagens %1$d mensagens + nenhum arquivo + %1$d arquivo + %1$d arquivos + %1$d arquivos + %1$d arquivos + %1$d arquivos de nenhum contato de %1$d contato de %1$d contatos diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index e6da462b..0f856f43 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -53,6 +53,10 @@ %1$s is using an older version of Telegram, so secret photos will be shown in compatibility mode.\n\nOnce %2$s updates Telegram, photos with timers for 1 minute or less will start working in \'Tap and hold to view\' mode, and you will be notified whenever the other party takes a screenshot. MESSAGES Search + Mute notifications + Mute for %1$s + Unmute + In %1$s New Broadcast List Enter list name @@ -73,6 +77,7 @@ System Root SD Card Folder + To send images without compression invisible typing... @@ -83,7 +88,7 @@ Gallery Location Video - Document + File No messages here yet... Forwarded message From @@ -105,6 +110,7 @@ Delete this chat SLIDE TO CANCEL Save to downloads + Share Apply localization file Unsupported attachment Set self-destruct timer @@ -121,7 +127,7 @@ %1$s sent you a video %1$s shared a contact with you %1$s sent you a location - %1$s sent you a document + %1$s sent you a file %1$s sent you an audio %1$s sent you a sticker %1$s @ %2$s: %3$s @@ -130,7 +136,7 @@ %1$s sent a video to the group %2$s %1$s shared a contact in the group %2$s %1$s sent a location to the group %2$s - %1$s sent a document to the group %2$s + %1$s sent a file to the group %2$s %1$s sent an audio to the group %2$s %1$s sent a sticker to the group %2$s %1$s invited you to the group %2$s @@ -151,7 +157,7 @@ Select Contact No contacts yet Hey, let\'s switch to Telegram: https://telegram.org/dl - today at + at yesterday at online last seen @@ -291,7 +297,9 @@ The number %1$s is already connected to a Telegram account. Please delete that account before migrating to the new number. Other - No shared media yet + Share photos and videos in this chat and access them on any of your devices. + Files + Share files and documents in this chat and access them on any of your devices. Map Satellite @@ -315,6 +323,7 @@ Search web Search GIFs Crop image + Edit image Password Change password @@ -418,7 +427,7 @@ Video Location Contact - Document + File Sticker Audio You @@ -502,6 +511,12 @@ %1$d messages %1$d messages %1$d messages + no files + %1$d file + %1$d files + %1$d files + %1$d files + %1$d files from no contacts from %1$d contact from %1$d contacts